Add Redstone Controller

This commit is contained in:
Shadowfacts 2021-02-23 22:05:05 -05:00
parent 700817919a
commit d3a0f279da
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
27 changed files with 866 additions and 40 deletions

View File

@ -1,8 +1,11 @@
package net.shadowfacts.phycon.api.util; package net.shadowfacts.phycon.api.util;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Random; import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* @author shadowfacts * @author shadowfacts
@ -25,6 +28,20 @@ public final class IPAddress {
return random(ipAddressRandom); 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 static final IPAddress BROADCAST = new IPAddress(0xff_ff_ff_ff);
public final int address; public final int address;

View File

@ -6,9 +6,7 @@ import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyItems
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem import net.shadowfacts.phycon.networking.*
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
import net.shadowfacts.phycon.networking.ServerReceiver
/** /**
* @author shadowfacts * @author shadowfacts
@ -25,6 +23,8 @@ object PhysicalConnectivity: ModInitializer {
registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalRequestItem)
registerGlobalReceiver(C2STerminalUpdateDisplayedItems) registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
registerGlobalReceiver(C2SConfigureActivationMode)
registerGlobalReceiver(C2SConfigureRedstoneController)
} }
private fun registerGlobalReceiver(receiver: ServerReceiver) { private fun registerGlobalReceiver(receiver: ServerReceiver) {

View File

@ -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.netinterface.InterfaceBlockEntity
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity 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.TerminalBlock
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.test.DestBlock import net.shadowfacts.phycon.network.block.test.DestBlock
@ -30,6 +32,7 @@ object PhyBlockEntities {
val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH) val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH)
val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR) val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR)
val MINER = create(::MinerBlockEntity, PhyBlocks.MINER) val MINER = create(::MinerBlockEntity, PhyBlocks.MINER)
val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER)
val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE) val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE)
val DEST = create(::DestBlockEntity, PhyBlocks.DEST) val DEST = create(::DestBlockEntity, PhyBlocks.DEST)
@ -44,6 +47,7 @@ object PhyBlockEntities {
register(SwitchBlock.ID, SWITCH) register(SwitchBlock.ID, SWITCH)
register(ExtractorBlock.ID, EXTRACTOR) register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER) register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE) register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST) register(DestBlock.ID, DEST)

View File

@ -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.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock 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.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock import net.shadowfacts.phycon.network.block.test.SourceBlock
@ -23,6 +24,7 @@ object PhyBlocks {
val CABLE = CableBlock() val CABLE = CableBlock()
val EXTRACTOR = ExtractorBlock() val EXTRACTOR = ExtractorBlock()
val MINER = MinerBlock() val MINER = MinerBlock()
val REDSTONE_CONTROLLER = RedstoneControllerBlock()
val SOURCE = SourceBlock() val SOURCE = SourceBlock()
val DEST = DestBlock() val DEST = DestBlock()
@ -34,6 +36,7 @@ object PhyBlocks {
register(CableBlock.ID, CABLE) register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR) register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER) register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE) register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST) register(DestBlock.ID, DEST)

View File

@ -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.miner.MinerBlock
import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock
import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock 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.terminal.TerminalBlock
import net.shadowfacts.phycon.network.block.test.DestBlock import net.shadowfacts.phycon.network.block.test.DestBlock
import net.shadowfacts.phycon.network.block.test.SourceBlock import net.shadowfacts.phycon.network.block.test.SourceBlock
@ -26,6 +27,7 @@ object PhyItems {
val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings()) val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings())
val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings()) val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings())
val MINER = BlockItem(PhyBlocks.MINER, 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 SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings())
val DEST = BlockItem(PhyBlocks.DEST , Item.Settings()) val DEST = BlockItem(PhyBlocks.DEST , Item.Settings())
@ -40,6 +42,7 @@ object PhyItems {
register(CableBlock.ID, CABLE) register(CableBlock.ID, CABLE)
register(ExtractorBlock.ID, EXTRACTOR) register(ExtractorBlock.ID, EXTRACTOR)
register(MinerBlock.ID, MINER) register(MinerBlock.ID, MINER)
register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER)
register(SourceBlock.ID, SOURCE) register(SourceBlock.ID, SOURCE)
register(DestBlock.ID, DEST) register(DestBlock.ID, DEST)

View File

@ -8,7 +8,11 @@ import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlock import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.DeviceBlockEntity 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.DeviceConsoleScreen
import net.shadowfacts.phycon.screen.RedstoneControllerConsoleScreen
/** /**
* @author shadowfacts * @author shadowfacts
@ -36,7 +40,11 @@ class ConsoleItem: Item(Settings()) {
} }
private fun openScreen(be: DeviceBlockEntity) { 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) MinecraftClient.getInstance().openScreen(screen)
} }

View File

@ -18,8 +18,7 @@ import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.ARPQueryFrame import net.shadowfacts.phycon.network.frame.ARPQueryFrame
import net.shadowfacts.phycon.network.frame.ARPResponseFrame import net.shadowfacts.phycon.network.frame.ARPResponseFrame
import net.shadowfacts.phycon.network.frame.BasePacketFrame import net.shadowfacts.phycon.network.frame.BasePacketFrame
import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket import net.shadowfacts.phycon.network.packet.*
import java.lang.RuntimeException
import java.util.* import java.util.*
/** /**
@ -52,6 +51,15 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
abstract override fun handle(packet: Packet) 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) { override fun send(frame: EthernetFrame) {
findDestination()?.receive(frame) findDestination()?.receive(frame)
} }
@ -64,7 +72,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
is PacketFrame -> { is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) { if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
println("$this ($ipAddress) received packet: ${frame.packet}") println("$this ($ipAddress) received packet: ${frame.packet}")
handle(frame.packet) doHandlePacket(frame.packet)
} }
} }
} }

View File

@ -71,18 +71,23 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): Device
builder.add(CABLE_CONNECTION) builder.add(CABLE_CONNECTION)
} }
override fun getPlacementState(context: ItemPlacementContext): BlockState? { override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
val cableConnection = getCableConnectedSide(context.world, context.blockPos) ?: facing.opposite val cableConnection = getCableConnectedSide(context.world, context.blockPos, facing) ?: facing.opposite
return defaultState return defaultState
.with(FACING, facing) .with(FACING, facing)
.with(CABLE_CONNECTION, cableConnection) .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()) { for (side in Direction.values()) {
if (side == facing) {
continue
}
val offsetPos = pos.offset(side) 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 return side
} }
} }

View File

@ -1,28 +1,38 @@
package net.shadowfacts.phycon.network.block.extractor package net.shadowfacts.phycon.network.block.extractor
import alexiil.mc.lib.attributes.SearchOptions 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.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes 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.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlockEntities
import net.shadowfacts.phycon.network.DeviceBlockEntity 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.CapacityPacket
import net.shadowfacts.phycon.network.packet.CheckCapacityPacket import net.shadowfacts.phycon.network.packet.CheckCapacityPacket
import net.shadowfacts.phycon.network.packet.ItemStackPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
ActivationController.ActivatableDevice {
companion object {
val SLEEP_TIME = 40L
}
private val facing: Direction private val facing: Direction
get() = cachedState[ExtractorBlock.FACING] get() = cachedState[ExtractorBlock.FACING]
private var inventory: GroupedItemInv? = null private var inventory: GroupedItemInv? = null
private var shouldExtract = false private var shouldExtract = false
override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() { fun updateInventory() {
val offsetPos = pos.offset(facing) val offsetPos = pos.offset(facing)
@ -36,7 +46,14 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) {
} }
override fun handle(packet: Packet) { 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 -> getInventory()?.also { inv ->
shouldExtract = false shouldExtract = false
val extracted = inv.extract(packet.stack, packet.capacity) val extracted = inv.extract(packet.stack, packet.capacity)
@ -48,12 +65,36 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) {
override fun tick() { override fun tick() {
super.tick() super.tick()
if (!world!!.isClient && counter % 40 == 0L) { if (!world!!.isClient) {
getInventory()?.also { controller.tick()
val stack = it.storedStacks.firstOrNull() ?: return }
}
override fun activate(): Boolean {
val inventory = getInventory() ?: return false
val stack = inventory.storedStacks.firstOrNull() ?: return false
shouldExtract = true shouldExtract = true
sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) 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"))
} }
} }

View File

@ -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<RedstoneControllerBlockEntity>(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<Block, BlockState>) {
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()
}
}

View File

@ -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<IPAddress?>(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"))
}
}

View File

@ -20,6 +20,8 @@ import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.networking.C2STerminalRequestItem import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems
import net.shadowfacts.phycon.util.SortMode import net.shadowfacts.phycon.util.SortMode
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
import java.lang.NumberFormatException import java.lang.NumberFormatException
import kotlin.math.ceil import kotlin.math.ceil

View File

@ -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<T>(
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
}
}

View File

@ -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
}
}

View File

@ -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 <T> 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()
}
}
}

View File

@ -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<IPAddress?>(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()
}
}
}

View File

@ -1,6 +1,9 @@
package net.shadowfacts.phycon.networking package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
/** /**
@ -8,4 +11,8 @@ import net.minecraft.util.Identifier
*/ */
interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler { interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier val CHANNEL: Identifier
fun createPacket(buf: PacketByteBuf): Packet<*> {
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
} }

View File

@ -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<T>(
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")
}
}

View File

@ -3,6 +3,8 @@ package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.util.math.MatrixStack import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.TranslatableText import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.DeviceBlockEntity
import org.lwjgl.glfw.GLFW import org.lwjgl.glfw.GLFW
@ -11,18 +13,18 @@ import org.lwjgl.glfw.GLFW
*/ */
class DeviceConsoleScreen( class DeviceConsoleScreen(
val device: DeviceBlockEntity, val device: DeviceBlockEntity,
): Screen(TranslatableText("item.phycon.onsole")) { ): Screen(TranslatableText("item.phycon.console")) {
override fun init() { companion object {
super.init() val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/console.png")
} }
override fun isPauseScreen() = false override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean { override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) { if (key == GLFW.GLFW_KEY_E) {
onClose(); onClose()
return true; return true
} }
return super.keyPressed(key, j, k) return super.keyPressed(key, j, k)
} }

View File

@ -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<TextFieldWidget>()
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"
})
}

View File

@ -0,0 +1,9 @@
package net.shadowfacts.phycon.util
/**
* @author shadowfacts
*/
enum class ActivationMode: RotatableEnum {
AUTOMATIC,
MANAGED,
}

View File

@ -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
}
}

View File

@ -0,0 +1,13 @@
package net.shadowfacts.phycon.util
/**
* @author shadowfacts
*/
interface RotatableEnum {
}
val <E> E.prev: E where E: Enum<E>, E: RotatableEnum
get() = javaClass.enumConstants[(ordinal - 1 + javaClass.enumConstants.size) % javaClass.enumConstants.size]
val <E> E.next: E where E: Enum<E>, E: RotatableEnum
get() = javaClass.enumConstants[(ordinal + 1) % javaClass.enumConstants.size]

