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 19ba8c5..961dbee 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -6,9 +6,7 @@ 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.C2STerminalUpdateDisplayedItems -import net.shadowfacts.phycon.networking.ServerReceiver +import net.shadowfacts.phycon.networking.* /** * @author shadowfacts @@ -25,6 +23,8 @@ object PhysicalConnectivity: ModInitializer { registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalUpdateDisplayedItems) + registerGlobalReceiver(C2SConfigureActivationMode) + registerGlobalReceiver(C2SConfigureRedstoneController) } private fun registerGlobalReceiver(receiver: ServerReceiver) { 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 a6570af..e3657e4 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 /** * @author shadowfacts @@ -36,8 +40,12 @@ class ConsoleItem: Item(Settings()) { } private fun openScreen(be: DeviceBlockEntity) { - val screen = DeviceConsoleScreen(be) + 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..4ce619a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt @@ -18,8 +18,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,6 +51,15 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), abstract override fun handle(packet: Packet) + private fun doHandlePacket(packet: Packet) { + when (packet) { + is DeviceRemovedPacket -> { + arpTable.remove(packet.source) + } + } + handle(packet) + } + override fun send(frame: EthernetFrame) { findDestination()?.receive(frame) } @@ -64,7 +72,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), is PacketFrame -> { if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) { println("$this ($ipAddress) received packet: ${frame.packet}") - handle(frame.packet) + doHandlePacket(frame.packet) } } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt index e81c754..b787008 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt @@ -71,18 +71,23 @@ abstract class FaceDeviceBlock(settings: Settings): Device builder.add(CABLE_CONNECTION) } - override fun getPlacementState(context: ItemPlacementContext): BlockState? { - val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite - val cableConnection = getCableConnectedSide(context.world, context.blockPos) ?: facing.opposite + 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): Direction? { + protected fun getCableConnectedSide(world: World, pos: BlockPos, facing: Direction): Direction? { for (side in Direction.values()) { + if (side == facing) { + continue + } val offsetPos = pos.offset(side) - if (world.getBlockState(offsetPos) is NetworkComponentBlock) { + val state = world.getBlockState(offsetPos) + val block = state.block + if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) { return side } } @@ -99,4 +104,4 @@ abstract class FaceDeviceBlock(settings: Settings): Device override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape { return getShape(state[FACING], state[CABLE_CONNECTION]) } -} \ No newline at end of file +} 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..59fe56f 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,28 +1,38 @@ package net.shadowfacts.phycon.network.block.extractor import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.item.FixedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.ItemAttributes +import net.minecraft.block.BlockState +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.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 /** * @author shadowfacts */ -class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { +class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), + 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 + override val controller = ActivationController(SLEEP_TIME, this) fun updateInventory() { val offsetPos = pos.offset(facing) @@ -36,7 +46,14 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { } override fun handle(packet: Packet) { - if (packet is CapacityPacket && shouldExtract) { + when (packet) { + is CapacityPacket -> handleCapacity(packet) + is RemoteActivationPacket -> controller.handleRemoteActivation(packet) + } + } + + private fun handleCapacity(packet: CapacityPacket) { + if (shouldExtract && packet.capacity > 0) { getInventory()?.also { inv -> shouldExtract = false val extracted = inv.extract(packet.stack, packet.capacity) @@ -48,12 +65,36 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { 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 + val stack = inventory.storedStacks.firstOrNull() ?: return false + shouldExtract = true + sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) + return true + } + + 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")) + } +} 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..5a9cb6e --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt @@ -0,0 +1,83 @@ +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.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 = 2.0 + override val faceShapes = mapOf( + Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 2.0, 16.0), + Direction.UP to createCuboidShape(0.0, 14.0, 0.0, 16.0, 16.0, 16.0), + Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 2.0), + Direction.SOUTH to createCuboidShape(0.0, 0.0, 14.0, 16.0, 16.0, 16.0), + Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 2.0, 16.0, 16.0), + Direction.EAST to createCuboidShape(14.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])) + } + + // todo: does this need to be separate from getStateForNeighborUpdate? + override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) { + if (!world.isClient) { + val wasLit = state[LIT] + val isLit = isPowered(world, pos, state[FACING]) + if (wasLit != isLit) { + if (wasLit) { + world.blockTickScheduler.schedule(pos, this, 4) + } else { + toggleLit(state, world, pos) + } + } + } + } + + override fun scheduledTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) { + if (state[LIT] && !isPowered(world, pos, state[FACING])) { + 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/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index e245942..376e194 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 @@ -20,6 +20,8 @@ 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 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/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/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..609dc5e --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt @@ -0,0 +1,75 @@ +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.minecraft.text.LiteralText +import net.minecraft.text.Text +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 + + lateinit var mode: ButtonWidget + mode = ButtonWidget(minX + 5, minY + 25, 55, 20, device.controller.activationMode.friendlyName) { + device.controller.activationMode = device.controller.activationMode.next + mode.message = device.controller.activationMode.friendlyName + 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) + } + + private val ActivationMode.friendlyName: Text + get() = when (this) { + ActivationMode.AUTOMATIC -> LiteralText("Automatic") + ActivationMode.MANAGED -> LiteralText("Managed") + } + +} 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/RedstoneControllerConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt new file mode 100644 index 0000000..b327a80 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt @@ -0,0 +1,111 @@ +package net.shadowfacts.phycon.screen + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.font.TextRenderer +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.minecraft.text.Text +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.RedstoneMode +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 + + lateinit var mode: ButtonWidget + mode = ButtonWidget(minX + 5, minY + 25, 75, 20, device.redstoneMode.friendlyName) { + device.redstoneMode = device.redstoneMode.next + mode.message = device.redstoneMode.friendlyName + 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) + } + + private val RedstoneMode.friendlyName: Text + get() = LiteralText(when (this) { + RedstoneMode.HIGH -> "High" + RedstoneMode.LOW -> "Low" + RedstoneMode.TOGGLE -> "Toggle" + RedstoneMode.RISING_EDGE -> "Rising Edge" + RedstoneMode.FALLING_EDGE -> "Falling Edge" + }) + +} 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..28fa901 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt @@ -0,0 +1,9 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +enum class ActivationMode: RotatableEnum { + AUTOMATIC, + MANAGED, +} 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..cf2c7f3 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt @@ -0,0 +1,24 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +enum class RedstoneMode: RotatableEnum { + 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 + } +} 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 index 7b61025..fbcde94 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt @@ -6,17 +6,11 @@ import net.minecraft.text.Text /** * @author shadowfacts */ -enum class SortMode { +enum class SortMode: RotatableEnum { COUNT_HIGH_FIRST, COUNT_LOW_FIRST, ALPHABETICAL; - val prev: SortMode - get() = values()[(ordinal - 1 + values().size) % values().size] - - val next: SortMode - get() = values()[(ordinal + 1) % values().size] - val tooltip: Text get() = when (this) { COUNT_HIGH_FIRST -> LiteralText("Count, highest first") @@ -24,4 +18,4 @@ enum class SortMode { ALPHABETICAL -> LiteralText("Name") } -} \ No newline at end of file +} 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..32abdb2 --- /dev/null +++ b/src/main/resources/assets/phycon/blockstates/redstone_controller.json @@ -0,0 +1,159 @@ +{ + "multipart": [ + { + "apply": { "model": "phycon:block/cable_center" } + }, + { + "when": { "facing": "down", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side" } + }, + { + "when": { "facing": "up", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 180 } + }, + { + "when": { "facing": "north", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 270 } + }, + { + "when": { "facing": "south", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 90 } + }, + { + "when": { "facing": "west", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 90, "y": 90 } + }, + { + "when": { "facing": "east", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "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..d88e438 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -5,7 +5,8 @@ "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 +} 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