diff --git a/PhyConDebugLogging.xml b/PhyConDebugLogging.xml
new file mode 100644
index 0000000..62f2153
--- /dev/null
+++ b/PhyConDebugLogging.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 0561ae3..dbb768d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id "fabric-loom" version "0.5-SNAPSHOT"
+ id "fabric-loom" version "0.6.49"
id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id "com.github.johnrengelman.shadow" version "4.0.4"
@@ -13,6 +13,7 @@ version = project.mod_version
group = project.maven_group
minecraft {
+ log4jConfigs.from "PhyConDebugLogging.xml"
}
repositories {
diff --git a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java
index 737b239..e15ed87 100644
--- a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java
+++ b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java
@@ -1,8 +1,11 @@
package net.shadowfacts.phycon.api.util;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author shadowfacts
@@ -25,6 +28,20 @@ public final class IPAddress {
return random(ipAddressRandom);
}
+ private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
+ @Nullable
+ public static IPAddress parse(String s) {
+ Matcher matcher = IP_PATTERN.matcher(s);
+ if (!matcher.matches()) {
+ return null;
+ }
+ int a = Integer.parseInt(matcher.group(1));
+ int b = Integer.parseInt(matcher.group(2));
+ int c = Integer.parseInt(matcher.group(3));
+ int d = Integer.parseInt(matcher.group(4));
+ return new IPAddress(a, b, c, d);
+ }
+
public static final IPAddress BROADCAST = new IPAddress(0xff_ff_ff_ff);
public final int address;
diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt
index 1a1459b..d43573f 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt
@@ -6,8 +6,8 @@ import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens
-import net.shadowfacts.phycon.networking.C2STerminalRequestItem
-import net.shadowfacts.phycon.networking.ServerReceiver
+import net.shadowfacts.phycon.networking.*
+import org.apache.logging.log4j.LogManager
/**
* @author shadowfacts
@@ -16,6 +16,8 @@ object PhysicalConnectivity: ModInitializer {
val MODID = "phycon"
+ val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
+
override fun onInitialize() {
PhyBlocks.init()
PhyBlockEntities.init()
@@ -23,6 +25,9 @@ object PhysicalConnectivity: ModInitializer {
PhyScreens.init()
registerGlobalReceiver(C2STerminalRequestItem)
+ registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
+ registerGlobalReceiver(C2SConfigureActivationMode)
+ registerGlobalReceiver(C2SConfigureRedstoneController)
}
private fun registerGlobalReceiver(receiver: ServerReceiver) {
diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt
index 4575fbb..c32c7b4 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt
@@ -2,19 +2,30 @@ package net.shadowfacts.phycon
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
import net.minecraft.client.render.RenderLayer
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.network.block.terminal.TerminalScreen
+import net.shadowfacts.phycon.networking.ClientReceiver
+import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
/**
* @author shadowfacts
*/
object PhysicalConnectivityClient: ClientModInitializer {
+
override fun onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent())
ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
+
+ registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
}
+
+ private fun registerGlobalReceiver(receiver: ClientReceiver) {
+ ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
+ }
+
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt
index 8f9bd00..a092601 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt
@@ -13,6 +13,8 @@ import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.test.DestBlock
@@ -30,6 +32,7 @@ object PhyBlockEntities {
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
val MINER = create(::MinerBlockEntity, PhyBlocks.MINER)
+ val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER)
val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE)
val DEST = create(::DestBlockEntity, PhyBlocks.DEST)
@@ -44,6 +47,7 @@ object PhyBlockEntities {
register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
+ register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt
index 9a90e4f..caabd63 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt
@@ -8,6 +8,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
@@ -23,6 +24,7 @@ object PhyBlocks {
val CABLE = CableBlock()
val EXTRACTOR = ExtractorBlock()
val MINER = MinerBlock()
+ val REDSTONE_CONTROLLER = RedstoneControllerBlock()
val SOURCE = SourceBlock()
val DEST = DestBlock()
@@ -34,6 +36,7 @@ object PhyBlocks {
register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
+ register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt
index a25f779..ff75216 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt
@@ -11,6 +11,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock
import net.shadowfacts.phycon.network.block.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock
@@ -26,6 +27,7 @@ object PhyItems {
val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings())
val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
val MINER = BlockItem(PhyBlocks.MINER, Item.Settings())
+ val REDSTONE_CONTROLLER = BlockItem(PhyBlocks.REDSTONE_CONTROLLER, Item.Settings())
val SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings())
val DEST = BlockItem(PhyBlocks.DEST , Item.Settings())
@@ -40,6 +42,7 @@ object PhyItems {
register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER)
+ register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST)
diff --git a/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt b/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt
index f5cb03a..8df0680 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt
@@ -8,7 +8,11 @@ import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
+import net.shadowfacts.phycon.network.component.ActivationController
+import net.shadowfacts.phycon.screen.ActivatableDeviceConsoleScreen
import net.shadowfacts.phycon.screen.DeviceConsoleScreen
+import net.shadowfacts.phycon.screen.RedstoneControllerConsoleScreen
import net.shadowfacts.phycon.screen.TestCacaoScreen
/**
@@ -37,9 +41,13 @@ class ConsoleItem: Item(Settings()) {
}
private fun openScreen(be: DeviceBlockEntity) {
-// val screen = DeviceConsoleScreen(be)
val screen = TestCacaoScreen()
+// val screen = when (be) {
+// is ActivationController.ActivatableDevice -> ActivatableDeviceConsoleScreen(be)
+// is RedstoneControllerBlockEntity -> RedstoneControllerConsoleScreen(be)
+// else -> DeviceConsoleScreen(be)
+// }
MinecraftClient.getInstance().openScreen(screen)
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt
index 42a68b9..b30553b 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt
@@ -6,6 +6,7 @@ import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
+import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface
@@ -18,8 +19,7 @@ import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.ARPQueryFrame
import net.shadowfacts.phycon.network.frame.ARPResponseFrame
import net.shadowfacts.phycon.network.frame.BasePacketFrame
-import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket
-import java.lang.RuntimeException
+import net.shadowfacts.phycon.network.packet.*
import java.util.*
/**
@@ -52,36 +52,45 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
abstract override fun handle(packet: Packet)
+ private fun doHandlePacket(packet: Packet) {
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
+ when (packet) {
+ is DeviceRemovedPacket -> {
+ arpTable.remove(packet.source)
+ }
+ }
+ handle(packet)
+ }
+
override fun send(frame: EthernetFrame) {
findDestination()?.receive(frame)
}
override fun receive(frame: EthernetFrame) {
- println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame)
when (frame) {
is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame)
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
- println("$this ($ipAddress) received packet: ${frame.packet}")
- handle(frame.packet)
+ doHandlePacket(frame.packet)
}
}
}
}
private fun handleARPQuery(frame: ARPQueryFrame) {
- println("$this ($ipAddress) received ARP query for ${frame.queryIP}")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) {
- println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress)
send(ARPResponseFrame(ipAddress, macAddress, frame.source))
}
}
private fun handleARPResponse(frame: ARPResponseFrame) {
arpTable[frame.query] = frame.source
- println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source)
val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) {
@@ -102,7 +111,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} else {
packetQueue.add(PendingPacket(packet, counter))
- println("$this ($ipAddress) sending ARP query for ${packet.destination}")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination)
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
}
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt
new file mode 100644
index 0000000..b787008
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt
@@ -0,0 +1,107 @@
+package net.shadowfacts.phycon.network
+
+import net.minecraft.block.Block
+import net.minecraft.block.BlockState
+import net.minecraft.block.ShapeContext
+import net.minecraft.item.ItemPlacementContext
+import net.minecraft.state.StateManager
+import net.minecraft.state.property.EnumProperty
+import net.minecraft.state.property.Properties
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+import net.minecraft.util.shape.VoxelShape
+import net.minecraft.util.shape.VoxelShapes
+import net.minecraft.world.BlockView
+import net.minecraft.world.World
+import net.minecraft.world.WorldAccess
+import net.shadowfacts.phycon.api.Interface
+import net.shadowfacts.phycon.api.NetworkComponentBlock
+import net.shadowfacts.phycon.network.block.cable.CableBlock
+import java.util.*
+
+
+/**
+ * @author shadowfacts
+ */
+abstract class FaceDeviceBlock(settings: Settings): DeviceBlock(settings) {
+ companion object {
+ val FACING = Properties.FACING
+ val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
+ }
+
+ protected abstract val faceThickness: Double
+ protected abstract val faceShapes: Map
+ private val centerShapes: Map by lazy {
+ mapOf(
+ Direction.DOWN to createCuboidShape(6.0, faceThickness, 6.0, 10.0, 10.0, 10.0),
+ Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 16.0 - faceThickness, 10.0),
+ Direction.NORTH to createCuboidShape(6.0, 6.0, faceThickness, 10.0, 10.0, 10.0),
+ Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 16.0 - faceThickness),
+ Direction.WEST to createCuboidShape(faceThickness, 6.0, 6.0, 10.0, 10.0, 10.0),
+ Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 16.0 - faceThickness, 10.0, 10.0)
+ )
+ }
+ private val shapeCache = mutableMapOf, VoxelShape>()
+
+ fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
+ return shapeCache.getOrPut(facing to cableConnection) {
+ VoxelShapes.union(
+ faceShapes[facing],
+ centerShapes[facing],
+ CableBlock.SIDE_SHAPES[cableConnection]
+ )
+ }
+ }
+
+ override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection {
+ return EnumSet.of(state[CABLE_CONNECTION])
+ }
+
+ override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
+ return if (side == state[FACING]) {
+ null
+ } else {
+ getBlockEntity(world, pos)
+ }
+ }
+
+ override fun appendProperties(builder: StateManager.Builder) {
+ super.appendProperties(builder)
+ builder.add(FACING)
+ builder.add(CABLE_CONNECTION)
+ }
+
+ override fun getPlacementState(context: ItemPlacementContext): BlockState {
+ val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
+ val cableConnection = getCableConnectedSide(context.world, context.blockPos, facing) ?: facing.opposite
+ return defaultState
+ .with(FACING, facing)
+ .with(CABLE_CONNECTION, cableConnection)
+ }
+
+ protected fun getCableConnectedSide(world: World, pos: BlockPos, facing: Direction): Direction? {
+ for (side in Direction.values()) {
+ if (side == facing) {
+ continue
+ }
+ val offsetPos = pos.offset(side)
+ val state = world.getBlockState(offsetPos)
+ val block = state.block
+ if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) {
+ return side
+ }
+ }
+ return null
+ }
+
+ override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
+ if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
+ return state.with(CABLE_CONNECTION, side)
+ }
+ return state
+ }
+
+ override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
+ return getShape(state[FACING], state[CABLE_CONNECTION])
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt
index 90f0de9..7d83833 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt
@@ -1,59 +1,126 @@
package net.shadowfacts.phycon.network.block.extractor
import alexiil.mc.lib.attributes.SearchOptions
+import alexiil.mc.lib.attributes.Simulation
import alexiil.mc.lib.attributes.item.FixedItemInv
-import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes
+import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
+import net.minecraft.block.BlockState
+import net.minecraft.item.ItemStack
+import net.minecraft.nbt.CompoundTag
import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
-import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.component.ActivationController
+import net.shadowfacts.phycon.network.component.NetworkStackDispatcher
+import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.CapacityPacket
-import net.shadowfacts.phycon.network.packet.CheckCapacityPacket
import net.shadowfacts.phycon.network.packet.ItemStackPacket
+import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
+import net.shadowfacts.phycon.util.ActivationMode
+import kotlin.properties.Delegates
/**
* @author shadowfacts
*/
-class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) {
+class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
+ NetworkStackDispatcher,
+ ActivationController.ActivatableDevice {
+
+ companion object {
+ val SLEEP_TIME = 40L
+ }
private val facing: Direction
get() = cachedState[ExtractorBlock.FACING]
- private var inventory: GroupedItemInv? = null
- private var shouldExtract = false
+ private var inventory: FixedItemInv? = null
+ override val pendingInsertions = mutableListOf()
+ override val dispatchStackTimeout = 40L
+ override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() {
val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing)
- inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option)
+ inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
}
- private fun getInventory(): GroupedItemInv? {
+ private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory()
return inventory
}
override fun handle(packet: Packet) {
- if (packet is CapacityPacket && shouldExtract) {
- getInventory()?.also { inv ->
- shouldExtract = false
- val extracted = inv.extract(packet.stack, packet.capacity)
- sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress))
- }
+ when (packet) {
+ is CapacityPacket -> handleCapacity(packet)
+ is ItemStackPacket -> handleItemStack(packet)
+ is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
}
}
+ override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
+ // we can't insert things back into the inventory, so just let them spawn
+ return packet.stack
+ }
+
+ override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
+
+ override fun finishInsertion(insertion: PendingInsertion): ItemStack {
+ val inventory = getInventory() ?: return insertion.stack
+ // if the inventory has changed, the old slot index is meaningless
+ if (inventory !== insertion.inventory) return insertion.stack
+ val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
+ if (extracted.isEmpty) return insertion.stack
+ // if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
+ insertion.stack = extracted
+ return super.finishInsertion(insertion)
+ }
+
override fun tick() {
super.tick()
- if (!world!!.isClient && counter % 40 == 0L) {
- getInventory()?.also {
- val stack = it.storedStacks.firstOrNull() ?: return
- shouldExtract = true
- sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST))
- }
+ if (!world!!.isClient) {
+ controller.tick()
}
}
-}
\ No newline at end of file
+
+ override fun activate(): Boolean {
+ val inventory = getInventory() ?: return false
+ for (slot in 0 until inventory.slotCount) {
+ val slotStack = inventory.getInvStack(slot)
+ if (slotStack.isEmpty) continue
+ dispatchItemStack(slotStack) { insertion ->
+ insertion.inventory = inventory
+ insertion.inventorySlot = slot
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun toTag(tag: CompoundTag): CompoundTag {
+ tag.putString("ActivationMode", controller.activationMode.name)
+ return super.toTag(tag)
+ }
+
+ override fun fromTag(state: BlockState, tag: CompoundTag) {
+ super.fromTag(state, tag)
+ controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
+ }
+
+ override fun toClientTag(tag: CompoundTag): CompoundTag {
+ tag.putString("ActivationMode", controller.activationMode.name)
+ return super.toClientTag(tag)
+ }
+
+ override fun fromClientTag(tag: CompoundTag) {
+ super.fromClientTag(tag)
+ controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
+ }
+
+ class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion(stack, timestamp) {
+ lateinit var inventory: FixedItemInv
+ var inventorySlot by Delegates.notNull()
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt
index cab6826..0a091ff 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt
@@ -4,103 +4,39 @@ import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.*
import net.minecraft.entity.LivingEntity
-import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack
-import net.minecraft.state.StateManager
-import net.minecraft.state.property.EnumProperty
-import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
-import net.minecraft.util.shape.VoxelShape
-import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
-import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponentBlock
-import net.shadowfacts.phycon.api.Interface
-import net.shadowfacts.phycon.network.DeviceBlock
-import net.shadowfacts.phycon.network.block.cable.CableBlock
-import java.util.*
+import net.shadowfacts.phycon.network.FaceDeviceBlock
/**
* @author shadowfacts
*/
-class InterfaceBlock: DeviceBlock(Settings.of(Material.METAL)),
+class InterfaceBlock: FaceDeviceBlock(Settings.of(Material.METAL)),
NetworkComponentBlock,
AttributeProvider {
companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
- val FACING = Properties.FACING
- val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
- private val SIDE_SHAPES = mapOf(
- Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
- Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
- Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
- Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
- Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
- Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
- )
- private val CENTER_SHAPES = mapOf(
- Direction.DOWN to createCuboidShape(6.0, 2.0, 6.0, 10.0, 10.0, 10.0),
- Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 14.0, 10.0),
- Direction.NORTH to createCuboidShape(6.0, 6.0, 2.0, 10.0, 10.0, 10.0),
- Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 14.0),
- Direction.WEST to createCuboidShape(2.0, 6.0, 6.0, 10.0, 10.0, 10.0),
- Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 14.0, 10.0, 10.0)
- )
-
- private val shapeCache = mutableMapOf, VoxelShape>()
- fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
- return shapeCache.getOrPut(facing to cableConnection) {
- VoxelShapes.union(
- VoxelShapes.union(SIDE_SHAPES[facing], CENTER_SHAPES[facing]),
- CableBlock.SIDE_SHAPES[cableConnection]
- )
- }
- }
}
- override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection {
- val set = EnumSet.of(state[CABLE_CONNECTION])
- set.remove(state[FACING])
- return set
- }
-
- override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
- return if (side == state[FACING]) {
- null
- } else {
- getBlockEntity(world, pos)
- }
- }
-
- override fun appendProperties(builder: StateManager.Builder) {
- super.appendProperties(builder)
- builder.add(FACING)
- builder.add(CABLE_CONNECTION)
- }
+ override val faceThickness = 2.0
+ override val faceShapes = mapOf(
+ Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
+ Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
+ Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
+ Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
+ Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
+ Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
+ )
override fun createBlockEntity(world: BlockView) = InterfaceBlockEntity()
- override fun getPlacementState(context: ItemPlacementContext): BlockState {
- val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
- val cableConnection = getCableConnectionSide(context.world, context.blockPos) ?: facing.opposite
- return defaultState.with(FACING, facing).with(CABLE_CONNECTION, cableConnection)
- }
-
- private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? {
- for (side in Direction.values()) {
- val offsetPos = pos.offset(side)
- if (world.getBlockState(offsetPos).block is NetworkComponentBlock) {
- return side
- }
- }
- return null
- }
-
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory()
@@ -113,19 +49,8 @@ class InterfaceBlock: DeviceBlock(Settings.of(Material.MET
}
}
- override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
- if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
- return state.with(CABLE_CONNECTION, side)
- }
- return state
- }
-
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos))
}
- override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
- return getShape(state[FACING], state[CABLE_CONNECTION])
- }
-
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt
index 85500f6..9cb58ae 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt
@@ -9,11 +9,13 @@ import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.FaceDeviceBlock
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.*
+import kotlin.math.min
/**
* @author shadowfacts
@@ -24,7 +26,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
NetworkStackReceiver {
private val facing: Direction
- get() = cachedState[InterfaceBlock.FACING]
+ get() = cachedState[FaceDeviceBlock.FACING]
// todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null
@@ -67,8 +69,16 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv ->
- val extracted = inv.extract(packet.stack, packet.amount)
- sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
+ var amount = packet.amount
+ while (amount > 0) {
+ val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount))
+ if (extracted.isEmpty) {
+ break
+ } else {
+ amount -= extracted.count
+ sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
+ }
+ }
}
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt
index 51d0d51..23c54b8 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt
@@ -7,6 +7,7 @@ import net.minecraft.entity.ItemEntity
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction
+import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
@@ -53,10 +54,10 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!!
- println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
interfaceForSide(dir).send(frame)
} else {
- println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame")
+ PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame)
flood(frame, fromItf)
}
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt
new file mode 100644
index 0000000..51d4773
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt
@@ -0,0 +1,74 @@
+package net.shadowfacts.phycon.network.block.redstone
+
+import net.minecraft.block.Block
+import net.minecraft.block.BlockState
+import net.minecraft.block.Material
+import net.minecraft.item.ItemPlacementContext
+import net.minecraft.server.world.ServerWorld
+import net.minecraft.state.StateManager
+import net.minecraft.state.property.Properties
+import net.minecraft.util.Identifier
+import net.minecraft.util.math.BlockPos
+import net.minecraft.util.math.Direction
+import net.minecraft.world.BlockView
+import net.minecraft.world.World
+import net.minecraft.world.WorldAccess
+import net.shadowfacts.phycon.PhysicalConnectivity
+import net.shadowfacts.phycon.network.FaceDeviceBlock
+import java.util.*
+
+/**
+ * @author shadowfacts
+ */
+class RedstoneControllerBlock: FaceDeviceBlock(Settings.of(Material.METAL)) {
+
+ companion object {
+ val ID = Identifier(PhysicalConnectivity.MODID, "redstone_controller")
+ val LIT = Properties.LIT
+ }
+
+ // todo: don't just copy this from the Interface block
+ override val faceThickness = 3.0
+ override val faceShapes = mapOf(
+ Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
+ Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
+ Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
+ Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
+ Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
+ Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
+ )
+
+ override fun appendProperties(builder: StateManager.Builder) {
+ super.appendProperties(builder)
+ builder.add(LIT)
+ }
+
+ override fun createBlockEntity(world: BlockView) = RedstoneControllerBlockEntity()
+
+ override fun getPlacementState(context: ItemPlacementContext): BlockState {
+ val state = super.getPlacementState(context)
+ return state.with(LIT, isPowered(context.world, context.blockPos, state[FACING]))
+ }
+
+ override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
+ // this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
+ if (!world.isClient) {
+ val wasLit = state[LIT]
+ val isLit = isPowered(world, pos, state[FACING])
+ if (wasLit != isLit) {
+ toggleLit(state, world, pos)
+ }
+ }
+ }
+
+ private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean {
+ val offset = pos.offset(facing)
+ return world.getEmittedRedstonePower(offset, facing) > 0
+ }
+
+ private fun toggleLit(state: BlockState, world: World, pos: BlockPos) {
+ world.setBlockState(pos, state.cycle(LIT), 2)
+ getBlockEntity(world, pos)!!.redstoneStateChanged()
+ }
+
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt
new file mode 100644
index 0000000..d9bbfbb
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt
@@ -0,0 +1,75 @@
+package net.shadowfacts.phycon.network.block.redstone
+
+import net.minecraft.block.BlockState
+import net.minecraft.nbt.CompoundTag
+import net.shadowfacts.phycon.api.packet.Packet
+import net.shadowfacts.phycon.api.util.IPAddress
+import net.shadowfacts.phycon.init.PhyBlockEntities
+import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
+import net.shadowfacts.phycon.util.RedstoneMode
+
+/**
+ * @author shadowfacts
+ */
+class RedstoneControllerBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER) {
+
+ var managedDevices = Array(5) { null }
+ var redstoneMode = RedstoneMode.HIGH
+
+ private var redstonePowered = false
+
+ override fun handle(packet: Packet) {
+ }
+
+ fun redstoneStateChanged() {
+ val oldPowered = redstonePowered
+ redstonePowered = cachedState[RedstoneControllerBlock.LIT]
+
+ val mode: RemoteActivationPacket.Mode? = when (redstoneMode) {
+ RedstoneMode.TOGGLE -> if (oldPowered != redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
+ RedstoneMode.RISING_EDGE -> if (!oldPowered && redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
+ RedstoneMode.FALLING_EDGE -> if (oldPowered && !redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
+ RedstoneMode.HIGH -> if (redstonePowered) RemoteActivationPacket.Mode.ENABLE else RemoteActivationPacket.Mode.DISABLE
+ RedstoneMode.LOW -> if (redstonePowered) RemoteActivationPacket.Mode.DISABLE else RemoteActivationPacket.Mode.ENABLE
+ }
+
+ if (mode != null) {
+ sendActivatePacket(mode)
+ }
+ }
+
+ private fun sendActivatePacket(mode: RemoteActivationPacket.Mode) {
+ for (ip in managedDevices) {
+ if (ip == null) continue
+ sendPacket(RemoteActivationPacket(mode, ipAddress, ip))
+ }
+ }
+
+ override fun toTag(tag: CompoundTag): CompoundTag {
+ tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
+ tag.putString("RedstoneMode", redstoneMode.name)
+ return super.toTag(tag)
+ }
+
+ override fun fromTag(state: BlockState, tag: CompoundTag) {
+ super.fromTag(state, tag)
+ val addresses = tag.getIntArray("ManagedDevices")
+ managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
+ redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
+ }
+
+ override fun toClientTag(tag: CompoundTag): CompoundTag {
+ tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
+ tag.putString("RedstoneMode", redstoneMode.name)
+ return super.toClientTag(tag)
+ }
+
+ override fun fromClientTag(tag: CompoundTag) {
+ super.fromClientTag(tag)
+ val addresses = tag.getIntArray("ManagedDevices")
+ managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
+ redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
+ }
+
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt
index eab52cf..e17af71 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt
@@ -56,7 +56,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private var observers = 0
val cachedNetItems = ItemStackCollections.intMap()
- var cachedSortedNetItems = listOf()
var netItemObserver: WeakReference? = null
@@ -86,14 +85,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private fun handleReadInventory(packet: ReadInventoryPacket) {
inventoryCache[packet.source] = packet.inventory
- updateNetItems()
- sync()
+ updateAndSync()
}
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
inventoryCache.remove(packet.source)
- updateNetItems()
- sync()
+ updateAndSync()
}
private fun handleStackLocation(packet: StackLocationPacket) {
@@ -113,12 +110,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
// this happens outside the normal update loop because by receiving the item stack packet
// we "know" how much the count in the source inventory has changed
- updateNetItems()
- sync()
+ updateAndSync()
return remaining
}
+ private fun updateAndSync() {
+ updateNetItems()
+ sync()
+ netItemObserver?.get()?.netItemsChanged()
+ }
+
private fun updateNetItems() {
cachedNetItems.clear()
for (inventory in inventoryCache.values) {
@@ -127,12 +129,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
}
}
- // todo: is the map necessary or is just the sorted list enough?
- cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
- val stack = it.key.copy()
- stack.count = it.intValue
- stack
- }
}
private fun beginInsertions() {
@@ -175,21 +171,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
finishTimedOutPendingInsertions()
}
- if (observers > 0) {
- if (world!!.isClient) {
- println(cachedNetItems)
- } else {
- updateNetItems()
- sync()
- }
+ if (observers > 0 && !world!!.isClient) {
+ updateAndSync()
}
}
}
fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) {
- updateNetItems()
- sync()
+ updateAndSync()
inventoryCache.clear()
sendPacket(RequestInventoryPacket(ipAddress))
@@ -238,8 +228,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
internalBuffer.setStack(insertion.bufferSlot, remaining)
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
- updateNetItems()
- sync()
+ updateAndSync()
return remaining
}
@@ -263,31 +252,11 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag())
- val list = ListTag()
- tag.put("CachedNetItems", list)
- for ((stack, amount) in cachedNetItems) {
- val entryTag = stack.toTag(CompoundTag())
- entryTag.putInt("NetAmount", amount)
- list.add(entryTag)
- }
return tag
}
override fun fromClientTag(tag: CompoundTag) {
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
- val list = tag.getList("CachedNetItems", 10)
- cachedNetItems.clear()
- for (entryTag in list) {
- val stack = ItemStack.fromTag(entryTag as CompoundTag)
- val netAmount = entryTag.getInt("NetAmount")
- cachedNetItems[stack] = netAmount
- }
- netItemObserver?.get()?.netItemsChanged()
- cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
- val stack = it.key.copy()
- stack.count = it.intValue
- stack
- }
}
interface NetItemObserver {
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt
index 372a17e..762c0e9 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt
@@ -1,18 +1,33 @@
package net.shadowfacts.phycon.network.block.terminal
-import com.mojang.blaze3d.platform.GlStateManager
+import com.mojang.blaze3d.systems.RenderSystem
+import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.ingame.HandledScreen
+import net.minecraft.client.gui.widget.AbstractButtonWidget
+import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
+import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory
+import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
+import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
+import net.shadowfacts.phycon.networking.C2STerminalRequestItem
+import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
+import net.shadowfacts.phycon.util.SortMode
+import net.shadowfacts.phycon.util.next
+import net.shadowfacts.phycon.util.prev
import org.lwjgl.glfw.GLFW
+import java.lang.NumberFormatException
+import kotlin.math.ceil
+import kotlin.math.floor
+import kotlin.math.min
/**
* @author shadowfacts
@@ -20,10 +35,32 @@ import org.lwjgl.glfw.GLFW
// todo: translate title
class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen(handler, playerInv, title) {
companion object {
- val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
+ private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
+ private val DIALOG = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png")
}
private lateinit var searchBox: TextFieldWidget
+ private lateinit var sortButton: SortButton
+ private lateinit var amountBox: TextFieldWidget
+ private var dialogStack = ItemStack.EMPTY
+ private var showingAmountDialog = false
+ set(value) {
+ val oldValue = field
+ field = value
+ for (e in dialogChildren) {
+ e.visible = value
+ }
+ amountBox.isVisible
+ searchBox.setSelected(!value)
+ amountBox.setSelected(value)
+ if (value && !oldValue) {
+ amountBox.text = "1"
+ }
+ }
+ private var dialogChildren = mutableListOf()
+
+ private val dialogWidth = 158
+ private val dialogHeight = 62
init {
backgroundWidth = 252
@@ -33,6 +70,9 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
override fun init() {
super.init()
+ children.clear()
+ dialogChildren.clear()
+
client!!.keyboard.setRepeatEvents(true)
searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search"))
@@ -42,37 +82,134 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
searchBox.isVisible = true
searchBox.setSelected(true)
searchBox.setEditableColor(0xffffff)
- children.add(searchBox)
+ addChild(searchBox)
+
+ sortButton = SortButton(x + 256, y + 0, handler.sortMode, {
+ requestUpdatedItems()
+ }, ::renderTooltip)
+ addButton(sortButton)
+
+ val dialogMinX = width / 2 - dialogWidth / 2
+ val dialogMinY = height / 2 - dialogHeight / 2
+ amountBox = TextFieldWidget(textRenderer, dialogMinX + 8, dialogMinY + 27, 80, 9, LiteralText("Amount"))
+ amountBox.setHasBorder(false)
+ amountBox.isVisible = false
+ amountBox.setSelected(false)
+ amountBox.setEditableColor(0xffffff)
+ amountBox.setTextPredicate {
+ if (it.isEmpty()) {
+ true
+ } else {
+ try {
+ Integer.parseInt(it) > 0
+ } catch (e: NumberFormatException) {
+ false
+ }
+ }
+ }
+ dialogChildren.add(amountBox)
+
+ val plusOne = SmallButton(dialogMinX + 7, dialogMinY + 7, 28, LiteralText("+1")) {
+ amountBox.intValue += 1
+ }
+ dialogChildren.add(plusOne)
+
+ val plusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 7, 28, LiteralText("+10")) {
+ amountBox.intValue = ceil((amountBox.intValue + 1) / 10.0).toInt() * 10
+ }
+ dialogChildren.add(plusTen)
+
+ val plusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 7, 28, LiteralText("+100")) {
+ amountBox.intValue = ceil((amountBox.intValue + 1) / 100.0).toInt() * 100
+ }
+ dialogChildren.add(plusHundred)
+
+ val minusOne = SmallButton(dialogMinX + 7, dialogMinY + 39, 28, LiteralText("-1")) {
+ amountBox.intValue -= 1
+ }
+ dialogChildren.add(minusOne)
+
+ val minusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 39, 28, LiteralText("-10")) {
+ amountBox.intValue = floor((amountBox.intValue - 1) / 10.0).toInt() * 10
+ }
+ dialogChildren.add(minusTen)
+
+ val minusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 39, 28, LiteralText("-100")) {
+ amountBox.intValue = floor((amountBox.intValue - 1) / 100.0).toInt() * 100
+ }
+ dialogChildren.add(minusHundred)
+
+ // 101,25
+ val request = ButtonWidget(dialogMinX + 101, dialogMinY + 21, 50, 20, LiteralText("Request")) {
+ doDialogRequest()
+ }
+ dialogChildren.add(request)
+
+ requestUpdatedItems()
+ }
+
+ private fun requestUpdatedItems() {
+ val player = MinecraftClient.getInstance().player!!
+ player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchBox.text, sortButton.mode))
}
override fun tick() {
super.tick()
searchBox.tick()
+ amountBox.tick()
}
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
- // todo: translate this
- textRenderer.draw(matrixStack, "Buffer", 7f, 6f, 0x404040)
+ textRenderer.draw(matrixStack, TranslatableText("gui.phycon.terminal_buffer"), 7f, 6f, 0x404040)
}
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
- renderBackground(matrixStack)
+ // if the dialog is open, the background gradient will be drawn in front of the main terminal gui
+ if (!showingAmountDialog) {
+ renderBackground(matrixStack)
+ }
- GlStateManager.color4f(1f, 1f, 1f, 1f)
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND)
val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
}
+ @ExperimentalUnsignedTypes
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
- super.render(matrixStack, mouseX, mouseY, delta)
+ if (showingAmountDialog) {
+ RenderSystem.pushMatrix()
+ // items are rendered at some stupidly high z offset. item amounts at an even higher one
+ RenderSystem.translatef(0f, 0f, -350f)
+
+ // fake the mouse x/y while showing a dialog so slot mouseover highlights aren't drawn
+ super.render(matrixStack, -1, -1, delta)
+
+ RenderSystem.popMatrix()
+ } else {
+ super.render(matrixStack, mouseX, mouseY, delta)
+ }
searchBox.render(matrixStack, mouseX, mouseY, delta)
- drawMouseoverTooltip(matrixStack, mouseX, mouseY)
+ if (showingAmountDialog) {
+ renderBackground(matrixStack)
+
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
+ client!!.textureManager.bindTexture(DIALOG)
+ val dialogMinX = width / 2 - dialogWidth / 2
+ val dialogMinY = height / 2 - dialogHeight / 2
+ drawTexture(matrixStack, dialogMinX, dialogMinY, 0, 0, dialogWidth, dialogHeight)
+
+ for (e in dialogChildren) {
+ e.render(matrixStack, mouseX, mouseY, delta)
+ }
+ } else {
+ drawMouseoverTooltip(matrixStack, mouseX, mouseY)
+ }
}
@ExperimentalUnsignedTypes
@@ -90,42 +227,208 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
}
- override fun onMouseClick(slot: Slot?, i: Int, j: Int, slotActionType: SlotActionType?) {
- super.onMouseClick(slot, i, j, slotActionType)
+ override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
+ super.onMouseClick(slot, invSlot, clickData, type)
// don't unfocus the search box on mouse click
searchBox.setSelected(true)
+
+ if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
+ val stack = slot.stack
+
+ if (type == SlotActionType.QUICK_MOVE) {
+ // shift click, request full stack
+ requestItem(stack, min(stack.count, stack.maxCount))
+ } else if (type == SlotActionType.PICKUP) {
+ if (clickData == 1) {
+ // right click, request half stack
+ requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
+ } else {
+ dialogStack = stack
+ showingAmountDialog = true
+ searchBox.setSelected(false)
+ }
+ }
+ }
+ }
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ if (showingAmountDialog) {
+ for (e in dialogChildren) {
+ if (e.mouseClicked(mouseX, mouseY, button)) {
+ return true
+ }
+ }
+ return false
+ } else {
+ return super.mouseClicked(mouseX, mouseY, button)
+ }
+ }
+
+ override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double): Boolean {
+ if (showingAmountDialog) {
+ return false
+ } else {
+ return super.mouseDragged(d, e, i, f, g)
+ }
+ }
+
+ override fun mouseMoved(d: Double, e: Double) {
+ if (showingAmountDialog) {
+ } else {
+ super.mouseMoved(d, e)
+ }
+ }
+
+ override fun mouseReleased(d: Double, e: Double, i: Int): Boolean {
+ if (showingAmountDialog) {
+ return false
+ } else {
+ return super.mouseReleased(d, e, i)
+ }
+ }
+
+ override fun mouseScrolled(d: Double, e: Double, f: Double): Boolean {
+ if (showingAmountDialog) {
+ return false
+ } else {
+ return super.mouseScrolled(d, e, f)
+ }
}
override fun charTyped(c: Char, i: Int): Boolean {
- val oldText = searchBox.text
- if (searchBox.charTyped(c, i)) {
- if (searchBox.text != oldText) {
- search()
+ if (showingAmountDialog) {
+ return amountBox.charTyped(c, i)
+ } else {
+ val oldText = searchBox.text
+ if (searchBox.charTyped(c, i)) {
+ if (searchBox.text != oldText) {
+ requestUpdatedItems()
+ }
+ return true
}
- return true
- }
- return super.charTyped(c, i)
+ return super.charTyped(c, i)
+ }
}
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
- val oldText = searchBox.text
- if (searchBox.keyPressed(key, j, k)) {
- if (searchBox.text != oldText) {
- search()
+ if (showingAmountDialog) {
+ return when (key) {
+ GLFW.GLFW_KEY_ESCAPE -> {
+ showingAmountDialog = false
+ true
+ }
+ GLFW.GLFW_KEY_ENTER -> {
+ doDialogRequest()
+ true
+ }
+ else -> {
+ amountBox.keyPressed(key, j, k)
+ }
}
- return true
- }
- return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
- true
} else {
- super.keyPressed(key, j, k)
+ val oldText = searchBox.text
+ if (searchBox.keyPressed(key, j, k)) {
+ if (searchBox.text != oldText) {
+ requestUpdatedItems()
+ }
+ return true
+ }
+ return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
+ true
+ } else {
+ super.keyPressed(key, j, k)
+ }
}
}
- private fun search() {
- screenHandler.search(searchBox.text)
+ private fun doDialogRequest() {
+ showingAmountDialog = false
+ requestItem(dialogStack, amountBox.intValue)
+ }
+
+ private fun requestItem(stack: ItemStack, amount: Int) {
+ val netHandler = MinecraftClient.getInstance().player!!.networkHandler
+ val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
+ netHandler.sendPacket(packet)
+ }
+
+ private var TextFieldWidget.intValue: Int
+ get() = if (text.isEmpty()) 0 else Integer.parseInt(text)
+ set(value) {
+ text = value.toString()
+ setSelected(true)
+ }
+
+ class SmallButton(x: Int, y: Int, width: Int, title: Text, action: PressAction): ButtonWidget(x, y, width, 14, title, action) {
+ @ExperimentalUnsignedTypes
+ override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
+ val client = MinecraftClient.getInstance()
+ client.textureManager.bindTexture(DIALOG)
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
+ val v = if (isHovered) 142 else 128
+ RenderSystem.enableBlend()
+ RenderSystem.defaultBlendFunc()
+ RenderSystem.enableDepthTest()
+ drawTexture(matrixStack, x, y, 0, v, width / 2, height)
+ drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, v, width / 2, height)
+ drawCenteredText(matrixStack, client.textRenderer, message, x + width / 2, y + (height - 8) / 2, 0xffffffffu.toInt())
+ }
+ }
+
+ class SortButton(
+ x: Int,
+ y: Int,
+ var mode: SortMode,
+ val onChange: (SortMode) -> Unit,
+ val doRenderTooltip: (MatrixStack, Text, Int, Int) -> Unit
+ ): AbstractPressableButtonWidget(x, y, 20, 20, LiteralText("")) {
+ override fun onPress() {}
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ if ((button == 0 || button == 1) && clicked(mouseX, mouseY)) {
+ val newVal = if (button == 0) mode.next else mode.prev
+ mode = newVal
+ onChange(mode)
+
+ playDownSound(MinecraftClient.getInstance().soundManager)
+ return true
+ }
+ return false
+ }
+
+ override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
+ val client = MinecraftClient.getInstance()
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
+ RenderSystem.enableBlend()
+ RenderSystem.defaultBlendFunc()
+ RenderSystem.enableDepthTest()
+
+ client.textureManager.bindTexture(WIDGETS_LOCATION)
+ val k = getYImage(isHovered)
+ drawTexture(matrixStack, x, y, 0, 46 + k * 20, width / 2, height)
+ drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, 46 + k * 20, width / 2, height)
+
+ client.textureManager.bindTexture(BACKGROUND)
+ val u: Int = when (mode) {
+ SortMode.COUNT_HIGH_FIRST -> 0
+ SortMode.COUNT_LOW_FIRST -> 16
+ SortMode.ALPHABETICAL -> 32
+ }
+ drawTexture(matrixStack, x + 2, y + 2, u, 230, 16, 16)
+
+ if (isHovered) {
+ renderToolTip(matrixStack, mouseX, mouseY)
+ }
+ }
+
+ override fun renderToolTip(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
+ val text = LiteralText("")
+ text.append("Sort by: ")
+ text.append(mode.tooltip)
+ doRenderTooltip(matrixStack, text, mouseX, mouseY)
+ }
}
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt
index e648f07..e361c85 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt
@@ -8,12 +8,15 @@ import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler
+import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem
+import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
+import net.shadowfacts.phycon.util.SortMode
import java.lang.ref.WeakReference
import kotlin.math.ceil
import kotlin.math.min
@@ -21,7 +24,7 @@ import kotlin.math.min
/**
* @author shadowfacts
*/
-class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
+class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
TerminalBlockEntity.NetItemObserver {
companion object {
@@ -30,6 +33,19 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
private val fakeInv = FakeInventory(this)
private var searchQuery: String = ""
+ var sortMode = SortMode.COUNT_HIGH_FIRST
+ private set
+ private var itemEntries = listOf()
+ set(value) {
+ field = value
+ if (terminal.world!!.isClient) {
+ itemsForDisplay = value.map {
+ val stack = it.stack.copy()
+ stack.count = it.amount
+ stack
+ }
+ }
+ }
var itemsForDisplay = listOf()
private set
@@ -37,8 +53,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init {
- terminal.netItemObserver = WeakReference(this)
- netItemsChanged()
+ if (!terminal.world!!.isClient) {
+ terminal.netItemObserver = WeakReference(this)
+ netItemsChanged()
+ }
// network
for (y in 0 until 6) {
@@ -67,7 +85,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
override fun netItemsChanged() {
- itemsForDisplay = terminal.cachedNetItems.object2IntEntrySet().filter {
+ val player = playerInv.player
+ assert(player is ServerPlayerEntity)
+
+ val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
if (searchQuery.isBlank()) return@filter true
if (searchQuery.startsWith('@')) {
val unprefixed = searchQuery.drop(1)
@@ -77,20 +98,34 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
}
it.key.name.string.contains(searchQuery, true)
- }.sortedByDescending {
- it.intValue
- }.map {
- val stack = it.key.copy()
- stack.count = it.intValue
- stack
}
+
+ val sorted =
+ when (sortMode) {
+ SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
+ SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
+ SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
+ }
+
+ itemEntries = sorted.map { Entry(it.key, it.intValue) }
+
+ (player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode))
}
- fun search(query: String) {
- searchQuery = query
+ fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode) {
+ this.searchQuery = query
+ this.sortMode = sortMode
netItemsChanged()
}
+ fun receivedUpdatedItemsFromServer(entries: List, query: String, sortMode: SortMode) {
+ assert(playerInv.player.world.isClient)
+
+ this.searchQuery = query
+ this.sortMode = sortMode
+ itemEntries = entries
+ }
+
override fun canUse(player: PlayerEntity): Boolean {
return true
}
@@ -102,28 +137,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
}
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
- if (isNetworkSlot(slotId)) {
- // the slot clicked was one of the network stacks
- if (actionType == SlotActionType.QUICK_MOVE) {
- val stack = slots[slotId].stack
- if (!stack.isEmpty && player.world.isClient) {
- requestItem(player, stack, min(stack.count, stack.maxCount))
-
- }
- } else if (actionType == SlotActionType.PICKUP && clickData == 1) {
- if (clickData == 1) {
- // right click, request half stack
- val stack = slots[slotId].stack
- if (!stack.isEmpty && player.world.isClient) {
- requestItem(player, stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
- }
- } else {
- // todo: left click, show amount dialog
- }
- }
- return ItemStack.EMPTY
- } else if (isBufferSlot(slotId)) {
- // internal buffer
+ if (isBufferSlot(slotId)) {
// todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
// placing cursor stack into buffer
@@ -134,13 +148,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
return super.onSlotClick(slotId, clickData, actionType, player)
}
- private fun requestItem(player: PlayerEntity, stack: ItemStack, amount: Int) {
- if (!player.world.isClient) return
- val handler = (player as ClientPlayerEntity).networkHandler
- val packet = C2STerminalRequestItem(terminal, stack, amount)
- handler.sendPacket(packet)
- }
-
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY;
@@ -154,7 +161,8 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
val result = slot.stack.copy()
if (isBufferSlot(slotId)) {
- if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, false)) {
+ // last boolean param is fromLast
+ if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
return ItemStack.EMPTY
}
if (slot.stack.isEmpty) {
@@ -204,4 +212,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
+
+ data class Entry(val stack: ItemStack, val amount: Int)
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt
new file mode 100644
index 0000000..5ec6b2c
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt
@@ -0,0 +1,60 @@
+package net.shadowfacts.phycon.network.component
+
+import net.minecraft.block.entity.BlockEntity
+import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
+import net.shadowfacts.phycon.util.ActivationMode
+
+/**
+ * @author shadowfacts
+ */
+class ActivationController(
+ private val sleepInterval: Long,
+ private val device: T
+) where T: ActivationController.ActivatableDevice, T: BlockEntity {
+
+ var activationMode = ActivationMode.AUTOMATIC
+ set(value) {
+ field = value
+ // when the activation mode changes, reset the remote enabled status
+ remotelyEnabled = false
+ }
+
+ private var lastActivation = -sleepInterval
+ private var remotelyEnabled = false
+
+ fun tick() {
+ if (activationMode == ActivationMode.AUTOMATIC || remotelyEnabled) {
+ tryActivate()
+ }
+ }
+
+ fun handleRemoteActivation(packet: RemoteActivationPacket) {
+ if (activationMode != ActivationMode.MANAGED) {
+ return
+ }
+
+ when (packet.mode) {
+ RemoteActivationPacket.Mode.SINGLE -> tryActivate()
+ RemoteActivationPacket.Mode.ENABLE -> remotelyEnabled = true
+ RemoteActivationPacket.Mode.DISABLE -> remotelyEnabled = false
+ }
+ }
+
+ private fun tryActivate() {
+ assert(!device.world!!.isClient)
+ if ((device.counter - lastActivation) < sleepInterval) {
+ return
+ }
+ if (device.activate()) {
+ lastActivation = device.counter
+ }
+ }
+
+ interface ActivatableDevice {
+ val controller: ActivationController<*>
+
+ val counter: Long
+
+ fun activate(): Boolean
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt
index 1b6d7b8..6d2f3f6 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt
@@ -33,7 +33,8 @@ interface NetworkStackDispatcher>(
- val stack: ItemStack,
+ var stack: ItemStack,
val timestamp: Long
) {
val results = mutableSetOf>()
@@ -77,5 +79,6 @@ fun > Self.f
val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion)
+ // todo: do something with remaining?
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt
new file mode 100644
index 0000000..4a92ba8
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt
@@ -0,0 +1,14 @@
+package net.shadowfacts.phycon.network.packet
+
+import net.shadowfacts.phycon.api.util.IPAddress
+
+/**
+ * @author shadowfacts
+ */
+class RemoteActivationPacket(val mode: Mode, source: IPAddress, destination: IPAddress): BasePacket(source, destination) {
+ enum class Mode {
+ SINGLE,
+ ENABLE,
+ DISABLE
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt
new file mode 100644
index 0000000..3abd003
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt
@@ -0,0 +1,49 @@
+package net.shadowfacts.phycon.networking
+
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
+import net.fabricmc.fabric.api.networking.v1.PacketSender
+import net.minecraft.network.Packet
+import net.minecraft.network.PacketByteBuf
+import net.minecraft.server.MinecraftServer
+import net.minecraft.server.network.ServerPlayNetworkHandler
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.util.Identifier
+import net.minecraft.util.registry.Registry
+import net.minecraft.util.registry.RegistryKey
+import net.shadowfacts.phycon.PhysicalConnectivity
+import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.component.ActivationController
+import net.shadowfacts.phycon.util.ActivationMode
+
+/**
+ * @author shadowfacts
+ */
+object C2SConfigureActivationMode: ServerReceiver {
+ override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_activation_mode")
+
+ operator fun invoke(be: T): Packet<*> where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice {
+ val buf = PacketByteBufs.create()
+
+ buf.writeIdentifier(be.world!!.registryKey.value)
+ buf.writeBlockPos(be.pos)
+ buf.writeString(be.controller.activationMode.name)
+
+ return createPacket(buf)
+ }
+
+ override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
+ val dimID = buf.readIdentifier()
+ val pos = buf.readBlockPos()
+ val mode = ActivationMode.valueOf(buf.readString())
+
+ server.execute {
+ // todo: check the player is close enough
+ val key = RegistryKey.of(Registry.DIMENSION, dimID)
+ val world = server.getWorld(key) ?: return@execute
+ val device = world.getBlockEntity(pos) ?: return@execute
+ if (device !is ActivationController.ActivatableDevice) return@execute
+ device.controller.activationMode = mode
+ device.markDirty()
+ }
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt
new file mode 100644
index 0000000..f51ec0c
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt
@@ -0,0 +1,59 @@
+package net.shadowfacts.phycon.networking
+
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
+import net.fabricmc.fabric.api.networking.v1.PacketSender
+import net.minecraft.network.Packet
+import net.minecraft.network.PacketByteBuf
+import net.minecraft.server.MinecraftServer
+import net.minecraft.server.network.ServerPlayNetworkHandler
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.util.Identifier
+import net.minecraft.util.registry.Registry
+import net.minecraft.util.registry.RegistryKey
+import net.shadowfacts.phycon.PhysicalConnectivity
+import net.shadowfacts.phycon.api.util.IPAddress
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
+import net.shadowfacts.phycon.util.RedstoneMode
+
+/**
+ * @author shadowfacts
+ */
+object C2SConfigureRedstoneController: ServerReceiver {
+ // todo: it would be nice if there wasn't so much duplication with C2SConfigureActivationMode
+
+ override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_redstone_controller")
+
+ operator fun invoke(be: RedstoneControllerBlockEntity): Packet<*> {
+ val buf = PacketByteBufs.create()
+
+ buf.writeIdentifier(be.world!!.registryKey.value)
+ buf.writeBlockPos(be.pos)
+ buf.writeString(be.redstoneMode.name)
+ be.managedDevices.forEach {
+ buf.writeInt(it?.address ?: 0)
+ }
+
+ return createPacket(buf)
+ }
+
+ override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
+ val dimID = buf.readIdentifier()
+ val pos = buf.readBlockPos()
+ val mode = RedstoneMode.valueOf(buf.readString())
+ val managedDevices = Array(5) { null }
+ (0..4).map {
+ val v = buf.readInt()
+ managedDevices[it] = if (v == 0) null else IPAddress(v)
+ }
+
+ server.execute {
+ // todo: check if the player is close enough
+ val key = RegistryKey.of(Registry.DIMENSION, dimID)
+ val world = server.getWorld(key) ?: return@execute
+ val device = world.getBlockEntity(pos) as? RedstoneControllerBlockEntity ?: return@execute
+ device.redstoneMode = mode
+ device.managedDevices = managedDevices
+ device.markDirty()
+ }
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt
new file mode 100644
index 0000000..eed71b9
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt
@@ -0,0 +1,50 @@
+package net.shadowfacts.phycon.networking
+
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
+import net.fabricmc.fabric.api.networking.v1.PacketSender
+import net.minecraft.network.Packet
+import net.minecraft.network.PacketByteBuf
+import net.minecraft.server.MinecraftServer
+import net.minecraft.server.network.ServerPlayNetworkHandler
+import net.minecraft.server.network.ServerPlayerEntity
+import net.minecraft.util.Identifier
+import net.shadowfacts.phycon.PhysicalConnectivity
+import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
+import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
+import net.shadowfacts.phycon.util.SortMode
+
+/**
+ * @author shadowfacts
+ */
+object C2STerminalUpdateDisplayedItems: ServerReceiver {
+
+ override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed")
+
+ operator fun invoke(terminal: TerminalBlockEntity, query: String, sortMode: SortMode): Packet<*> {
+ val buf = PacketByteBufs.create()
+
+ buf.writeIdentifier(terminal.world!!.registryKey.value)
+ buf.writeBlockPos(terminal.pos)
+
+ buf.writeString(query)
+ buf.writeVarInt(sortMode.ordinal)
+
+ return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
+ }
+
+ override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
+ val dimID = buf.readIdentifier()
+ val pos = buf.readBlockPos()
+ val query = buf.readString()
+ val sortMode = SortMode.values()[buf.readVarInt()]
+
+ server.execute {
+ if (player.world.registryKey.value != dimID) return@execute
+ val screenHandler = player.currentScreenHandler
+ if (screenHandler !is TerminalScreenHandler) return@execute
+ if (screenHandler.terminal.pos != pos) return@execute
+ screenHandler.sendUpdatedItemsToClient(player, query, sortMode)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt
new file mode 100644
index 0000000..9fa0cf6
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt
@@ -0,0 +1,11 @@
+package net.shadowfacts.phycon.networking
+
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
+import net.minecraft.util.Identifier
+
+/**
+ * @author shadowfacts
+ */
+interface ClientReceiver: ClientPlayNetworking.PlayChannelHandler {
+ val CHANNEL: Identifier
+}
\ No newline at end of file
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt
new file mode 100644
index 0000000..db0efe9
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt
@@ -0,0 +1,57 @@
+package net.shadowfacts.phycon.networking
+
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
+import net.fabricmc.fabric.api.networking.v1.PacketSender
+import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
+import net.minecraft.client.MinecraftClient
+import net.minecraft.client.network.ClientPlayNetworkHandler
+import net.minecraft.network.Packet
+import net.minecraft.network.PacketByteBuf
+import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
+import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
+import net.shadowfacts.phycon.util.SortMode
+
+/**
+ * @author shadowfacts
+ */
+object S2CTerminalUpdateDisplayedItems: ClientReceiver {
+ override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
+
+ operator fun invoke(terminal: TerminalBlockEntity, entries: List, query: String, sortMode: SortMode): Packet<*> {
+ val buf = PacketByteBufs.create()
+
+ buf.writeIdentifier(terminal.world!!.registryKey.value)
+ buf.writeBlockPos(terminal.pos)
+
+ buf.writeVarInt(entries.size)
+ for (e in entries) {
+ buf.writeItemStack(e.stack)
+ buf.writeVarInt(e.amount)
+ }
+
+ buf.writeString(query)
+ buf.writeVarInt(sortMode.ordinal)
+
+ return ServerPlayNetworking.createS2CPacket(CHANNEL, buf)
+ }
+
+ override fun receive(client: MinecraftClient, handler: ClientPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
+ val dimID = buf.readIdentifier()
+ val pos = buf.readBlockPos()
+ val entryCount = buf.readVarInt()
+ val entries = ArrayList(entryCount)
+ for (i in 0 until entryCount) {
+ entries.add(TerminalScreenHandler.Entry(buf.readItemStack(), buf.readVarInt()))
+ }
+ val query = buf.readString()
+ val sortMode = SortMode.values()[buf.readVarInt()]
+
+ client.execute {
+ if (client.player!!.world.registryKey.value != dimID) return@execute
+ val screenHandler = client.player!!.currentScreenHandler
+ if (screenHandler !is TerminalScreenHandler) return@execute
+ if (screenHandler.terminal.pos != pos) return@execute
+ screenHandler.receivedUpdatedItemsFromServer(entries, query, sortMode)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt
index 0ea41e9..f940b49 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt
@@ -1,6 +1,9 @@
package net.shadowfacts.phycon.networking
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
+import net.minecraft.network.Packet
+import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier
/**
@@ -8,4 +11,8 @@ import net.minecraft.util.Identifier
*/
interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier
-}
\ No newline at end of file
+
+ fun createPacket(buf: PacketByteBuf): Packet<*> {
+ return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
+ }
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt
new file mode 100644
index 0000000..b1ffa81
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt
@@ -0,0 +1,64 @@
+package net.shadowfacts.phycon.screen
+
+import com.mojang.blaze3d.systems.RenderSystem
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.gui.widget.ButtonWidget
+import net.minecraft.client.util.math.MatrixStack
+import net.shadowfacts.phycon.network.DeviceBlockEntity
+import net.shadowfacts.phycon.network.component.ActivationController
+import net.shadowfacts.phycon.networking.C2SConfigureActivationMode
+import net.shadowfacts.phycon.util.ActivationMode
+import net.shadowfacts.phycon.util.next
+import org.lwjgl.glfw.GLFW
+
+/**
+ * @author shadowfacts
+ */
+class ActivatableDeviceConsoleScreen(
+ val device: T
+): Screen(device.cachedState.block.name) where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice {
+
+ private val backgroundWidth = 252
+ private val backgroundHeight = 222
+
+ override fun init() {
+ super.init()
+
+ buttons.clear()
+
+ val minX = (width - backgroundWidth) / 2
+ val minY = (height - backgroundHeight) / 2
+
+ val mode = EnumButton(device.controller::activationMode, minX + 5, minY + 25, 55, 20) {
+ client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device))
+ }
+ addButton(mode)
+ }
+
+ override fun isPauseScreen() = false
+
+ override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
+ if (key == GLFW.GLFW_KEY_E) {
+ onClose()
+ return true
+ }
+ return super.keyPressed(key, j, k)
+ }
+
+ override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
+ renderBackground(matrixStack)
+
+ val minX = (width - backgroundWidth) / 2
+ val minY = (height - backgroundHeight) / 2
+
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
+ client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
+ drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
+
+ super.render(matrixStack, mouseX, mouseY, delta)
+
+ textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
+ textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
+ }
+
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt
index 37f9751..2336157 100644
--- a/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt
+++ b/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt
@@ -3,6 +3,8 @@ package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.TranslatableText
+import net.minecraft.util.Identifier
+import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlockEntity
import org.lwjgl.glfw.GLFW
@@ -11,18 +13,18 @@ import org.lwjgl.glfw.GLFW
*/
class DeviceConsoleScreen(
val device: DeviceBlockEntity,
-): Screen(TranslatableText("item.phycon.onsole")) {
+): Screen(TranslatableText("item.phycon.console")) {
- override fun init() {
- super.init()
+ companion object {
+ val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/console.png")
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
- onClose();
- return true;
+ onClose()
+ return true
}
return super.keyPressed(key, j, k)
}
@@ -35,4 +37,4 @@ class DeviceConsoleScreen(
drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff)
drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff)
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt
new file mode 100644
index 0000000..79e2608
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt
@@ -0,0 +1,49 @@
+package net.shadowfacts.phycon.screen
+
+import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
+import net.minecraft.text.Text
+import net.shadowfacts.phycon.util.FriendlyNameable
+import net.shadowfacts.phycon.util.RotatableEnum
+import net.shadowfacts.phycon.util.next
+import net.shadowfacts.phycon.util.prev
+import kotlin.reflect.KMutableProperty
+
+/**
+ * @author shadowfacts
+ */
+class EnumButton(
+ val prop: KMutableProperty,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ val onChange: () -> Unit
+): AbstractPressableButtonWidget(
+ x,
+ y,
+ width,
+ height,
+ prop.getter.call().friendlyName
+) where E: Enum, E: RotatableEnum, E: FriendlyNameable {
+
+ private var currentButton: Int? = null
+
+ override fun mouseClicked(d: Double, e: Double, button: Int): Boolean {
+ currentButton = button
+ val res = super.mouseClicked(d, e, button)
+ currentButton = null
+ return res
+ }
+
+ override fun isValidClickButton(i: Int): Boolean {
+ return i == 0 || i == 1
+ }
+
+ override fun onPress() {
+ val newVal = if ((currentButton ?: 0) == 0) prop.getter.call().next else prop.getter.call().prev
+ prop.setter.call(newVal)
+ message = newVal.friendlyName
+ onChange()
+ }
+
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt
new file mode 100644
index 0000000..9f30544
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt
@@ -0,0 +1,96 @@
+package net.shadowfacts.phycon.screen
+
+import com.mojang.blaze3d.systems.RenderSystem
+import net.minecraft.client.gui.screen.Screen
+import net.minecraft.client.gui.widget.ButtonWidget
+import net.minecraft.client.gui.widget.TextFieldWidget
+import net.minecraft.client.util.math.MatrixStack
+import net.minecraft.text.LiteralText
+import net.shadowfacts.phycon.api.util.IPAddress
+import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
+import net.shadowfacts.phycon.networking.C2SConfigureRedstoneController
+import net.shadowfacts.phycon.util.next
+import org.lwjgl.glfw.GLFW
+
+/**
+ * @author shadowfacts
+ */
+class RedstoneControllerConsoleScreen(
+ val device: RedstoneControllerBlockEntity
+): Screen(device.cachedState.block.name) {
+
+ private val backgroundWidth = 252
+ private val backgroundHeight = 222
+
+ private val ipAddressTextFields = mutableListOf()
+
+ override fun init() {
+ super.init()
+
+ buttons.clear()
+ ipAddressTextFields.clear()
+
+ val minX = (width - backgroundWidth) / 2
+ val minY = (height - backgroundHeight) / 2
+
+ val mode = EnumButton(device::redstoneMode, minX + 5, minY + 25, 75, 20) {
+ client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
+ }
+ addButton(mode)
+
+ for (i in 0 until 5) {
+ // todo: field name
+ val field = TextFieldWidget(textRenderer, minX + 5, minY + 50 + 22 * i, backgroundWidth / 2, 20, LiteralText(""))
+ field.setMaxLength(15)
+ field.setHasBorder(true)
+ field.isVisible = true
+ field.setEditableColor(0xffffff)
+ field.text = device.managedDevices[i]?.toString()
+ field.setChangedListener { newVal ->
+ device.managedDevices[i] = IPAddress.parse(newVal)
+ client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
+ }
+ addChild(field)
+ ipAddressTextFields.add(field)
+ }
+ }
+
+ override fun isPauseScreen() = false
+
+ override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
+ if (key == GLFW.GLFW_KEY_E) {
+ onClose()
+ return true
+ }
+ return super.keyPressed(key, j, k)
+ }
+
+ override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
+ val clickedField = ipAddressTextFields.find { it.x <= mouseX && it.x + it.width >= mouseX && it.y <= mouseY && it.y + it.height >= mouseY }
+ if (clickedField != null) {
+ ipAddressTextFields.forEach {
+ if (it !== clickedField) it.setSelected(false)
+ }
+ }
+ return super.mouseClicked(mouseX, mouseY, button)
+ }
+
+ override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
+ renderBackground(matrixStack)
+
+ val minX = (width - backgroundWidth) / 2
+ val minY = (height - backgroundHeight) / 2
+
+ RenderSystem.color4f(1f, 1f, 1f, 1f)
+ client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
+ drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
+
+ super.render(matrixStack, mouseX, mouseY, delta)
+
+ ipAddressTextFields.forEach { it.render(matrixStack, mouseX, mouseY, delta) }
+
+ textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
+ textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
+ }
+
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt
new file mode 100644
index 0000000..2db1810
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt
@@ -0,0 +1,15 @@
+package net.shadowfacts.phycon.util
+
+import net.minecraft.text.Text
+import net.minecraft.text.TranslatableText
+
+/**
+ * @author shadowfacts
+ */
+enum class ActivationMode: RotatableEnum, FriendlyNameable {
+ AUTOMATIC,
+ MANAGED;
+
+ override val friendlyName: Text
+ get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}")
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt b/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt
new file mode 100644
index 0000000..978d21a
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt
@@ -0,0 +1,10 @@
+package net.shadowfacts.phycon.util
+
+import net.minecraft.text.Text
+
+/**
+ * @author shadowfacts
+ */
+interface FriendlyNameable {
+ val friendlyName: Text
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt
new file mode 100644
index 0000000..0fe895a
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt
@@ -0,0 +1,30 @@
+package net.shadowfacts.phycon.util
+
+import net.minecraft.text.Text
+import net.minecraft.text.TranslatableText
+
+/**
+ * @author shadowfacts
+ */
+enum class RedstoneMode: RotatableEnum, FriendlyNameable {
+ HIGH,
+ LOW,
+ TOGGLE,
+ RISING_EDGE,
+ FALLING_EDGE;
+
+ val isDiscrete: Boolean
+ get() = when (this) {
+ TOGGLE, RISING_EDGE, FALLING_EDGE -> true
+ else -> false
+ }
+
+ val isContinuous: Boolean
+ get() = when (this) {
+ HIGH, LOW -> true
+ else -> false
+ }
+
+ override val friendlyName: Text
+ get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}")
+}
diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt
new file mode 100644
index 0000000..75d40a3
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt
@@ -0,0 +1,13 @@
+package net.shadowfacts.phycon.util
+
+/**
+ * @author shadowfacts
+ */
+interface RotatableEnum {
+}
+
+val E.prev: E where E: Enum, E: RotatableEnum
+ get() = javaClass.enumConstants[(ordinal - 1 + javaClass.enumConstants.size) % javaClass.enumConstants.size]
+
+val E.next: E where E: Enum, E: RotatableEnum
+ get() = javaClass.enumConstants[(ordinal + 1) % javaClass.enumConstants.size]
diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt
new file mode 100644
index 0000000..fbcde94
--- /dev/null
+++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt
@@ -0,0 +1,21 @@
+package net.shadowfacts.phycon.util
+
+import net.minecraft.text.LiteralText
+import net.minecraft.text.Text
+
+/**
+ * @author shadowfacts
+ */
+enum class SortMode: RotatableEnum {
+ COUNT_HIGH_FIRST,
+ COUNT_LOW_FIRST,
+ ALPHABETICAL;
+
+ val tooltip: Text
+ get() = when (this) {
+ COUNT_HIGH_FIRST -> LiteralText("Count, highest first")
+ COUNT_LOW_FIRST -> LiteralText("Count, lowest first")
+ ALPHABETICAL -> LiteralText("Name")
+ }
+
+}
diff --git a/src/main/resources/assets/phycon/blockstates/network_interface.json b/src/main/resources/assets/phycon/blockstates/network_interface.json
index b1166ed..6d851e4 100644
--- a/src/main/resources/assets/phycon/blockstates/network_interface.json
+++ b/src/main/resources/assets/phycon/blockstates/network_interface.json
@@ -140,7 +140,6 @@
},
{
-
"when": {"cable_connection": "down", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
},
diff --git a/src/main/resources/assets/phycon/blockstates/redstone_controller.json b/src/main/resources/assets/phycon/blockstates/redstone_controller.json
new file mode 100644
index 0000000..196adf8
--- /dev/null
+++ b/src/main/resources/assets/phycon/blockstates/redstone_controller.json
@@ -0,0 +1,183 @@
+{
+ "multipart": [
+ {
+ "apply": { "model": "phycon:block/cable_center" }
+ },
+ {
+ "when": { "facing": "down", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off" }
+ },
+ {
+ "when": { "facing": "up", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 180 }
+ },
+ {
+ "when": { "facing": "north", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 270 }
+ },
+ {
+ "when": { "facing": "south", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90 }
+ },
+ {
+ "when": { "facing": "west", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 90 }
+ },
+ {
+ "when": { "facing": "east", "lit": "false" },
+ "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 270 }
+ },
+ {
+ "when": { "facing": "down", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on" }
+ },
+ {
+ "when": { "facing": "up", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on", "x": 180 }
+ },
+ {
+ "when": { "facing": "north", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on", "x": 270 }
+ },
+ {
+ "when": { "facing": "south", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90 }
+ },
+ {
+ "when": { "facing": "west", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 90 }
+ },
+ {
+ "when": { "facing": "east", "lit": "true" },
+ "apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 270 }
+ },
+
+ {
+ "when": { "cable_connection": "up", "facing": "down" },
+ "apply": { "model": "phycon:block/interface_cable_straight" }
+ },
+ {
+ "when": { "cable_connection": "down", "facing": "up" },
+ "apply": { "model": "phycon:block/interface_cable_straight", "x": 180 }
+ },
+ {
+ "when": { "cable_connection": "north", "facing": "south" },
+ "apply": { "model": "phycon:block/interface_cable_straight", "x": 90 }
+ },
+ {
+ "when": { "cable_connection": "south", "facing": "north" },
+ "apply": { "model": "phycon:block/interface_cable_straight", "x": 270 }
+ },
+ {
+ "when": { "cable_connection": "west", "facing": "east" },
+ "apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 270 }
+ },
+ {
+ "when": { "cable_connection": "east", "facing": "west" },
+ "apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 90 }
+ },
+
+ {
+ "when": {"cable_connection": "north", "facing": "down"},
+ "apply": { "model": "phycon:block/interface_cable_corner" }
+ },
+ {
+ "when": {"cable_connection": "east", "facing": "down"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "y": 90 }
+ },
+ {
+ "when": {"cable_connection": "south", "facing": "down"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "y": 180 }
+ },
+ {
+ "when": {"cable_connection": "west", "facing": "down"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "y": 270 }
+ },
+
+ {
+ "when": {"cable_connection": "north", "facing": "up"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 180 }
+ },
+ {
+ "when": {"cable_connection": "east", "facing": "up"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 270 }
+ },
+ {
+ "when": {"cable_connection": "south", "facing": "up"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 180 }
+ },
+ {
+ "when": {"cable_connection": "west", "facing": "up"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 90 }
+ },
+
+ {
+ "when": {"cable_connection": "down", "facing": "north"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 180 }
+ },
+ {
+ "when": {"cable_connection": "up", "facing": "north"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 270 }
+ },
+ {
+ "when": {"cable_connection": "west", "facing": "north"},
+ "apply": { "model": "phycon:block/interface_cable_corner_2" }
+ },
+ {
+ "when": {"cable_connection": "east", "facing": "north"},
+ "apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 180 }
+ },
+
+ {
+ "when": {"cable_connection": "down", "facing": "south"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 90 }
+ },
+ {
+ "when": {"cable_connection": "up", "facing": "south"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 180 }
+ },
+ {
+ "when": {"cable_connection": "west", "facing": "south"},
+ "apply": { "model": "phycon:block/interface_cable_corner_3" }
+ },
+ {
+ "when": {"cable_connection": "east", "facing": "south"},
+ "apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 180 }
+ },
+
+ {
+
+ "when": {"cable_connection": "down", "facing": "west"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 90 }
+ },
+ {
+ "when": {"cable_connection": "up", "facing": "west"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 270 }
+ },
+ {
+ "when": {"cable_connection": "north", "facing": "west"},
+ "apply": { "model": "phycon:block/interface_cable_corner_3", "y": 90 }
+ },
+ {
+ "when": {"cable_connection": "south", "facing": "west"},
+ "apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 270 }
+ },
+
+ {
+ "when": {"cable_connection": "down", "facing": "east"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
+ },
+ {
+ "when": {"cable_connection": "up", "facing": "east"},
+ "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 90 }
+ },
+ {
+ "when": {"cable_connection": "north", "facing": "east"},
+ "apply": { "model": "phycon:block/interface_cable_corner_2", "y": 90 }
+ },
+ {
+ "when": {"cable_connection": "south", "facing": "east"},
+ "apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 270 }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json
index 0ae03a5..ea4ce28 100644
--- a/src/main/resources/assets/phycon/lang/en_us.json
+++ b/src/main/resources/assets/phycon/lang/en_us.json
@@ -5,7 +5,17 @@
"block.phycon.cable": "Cable",
"block.phycon.extractor": "Inventory Extractor",
"block.phycon.miner": "Block Miner",
+ "block.phycon.redstone_controller": "Redstone Controller",
"item.phycon.screwdriver": "Screwdriver",
- "item.phycon.console": "Console"
-}
\ No newline at end of file
+ "item.phycon.console": "Console",
+
+ "gui.phycon.terminal_buffer": "Buffer",
+ "gui.phycon.redstone_mode.high": "High",
+ "gui.phycon.redstone_mode.low": "Low",
+ "gui.phycon.redstone_mode.toggle": "Toggle",
+ "gui.phycon.redstone_mode.rising_edge": "Rising Edge",
+ "gui.phycon.redstone_mode.falling_edge": "Falling Edge",
+ "gui.phycon.activation_mode.automatic": "Automatic",
+ "gui.phycon.activation_mode.managed": "Managed"
+}
diff --git a/src/main/resources/assets/phycon/models/block/cable_center.json b/src/main/resources/assets/phycon/models/block/cable_center.json
index 05ecfd8..cfda031 100644
--- a/src/main/resources/assets/phycon/models/block/cable_center.json
+++ b/src/main/resources/assets/phycon/models/block/cable_center.json
@@ -1,6 +1,7 @@
{
"textures": {
- "center": "phycon:block/cable_cap_end"
+ "center": "phycon:block/cable_cap_end",
+ "particle": "#center"
},
"elements": [
{
@@ -39,4 +40,4 @@
}
}
]
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json b/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json
new file mode 100644
index 0000000..2c26a3b
--- /dev/null
+++ b/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json
@@ -0,0 +1,33 @@
+{
+ "parent": "block/block",
+ "textures": {
+ "cable": "phycon:block/cable_straight",
+ "front": "phycon:block/redstone_controller_front_off",
+ "back": "phycon:block/redstone_controller_back",
+ "side": "phycon:block/redstone_controller_back"
+ },
+ "elements": [
+ {
+ "from": [0, 0, 0],
+ "to": [16, 3, 16],
+ "faces": {
+ "down": { "texture": "#front" },
+ "up": { "texture": "#back" },
+ "north": { "texture": "#side" },
+ "south": { "texture": "#side" },
+ "west": { "texture": "#side" },
+ "east": { "texture": "#side" }
+ }
+ },
+ {
+ "from": [6, 3, 6],
+ "to": [10, 6, 10],
+ "faces": {
+ "north": { "texture": "#cable" },
+ "south": { "texture": "#cable" },
+ "west": { "texture": "#cable" },
+ "east": { "texture": "#cable" }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json b/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json
new file mode 100644
index 0000000..65b44a4
--- /dev/null
+++ b/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json
@@ -0,0 +1,33 @@
+{
+ "parent": "block/block",
+ "textures": {
+ "cable": "phycon:block/cable_straight",
+ "front": "phycon:block/redstone_controller_front_on",
+ "back": "phycon:block/redstone_controller_back",
+ "side": "phycon:block/redstone_controller_back"
+ },
+ "elements": [
+ {
+ "from": [0, 0, 0],
+ "to": [16, 3, 16],
+ "faces": {
+ "down": { "texture": "#front" },
+ "up": { "texture": "#back" },
+ "north": { "texture": "#side" },
+ "south": { "texture": "#side" },
+ "west": { "texture": "#side" },
+ "east": { "texture": "#side" }
+ }
+ },
+ {
+ "from": [6, 3, 6],
+ "to": [10, 6, 10],
+ "faces": {
+ "north": { "texture": "#cable" },
+ "south": { "texture": "#cable" },
+ "west": { "texture": "#cable" },
+ "east": { "texture": "#cable" }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/phycon/models/item/redstone_controller.json b/src/main/resources/assets/phycon/models/item/redstone_controller.json
new file mode 100644
index 0000000..e4bd117
--- /dev/null
+++ b/src/main/resources/assets/phycon/models/item/redstone_controller.json
@@ -0,0 +1,51 @@
+{
+ "parent": "block/block",
+ "textures": {
+ "front": "phycon:block/redstone_controller_front_off",
+ "back": "phycon:block/redstone_controller_back",
+ "side": "phycon:block/redstone_controller_back",
+ "cable_end": "phycon:block/cable_cap_end",
+ "cable_side": "phycon:block/cable_straight"
+ },
+ "elements": [
+ {
+ "_comment": "cable center",
+ "from": [6, 6, 3],
+ "to": [10, 10, 16],
+ "faces": {
+ "down": { "texture": "#cable_side" },
+ "up": { "texture": "#cable_side" },
+ "south": { "texture": "#cable_end" },
+ "west": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] },
+ "east": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] }
+ }
+ },
+ {
+ "_comment": "redstone controller side",
+ "from": [0, 0, 0],
+ "to": [16, 16, 3],
+ "faces": {
+ "down": { "texture": "#side" },
+ "up": { "texture": "#side" },
+ "north": { "texture": "#front" },
+ "south": { "texture": "#back" },
+ "west": { "texture": "#side" },
+ "east": { "texture": "#side" }
+ }
+ }
+ ],
+ "_test": [
+ {
+ "_comment": "cable center",
+ "from": [6, 6, 3],
+ "to": [10, 10, 16],
+ "faces": {
+ "down": { "texture": "#cable_side", "rotation": 270, "uv": [4, 6, 10, 10] },
+ "up": { "texture": "#cable_side", "rotation": 90, "uv": [4, 6, 10, 10] },
+ "south": { "texture": "#cable_end" },
+ "west": { "texture": "#cable_side", "uv": [4, 6, 10, 10] },
+ "east": { "texture": "#cable_side", "rotation": 180, "uv": [4, 6, 10, 10] }
+ }
+ }
+ ]
+}
diff --git a/src/main/resources/assets/phycon/textures/block/redstone_controller_back.png b/src/main/resources/assets/phycon/textures/block/redstone_controller_back.png
new file mode 100644
index 0000000..14643d1
Binary files /dev/null and b/src/main/resources/assets/phycon/textures/block/redstone_controller_back.png differ
diff --git a/src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png b/src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png
new file mode 100644
index 0000000..7099eb9
Binary files /dev/null and b/src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png differ
diff --git a/src/main/resources/assets/phycon/textures/block/redstone_controller_front_on.png b/src/main/resources/assets/phycon/textures/block/redstone_controller_front_on.png
new file mode 100644
index 0000000..227353c
Binary files /dev/null and b/src/main/resources/assets/phycon/textures/block/redstone_controller_front_on.png differ
diff --git a/src/main/resources/assets/phycon/textures/gui/console.png b/src/main/resources/assets/phycon/textures/gui/console.png
new file mode 100644
index 0000000..b92218c
Binary files /dev/null and b/src/main/resources/assets/phycon/textures/gui/console.png differ
diff --git a/src/main/resources/assets/phycon/textures/gui/terminal.png b/src/main/resources/assets/phycon/textures/gui/terminal.png
index 7fd4d13..c2d10a7 100644
Binary files a/src/main/resources/assets/phycon/textures/gui/terminal.png and b/src/main/resources/assets/phycon/textures/gui/terminal.png differ
diff --git a/src/main/resources/assets/phycon/textures/gui/terminal_amount.png b/src/main/resources/assets/phycon/textures/gui/terminal_amount.png
new file mode 100644
index 0000000..3999831
Binary files /dev/null and b/src/main/resources/assets/phycon/textures/gui/terminal_amount.png differ