View File

@ -6,17 +6,11 @@ import net.minecraft.text.Text
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
enum class SortMode { enum class SortMode: RotatableEnum {
COUNT_HIGH_FIRST, COUNT_HIGH_FIRST,
COUNT_LOW_FIRST, COUNT_LOW_FIRST,
ALPHABETICAL; ALPHABETICAL;
val prev: SortMode
get() = values()[(ordinal - 1 + values().size) % values().size]
val next: SortMode
get() = values()[(ordinal + 1) % values().size]
val tooltip: Text val tooltip: Text
get() = when (this) { get() = when (this) {
COUNT_HIGH_FIRST -> LiteralText("Count, highest first") COUNT_HIGH_FIRST -> LiteralText("Count, highest first")

View File

@ -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 }
}
]
}

View File

@ -5,6 +5,7 @@
"block.phycon.cable": "Cable", "block.phycon.cable": "Cable",
"block.phycon.extractor": "Inventory Extractor", "block.phycon.extractor": "Inventory Extractor",
"block.phycon.miner": "Block Miner", "block.phycon.miner": "Block Miner",
"block.phycon.redstone_controller": "Redstone Controller",
"item.phycon.screwdriver": "Screwdriver", "item.phycon.screwdriver": "Screwdriver",
"item.phycon.console": "Console" "item.phycon.console": "Console"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB