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;
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;

View File

@ -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) {

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.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)

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.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)

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.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)

View File

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

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.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)
}
}
}

View File

@ -71,18 +71,23 @@ abstract class FaceDeviceBlock<T: DeviceBlockEntity>(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<T: DeviceBlockEntity>(settings: Settings): Device
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION])
}
}
}

View File

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

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.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

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
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
}
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.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)
}
}
}

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
*/
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")
}
}
}

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,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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB