Merge branch 'main' into cacao

This commit is contained in:
Shadowfacts 2021-02-26 18:52:23 -05:00
commit 2c8b7cbb63
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
51 changed files with 1786 additions and 256 deletions

8
PhyConDebugLogging.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Loggers>
<Logger name="PhyNet">
<AppenderRef ref="SysOut" level="debug"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -1,5 +1,5 @@
plugins { plugins {
id "fabric-loom" version "0.5-SNAPSHOT" id "fabric-loom" version "0.6.49"
id "maven-publish" id "maven-publish"
id "org.jetbrains.kotlin.jvm" version "1.4.30" id "org.jetbrains.kotlin.jvm" version "1.4.30"
id "com.github.johnrengelman.shadow" version "4.0.4" id "com.github.johnrengelman.shadow" version "4.0.4"
@ -13,6 +13,7 @@ version = project.mod_version
group = project.maven_group group = project.maven_group
minecraft { minecraft {
log4jConfigs.from "PhyConDebugLogging.xml"
} }
repositories { repositories {

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,8 +6,8 @@ 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.ServerReceiver import org.apache.logging.log4j.LogManager
/** /**
* @author shadowfacts * @author shadowfacts
@ -16,6 +16,8 @@ object PhysicalConnectivity: ModInitializer {
val MODID = "phycon" val MODID = "phycon"
val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
override fun onInitialize() { override fun onInitialize() {
PhyBlocks.init() PhyBlocks.init()
PhyBlockEntities.init() PhyBlockEntities.init()
@ -23,6 +25,9 @@ object PhysicalConnectivity: ModInitializer {
PhyScreens.init() PhyScreens.init()
registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalRequestItem)
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
registerGlobalReceiver(C2SConfigureActivationMode)
registerGlobalReceiver(C2SConfigureRedstoneController)
} }
private fun registerGlobalReceiver(receiver: ServerReceiver) { private fun registerGlobalReceiver(receiver: ServerReceiver) {

View File

@ -2,19 +2,30 @@ package net.shadowfacts.phycon
import net.fabricmc.api.ClientModInitializer import net.fabricmc.api.ClientModInitializer
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
import net.minecraft.client.render.RenderLayer import net.minecraft.client.render.RenderLayer
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.network.block.terminal.TerminalScreen import net.shadowfacts.phycon.network.block.terminal.TerminalScreen
import net.shadowfacts.phycon.networking.ClientReceiver
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
object PhysicalConnectivityClient: ClientModInitializer { object PhysicalConnectivityClient: ClientModInitializer {
override fun onInitializeClient() { override fun onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent()) BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent())
ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen) ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen)
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
} }
private fun registerGlobalReceiver(receiver: ClientReceiver) {
ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
}
} }

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
import net.shadowfacts.phycon.screen.TestCacaoScreen import net.shadowfacts.phycon.screen.TestCacaoScreen
/** /**
@ -37,9 +41,13 @@ class ConsoleItem: Item(Settings()) {
} }
private fun openScreen(be: DeviceBlockEntity) { private fun openScreen(be: DeviceBlockEntity) {
// val screen = DeviceConsoleScreen(be)
val screen = TestCacaoScreen() val screen = TestCacaoScreen()
// 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

@ -6,6 +6,7 @@ import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable import net.minecraft.util.Tickable
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.api.Interface
@ -18,8 +19,7 @@ import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.ARPQueryFrame import net.shadowfacts.phycon.network.frame.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,36 +52,45 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
abstract override fun handle(packet: Packet) abstract override fun handle(packet: Packet)
private fun doHandlePacket(packet: Packet) {
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
when (packet) {
is DeviceRemovedPacket -> {
arpTable.remove(packet.source)
}
}
handle(packet)
}
override fun send(frame: EthernetFrame) { override fun send(frame: EthernetFrame) {
findDestination()?.receive(frame) findDestination()?.receive(frame)
} }
override fun receive(frame: EthernetFrame) { override fun receive(frame: EthernetFrame) {
println("$this ($ipAddress, ${macAddress}) received frame from ${frame.source}: $frame") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame)
when (frame) { when (frame) {
is ARPQueryFrame -> handleARPQuery(frame) is ARPQueryFrame -> handleARPQuery(frame)
is ARPResponseFrame -> handleARPResponse(frame) is ARPResponseFrame -> handleARPResponse(frame)
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}") doHandlePacket(frame.packet)
handle(frame.packet)
} }
} }
} }
} }
private fun handleARPQuery(frame: ARPQueryFrame) { private fun handleARPQuery(frame: ARPQueryFrame) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}") PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
arpTable[frame.sourceIP] = frame.source arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) { if (frame.queryIP == ipAddress) {
println("$this ($ipAddress) sending ARP response to ${frame.source} with $macAddress") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress)
send(ARPResponseFrame(ipAddress, macAddress, frame.source)) send(ARPResponseFrame(ipAddress, macAddress, frame.source))
} }
} }
private fun handleARPResponse(frame: ARPResponseFrame) { private fun handleARPResponse(frame: ARPResponseFrame) {
arpTable[frame.query] = frame.source arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}") PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source)
val toRemove = packetQueue.filter { (packet, _) -> val toRemove = packetQueue.filter { (packet, _) ->
if (packet.destination == frame.query) { if (packet.destination == frame.query) {
@ -102,7 +111,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
} else { } else {
packetQueue.add(PendingPacket(packet, counter)) packetQueue.add(PendingPacket(packet, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination)
send(ARPQueryFrame(packet.destination, ipAddress, macAddress)) send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
} }
} }

View File

@ -0,0 +1,107 @@
package net.shadowfacts.phycon.network
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.ShapeContext
import net.minecraft.item.ItemPlacementContext
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.Properties
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
/**
* @author shadowfacts
*/
abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): DeviceBlock<T>(settings) {
companion object {
val FACING = Properties.FACING
val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
}
protected abstract val faceThickness: Double
protected abstract val faceShapes: Map<Direction, VoxelShape>
private val centerShapes: Map<Direction, VoxelShape> by lazy {
mapOf(
Direction.DOWN to createCuboidShape(6.0, faceThickness, 6.0, 10.0, 10.0, 10.0),
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 16.0 - faceThickness, 10.0),
Direction.NORTH to createCuboidShape(6.0, 6.0, faceThickness, 10.0, 10.0, 10.0),
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 16.0 - faceThickness),
Direction.WEST to createCuboidShape(faceThickness, 6.0, 6.0, 10.0, 10.0, 10.0),
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 16.0 - faceThickness, 10.0, 10.0)
)
}
private val shapeCache = mutableMapOf<Pair<Direction, Direction>, VoxelShape>()
fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
return shapeCache.getOrPut(facing to cableConnection) {
VoxelShapes.union(
faceShapes[facing],
centerShapes[facing],
CableBlock.SIDE_SHAPES[cableConnection]
)
}
}
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
return EnumSet.of(state[CABLE_CONNECTION])
}
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
return if (side == state[FACING]) {
null
} else {
getBlockEntity(world, pos)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
builder.add(CABLE_CONNECTION)
}
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
val cableConnection = getCableConnectedSide(context.world, context.blockPos, facing) ?: facing.opposite
return defaultState
.with(FACING, facing)
.with(CABLE_CONNECTION, cableConnection)
}
protected fun getCableConnectedSide(world: World, pos: BlockPos, facing: Direction): Direction? {
for (side in Direction.values()) {
if (side == facing) {
continue
}
val offsetPos = pos.offset(side)
val state = world.getBlockState(offsetPos)
val block = state.block
if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) {
return side
}
}
return null
}
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
return state.with(CABLE_CONNECTION, side)
}
return state
}
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION])
}
}

View File

@ -1,59 +1,126 @@
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.Simulation
import alexiil.mc.lib.attributes.item.FixedItemInv import alexiil.mc.lib.attributes.item.FixedItemInv
import alexiil.mc.lib.attributes.item.GroupedItemInv
import alexiil.mc.lib.attributes.item.ItemAttributes import alexiil.mc.lib.attributes.item.ItemAttributes
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
import net.minecraft.block.BlockState
import net.minecraft.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.math.Direction import net.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.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.component.NetworkStackDispatcher
import net.shadowfacts.phycon.network.component.handleItemStack
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.ItemStackPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket
import net.shadowfacts.phycon.network.packet.RemoteActivationPacket
import net.shadowfacts.phycon.util.ActivationMode
import kotlin.properties.Delegates
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR),
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
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: FixedItemInv? = null
private var shouldExtract = false override val pendingInsertions = mutableListOf<PendingInsertion>()
override val dispatchStackTimeout = 40L
override val controller = ActivationController(SLEEP_TIME, this)
fun updateInventory() { fun updateInventory() {
val offsetPos = pos.offset(facing) val offsetPos = pos.offset(facing)
val option = SearchOptions.inDirection(facing) val option = SearchOptions.inDirection(facing)
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option) inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
} }
private fun getInventory(): GroupedItemInv? { private fun getInventory(): FixedItemInv? {
if (inventory == null) updateInventory() if (inventory == null) updateInventory()
return inventory return inventory
} }
override fun handle(packet: Packet) { override fun handle(packet: Packet) {
if (packet is CapacityPacket && shouldExtract) { when (packet) {
getInventory()?.also { inv -> is CapacityPacket -> handleCapacity(packet)
shouldExtract = false is ItemStackPacket -> handleItemStack(packet)
val extracted = inv.extract(packet.stack, packet.capacity) is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
sendPacket(ItemStackPacket(extracted, ipAddress, packet.stackReceiver.ipAddress))
}
} }
} }
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
// we can't insert things back into the inventory, so just let them spawn
return packet.stack
}
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
val inventory = getInventory() ?: return insertion.stack
// if the inventory has changed, the old slot index is meaningless
if (inventory !== insertion.inventory) return insertion.stack
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
if (extracted.isEmpty) return insertion.stack
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
insertion.stack = extracted
return super.finishInsertion(insertion)
}
override fun tick() { 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
shouldExtract = true
sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST))
}
} }
} }
}
override fun activate(): Boolean {
val inventory = getInventory() ?: return false
for (slot in 0 until inventory.slotCount) {
val slotStack = inventory.getInvStack(slot)
if (slotStack.isEmpty) continue
dispatchItemStack(slotStack) { insertion ->
insertion.inventory = inventory
insertion.inventorySlot = slot
}
return true
}
return false
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putString("ActivationMode", controller.activationMode.name)
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putString("ActivationMode", controller.activationMode.name)
return super.toClientTag(tag)
}
override fun fromClientTag(tag: CompoundTag) {
super.fromClientTag(tag)
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
}
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
lateinit var inventory: FixedItemInv
var inventorySlot by Delegates.notNull<Int>()
}
}

View File

@ -4,103 +4,39 @@ import alexiil.mc.lib.attributes.AttributeList
import alexiil.mc.lib.attributes.AttributeProvider import alexiil.mc.lib.attributes.AttributeProvider
import net.minecraft.block.* import net.minecraft.block.*
import net.minecraft.entity.LivingEntity import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemPlacementContext
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.state.StateManager
import net.minecraft.state.property.EnumProperty
import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.minecraft.util.shape.VoxelShape
import net.minecraft.util.shape.VoxelShapes
import net.minecraft.world.BlockView import net.minecraft.world.BlockView
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.NetworkComponentBlock import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.network.FaceDeviceBlock
import net.shadowfacts.phycon.network.DeviceBlock
import net.shadowfacts.phycon.network.block.cable.CableBlock
import java.util.*
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.METAL)), class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>(Settings.of(Material.METAL)),
NetworkComponentBlock, NetworkComponentBlock,
AttributeProvider { AttributeProvider {
companion object { companion object {
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface") val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
val FACING = Properties.FACING
val CABLE_CONNECTION = EnumProperty.of("cable_connection", Direction::class.java)
private val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
)
private val CENTER_SHAPES = mapOf<Direction, VoxelShape>(
Direction.DOWN to createCuboidShape(6.0, 2.0, 6.0, 10.0, 10.0, 10.0),
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 14.0, 10.0),
Direction.NORTH to createCuboidShape(6.0, 6.0, 2.0, 10.0, 10.0, 10.0),
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 14.0),
Direction.WEST to createCuboidShape(2.0, 6.0, 6.0, 10.0, 10.0, 10.0),
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 14.0, 10.0, 10.0)
)
private val shapeCache = mutableMapOf<Pair<Direction, Direction>, VoxelShape>()
fun getShape(facing: Direction, cableConnection: Direction): VoxelShape {
return shapeCache.getOrPut(facing to cableConnection) {
VoxelShapes.union(
VoxelShapes.union(SIDE_SHAPES[facing], CENTER_SHAPES[facing]),
CableBlock.SIDE_SHAPES[cableConnection]
)
}
}
} }
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> { override val faceThickness = 2.0
val set = EnumSet.of(state[CABLE_CONNECTION]) override val faceShapes = mapOf(
set.remove(state[FACING]) Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
return set Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
} Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? { Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
return if (side == state[FACING]) { Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
null )
} else {
getBlockEntity(world, pos)
}
}
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
super.appendProperties(builder)
builder.add(FACING)
builder.add(CABLE_CONNECTION)
}
override fun createBlockEntity(world: BlockView) = InterfaceBlockEntity() override fun createBlockEntity(world: BlockView) = InterfaceBlockEntity()
override fun getPlacementState(context: ItemPlacementContext): BlockState {
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
val cableConnection = getCableConnectionSide(context.world, context.blockPos) ?: facing.opposite
return defaultState.with(FACING, facing).with(CABLE_CONNECTION, cableConnection)
}
private fun getCableConnectionSide(world: World, pos: BlockPos): Direction? {
for (side in Direction.values()) {
val offsetPos = pos.offset(side)
if (world.getBlockState(offsetPos).block is NetworkComponentBlock) {
return side
}
}
return null
}
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) { override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
if (!world.isClient) { if (!world.isClient) {
getBlockEntity(world, pos)!!.updateInventory() getBlockEntity(world, pos)!!.updateInventory()
@ -113,19 +49,8 @@ class InterfaceBlock: DeviceBlock<InterfaceBlockEntity>(Settings.of(Material.MET
} }
} }
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
if (neighborState.block is NetworkComponentBlock && world.getBlockState(pos.offset(state[CABLE_CONNECTION])).block !is NetworkComponentBlock) {
return state.with(CABLE_CONNECTION, side)
}
return state
}
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) { override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
to.offer(getBlockEntity(world, pos)) to.offer(getBlockEntity(world, pos))
} }
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
return getShape(state[FACING], state[CABLE_CONNECTION])
}
} }

View File

@ -9,11 +9,13 @@ import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.packet.Packet
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.FaceDeviceBlock
import net.shadowfacts.phycon.network.component.ItemStackPacketHandler import net.shadowfacts.phycon.network.component.ItemStackPacketHandler
import net.shadowfacts.phycon.network.component.NetworkStackProvider import net.shadowfacts.phycon.network.component.NetworkStackProvider
import net.shadowfacts.phycon.network.component.NetworkStackReceiver import net.shadowfacts.phycon.network.component.NetworkStackReceiver
import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.component.handleItemStack
import net.shadowfacts.phycon.network.packet.* import net.shadowfacts.phycon.network.packet.*
import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
@ -24,7 +26,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
NetworkStackReceiver { NetworkStackReceiver {
private val facing: Direction private val facing: Direction
get() = cachedState[InterfaceBlock.FACING] get() = cachedState[FaceDeviceBlock.FACING]
// todo: should this be a weak ref? // todo: should this be a weak ref?
private var inventory: GroupedItemInv? = null private var inventory: GroupedItemInv? = null
@ -67,8 +69,16 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE),
private fun handleExtractStack(packet: ExtractStackPacket) { private fun handleExtractStack(packet: ExtractStackPacket) {
getInventory()?.also { inv -> getInventory()?.also { inv ->
val extracted = inv.extract(packet.stack, packet.amount) var amount = packet.amount
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source)) while (amount > 0) {
val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount))
if (extracted.isEmpty) {
break
} else {
amount -= extracted.count
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
}
}
} }
} }

View File

@ -7,6 +7,7 @@ import net.minecraft.entity.ItemEntity
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable import net.minecraft.util.Tickable
import net.minecraft.util.math.Direction import net.minecraft.util.math.Direction
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.frame.EthernetFrame import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame import net.shadowfacts.phycon.api.frame.PacketFrame
@ -53,10 +54,10 @@ class SwitchBlockEntity: BlockEntity(PhyBlockEntities.SWITCH),
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) { if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
val dir = macTable[frame.destination]!! val dir = macTable[frame.destination]!!
println("$this (${fromItf.side}, ${fromItf.macAddress}) forwarding $frame to side $dir") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
interfaceForSide(dir).send(frame) interfaceForSide(dir).send(frame)
} else { } else {
println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame") PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame)
flood(frame, fromItf) flood(frame, fromItf)
} }
} }

View File

@ -0,0 +1,74 @@
package net.shadowfacts.phycon.network.block.redstone
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.block.Material
import net.minecraft.item.ItemPlacementContext
import net.minecraft.server.world.ServerWorld
import net.minecraft.state.StateManager
import net.minecraft.state.property.Properties
import net.minecraft.util.Identifier
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.world.BlockView
import net.minecraft.world.World
import net.minecraft.world.WorldAccess
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.FaceDeviceBlock
import java.util.*
/**
* @author shadowfacts
*/
class RedstoneControllerBlock: FaceDeviceBlock<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 = 3.0
override val faceShapes = mapOf(
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
)
override fun appendProperties(builder: StateManager.Builder<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]))
}
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
// this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
if (!world.isClient) {
val wasLit = state[LIT]
val isLit = isPowered(world, pos, state[FACING])
if (wasLit != isLit) {
toggleLit(state, world, pos)
}
}
}
private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean {
val offset = pos.offset(facing)
return world.getEmittedRedstonePower(offset, facing) > 0
}
private fun toggleLit(state: BlockState, world: World, pos: BlockPos) {
world.setBlockState(pos, state.cycle(LIT), 2)
getBlockEntity(world, pos)!!.redstoneStateChanged()
}
}

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

@ -56,7 +56,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private var observers = 0 private var observers = 0
val cachedNetItems = ItemStackCollections.intMap() val cachedNetItems = ItemStackCollections.intMap()
var cachedSortedNetItems = listOf<ItemStack>()
var netItemObserver: WeakReference<NetItemObserver>? = null var netItemObserver: WeakReference<NetItemObserver>? = null
@ -86,14 +85,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
private fun handleReadInventory(packet: ReadInventoryPacket) { private fun handleReadInventory(packet: ReadInventoryPacket) {
inventoryCache[packet.source] = packet.inventory inventoryCache[packet.source] = packet.inventory
updateNetItems() updateAndSync()
sync()
} }
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
inventoryCache.remove(packet.source) inventoryCache.remove(packet.source)
updateNetItems() updateAndSync()
sync()
} }
private fun handleStackLocation(packet: StackLocationPacket) { private fun handleStackLocation(packet: StackLocationPacket) {
@ -113,12 +110,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
// this happens outside the normal update loop because by receiving the item stack packet // this happens outside the normal update loop because by receiving the item stack packet
// we "know" how much the count in the source inventory has changed // we "know" how much the count in the source inventory has changed
updateNetItems() updateAndSync()
sync()
return remaining return remaining
} }
private fun updateAndSync() {
updateNetItems()
sync()
netItemObserver?.get()?.netItemsChanged()
}
private fun updateNetItems() { private fun updateNetItems() {
cachedNetItems.clear() cachedNetItems.clear()
for (inventory in inventoryCache.values) { for (inventory in inventoryCache.values) {
@ -127,12 +129,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
cachedNetItems.mergeInt(stack, amount) { a, b -> a + b } cachedNetItems.mergeInt(stack, amount) { a, b -> a + b }
} }
} }
// todo: is the map necessary or is just the sorted list enough?
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
} }
private fun beginInsertions() { private fun beginInsertions() {
@ -175,21 +171,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
finishTimedOutPendingInsertions() finishTimedOutPendingInsertions()
} }
if (observers > 0) { if (observers > 0 && !world!!.isClient) {
if (world!!.isClient) { updateAndSync()
println(cachedNetItems)
} else {
updateNetItems()
sync()
}
} }
} }
} }
fun onActivate(player: PlayerEntity) { fun onActivate(player: PlayerEntity) {
if (!world!!.isClient) { if (!world!!.isClient) {
updateNetItems() updateAndSync()
sync()
inventoryCache.clear() inventoryCache.clear()
sendPacket(RequestInventoryPacket(ipAddress)) sendPacket(RequestInventoryPacket(ipAddress))
@ -238,8 +228,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
internalBuffer.setStack(insertion.bufferSlot, remaining) internalBuffer.setStack(insertion.bufferSlot, remaining)
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets // as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
updateNetItems() updateAndSync()
sync()
return remaining return remaining
} }
@ -263,31 +252,11 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL),
override fun toClientTag(tag: CompoundTag): CompoundTag { override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.put("InternalBuffer", internalBuffer.toTag()) tag.put("InternalBuffer", internalBuffer.toTag())
val list = ListTag()
tag.put("CachedNetItems", list)
for ((stack, amount) in cachedNetItems) {
val entryTag = stack.toTag(CompoundTag())
entryTag.putInt("NetAmount", amount)
list.add(entryTag)
}
return tag return tag
} }
override fun fromClientTag(tag: CompoundTag) { override fun fromClientTag(tag: CompoundTag) {
internalBuffer.fromTag(tag.getCompound("InternalBuffer")) internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
val list = tag.getList("CachedNetItems", 10)
cachedNetItems.clear()
for (entryTag in list) {
val stack = ItemStack.fromTag(entryTag as CompoundTag)
val netAmount = entryTag.getInt("NetAmount")
cachedNetItems[stack] = netAmount
}
netItemObserver?.get()?.netItemsChanged()
cachedSortedNetItems = cachedNetItems.object2IntEntrySet().sortedByDescending { it.intValue }.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
}
} }
interface NetItemObserver { interface NetItemObserver {

View File

@ -1,18 +1,33 @@
package net.shadowfacts.phycon.network.block.terminal package net.shadowfacts.phycon.network.block.terminal
import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.DrawableHelper import net.minecraft.client.gui.DrawableHelper
import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.gui.widget.AbstractButtonWidget
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.TextFieldWidget import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack import net.minecraft.client.util.math.MatrixStack
import net.minecraft.entity.player.PlayerInventory import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack
import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.LiteralText import net.minecraft.text.LiteralText
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity 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 org.lwjgl.glfw.GLFW
import java.lang.NumberFormatException
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
@ -20,10 +35,32 @@ import org.lwjgl.glfw.GLFW
// todo: translate title // todo: translate title
class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen<TerminalScreenHandler>(handler, playerInv, title) { class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen<TerminalScreenHandler>(handler, playerInv, title) {
companion object { companion object {
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png") private val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal.png")
private val DIALOG = Identifier(PhysicalConnectivity.MODID, "textures/gui/terminal_amount.png")
} }
private lateinit var searchBox: TextFieldWidget private lateinit var searchBox: TextFieldWidget
private lateinit var sortButton: SortButton
private lateinit var amountBox: TextFieldWidget
private var dialogStack = ItemStack.EMPTY
private var showingAmountDialog = false
set(value) {
val oldValue = field
field = value
for (e in dialogChildren) {
e.visible = value
}
amountBox.isVisible
searchBox.setSelected(!value)
amountBox.setSelected(value)
if (value && !oldValue) {
amountBox.text = "1"
}
}
private var dialogChildren = mutableListOf<AbstractButtonWidget>()
private val dialogWidth = 158
private val dialogHeight = 62
init { init {
backgroundWidth = 252 backgroundWidth = 252
@ -33,6 +70,9 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
override fun init() { override fun init() {
super.init() super.init()
children.clear()
dialogChildren.clear()
client!!.keyboard.setRepeatEvents(true) client!!.keyboard.setRepeatEvents(true)
searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search")) searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search"))
@ -42,37 +82,134 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
searchBox.isVisible = true searchBox.isVisible = true
searchBox.setSelected(true) searchBox.setSelected(true)
searchBox.setEditableColor(0xffffff) searchBox.setEditableColor(0xffffff)
children.add(searchBox) addChild(searchBox)
sortButton = SortButton(x + 256, y + 0, handler.sortMode, {
requestUpdatedItems()
}, ::renderTooltip)
addButton(sortButton)
val dialogMinX = width / 2 - dialogWidth / 2
val dialogMinY = height / 2 - dialogHeight / 2
amountBox = TextFieldWidget(textRenderer, dialogMinX + 8, dialogMinY + 27, 80, 9, LiteralText("Amount"))
amountBox.setHasBorder(false)
amountBox.isVisible = false
amountBox.setSelected(false)
amountBox.setEditableColor(0xffffff)
amountBox.setTextPredicate {
if (it.isEmpty()) {
true
} else {
try {
Integer.parseInt(it) > 0
} catch (e: NumberFormatException) {
false
}
}
}
dialogChildren.add(amountBox)
val plusOne = SmallButton(dialogMinX + 7, dialogMinY + 7, 28, LiteralText("+1")) {
amountBox.intValue += 1
}
dialogChildren.add(plusOne)
val plusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 7, 28, LiteralText("+10")) {
amountBox.intValue = ceil((amountBox.intValue + 1) / 10.0).toInt() * 10
}
dialogChildren.add(plusTen)
val plusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 7, 28, LiteralText("+100")) {
amountBox.intValue = ceil((amountBox.intValue + 1) / 100.0).toInt() * 100
}
dialogChildren.add(plusHundred)
val minusOne = SmallButton(dialogMinX + 7, dialogMinY + 39, 28, LiteralText("-1")) {
amountBox.intValue -= 1
}
dialogChildren.add(minusOne)
val minusTen = SmallButton(dialogMinX + 7 + 28 + 3, dialogMinY + 39, 28, LiteralText("-10")) {
amountBox.intValue = floor((amountBox.intValue - 1) / 10.0).toInt() * 10
}
dialogChildren.add(minusTen)
val minusHundred = SmallButton(dialogMinX + 7 + (28 + 3) * 2, dialogMinY + 39, 28, LiteralText("-100")) {
amountBox.intValue = floor((amountBox.intValue - 1) / 100.0).toInt() * 100
}
dialogChildren.add(minusHundred)
// 101,25
val request = ButtonWidget(dialogMinX + 101, dialogMinY + 21, 50, 20, LiteralText("Request")) {
doDialogRequest()
}
dialogChildren.add(request)
requestUpdatedItems()
}
private fun requestUpdatedItems() {
val player = MinecraftClient.getInstance().player!!
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchBox.text, sortButton.mode))
} }
override fun tick() { override fun tick() {
super.tick() super.tick()
searchBox.tick() searchBox.tick()
amountBox.tick()
} }
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040) textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040)
textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040) textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040)
// todo: translate this textRenderer.draw(matrixStack, TranslatableText("gui.phycon.terminal_buffer"), 7f, 6f, 0x404040)
textRenderer.draw(matrixStack, "Buffer", 7f, 6f, 0x404040)
} }
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) { override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
renderBackground(matrixStack) // if the dialog is open, the background gradient will be drawn in front of the main terminal gui
if (!showingAmountDialog) {
renderBackground(matrixStack)
}
GlStateManager.color4f(1f, 1f, 1f, 1f) RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(BACKGROUND) client!!.textureManager.bindTexture(BACKGROUND)
val x = (width - backgroundWidth) / 2 val x = (width - backgroundWidth) / 2
val y = (height - backgroundHeight) / 2 val y = (height - backgroundHeight) / 2
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight) drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
} }
@ExperimentalUnsignedTypes
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
super.render(matrixStack, mouseX, mouseY, delta) if (showingAmountDialog) {
RenderSystem.pushMatrix()
// items are rendered at some stupidly high z offset. item amounts at an even higher one
RenderSystem.translatef(0f, 0f, -350f)
// fake the mouse x/y while showing a dialog so slot mouseover highlights aren't drawn
super.render(matrixStack, -1, -1, delta)
RenderSystem.popMatrix()
} else {
super.render(matrixStack, mouseX, mouseY, delta)
}
searchBox.render(matrixStack, mouseX, mouseY, delta) searchBox.render(matrixStack, mouseX, mouseY, delta)
drawMouseoverTooltip(matrixStack, mouseX, mouseY) if (showingAmountDialog) {
renderBackground(matrixStack)
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DIALOG)
val dialogMinX = width / 2 - dialogWidth / 2
val dialogMinY = height / 2 - dialogHeight / 2
drawTexture(matrixStack, dialogMinX, dialogMinY, 0, 0, dialogWidth, dialogHeight)
for (e in dialogChildren) {
e.render(matrixStack, mouseX, mouseY, delta)
}
} else {
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
}
} }
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@ -90,42 +227,208 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory,
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt()) DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
} }
override fun onMouseClick(slot: Slot?, i: Int, j: Int, slotActionType: SlotActionType?) { override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
super.onMouseClick(slot, i, j, slotActionType) super.onMouseClick(slot, invSlot, clickData, type)
// don't unfocus the search box on mouse click // don't unfocus the search box on mouse click
searchBox.setSelected(true) searchBox.setSelected(true)
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id)) {
val stack = slot.stack
if (type == SlotActionType.QUICK_MOVE) {
// shift click, request full stack
requestItem(stack, min(stack.count, stack.maxCount))
} else if (type == SlotActionType.PICKUP) {
if (clickData == 1) {
// right click, request half stack
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
} else {
dialogStack = stack
showingAmountDialog = true
searchBox.setSelected(false)
}
}
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (showingAmountDialog) {
for (e in dialogChildren) {
if (e.mouseClicked(mouseX, mouseY, button)) {
return true
}
}
return false
} else {
return super.mouseClicked(mouseX, mouseY, button)
}
}
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseDragged(d, e, i, f, g)
}
}
override fun mouseMoved(d: Double, e: Double) {
if (showingAmountDialog) {
} else {
super.mouseMoved(d, e)
}
}
override fun mouseReleased(d: Double, e: Double, i: Int): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseReleased(d, e, i)
}
}
override fun mouseScrolled(d: Double, e: Double, f: Double): Boolean {
if (showingAmountDialog) {
return false
} else {
return super.mouseScrolled(d, e, f)
}
} }
override fun charTyped(c: Char, i: Int): Boolean { override fun charTyped(c: Char, i: Int): Boolean {
val oldText = searchBox.text if (showingAmountDialog) {
if (searchBox.charTyped(c, i)) { return amountBox.charTyped(c, i)
if (searchBox.text != oldText) { } else {
search() val oldText = searchBox.text
if (searchBox.charTyped(c, i)) {
if (searchBox.text != oldText) {
requestUpdatedItems()
}
return true
} }
return true
}
return super.charTyped(c, i) return super.charTyped(c, i)
}
} }
override fun keyPressed(key: Int, j: Int, k: Int): Boolean { override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
val oldText = searchBox.text if (showingAmountDialog) {
if (searchBox.keyPressed(key, j, k)) { return when (key) {
if (searchBox.text != oldText) { GLFW.GLFW_KEY_ESCAPE -> {
search() showingAmountDialog = false
true
}
GLFW.GLFW_KEY_ENTER -> {
doDialogRequest()
true
}
else -> {
amountBox.keyPressed(key, j, k)
}
} }
return true
}
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
true
} else { } else {
super.keyPressed(key, j, k) val oldText = searchBox.text
if (searchBox.keyPressed(key, j, k)) {
if (searchBox.text != oldText) {
requestUpdatedItems()
}
return true
}
return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) {
true
} else {
super.keyPressed(key, j, k)
}
} }
} }
private fun search() { private fun doDialogRequest() {
screenHandler.search(searchBox.text) showingAmountDialog = false
requestItem(dialogStack, amountBox.intValue)
}
private fun requestItem(stack: ItemStack, amount: Int) {
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
netHandler.sendPacket(packet)
}
private var TextFieldWidget.intValue: Int
get() = if (text.isEmpty()) 0 else Integer.parseInt(text)
set(value) {
text = value.toString()
setSelected(true)
}
class SmallButton(x: Int, y: Int, width: Int, title: Text, action: PressAction): ButtonWidget(x, y, width, 14, title, action) {
@ExperimentalUnsignedTypes
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val client = MinecraftClient.getInstance()
client.textureManager.bindTexture(DIALOG)
RenderSystem.color4f(1f, 1f, 1f, 1f)
val v = if (isHovered) 142 else 128
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.enableDepthTest()
drawTexture(matrixStack, x, y, 0, v, width / 2, height)
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, v, width / 2, height)
drawCenteredText(matrixStack, client.textRenderer, message, x + width / 2, y + (height - 8) / 2, 0xffffffffu.toInt())
}
}
class SortButton(
x: Int,
y: Int,
var mode: SortMode,
val onChange: (SortMode) -> Unit,
val doRenderTooltip: (MatrixStack, Text, Int, Int) -> Unit
): AbstractPressableButtonWidget(x, y, 20, 20, LiteralText("")) {
override fun onPress() {}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if ((button == 0 || button == 1) && clicked(mouseX, mouseY)) {
val newVal = if (button == 0) mode.next else mode.prev
mode = newVal
onChange(mode)
playDownSound(MinecraftClient.getInstance().soundManager)
return true
}
return false
}
override fun renderButton(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
val client = MinecraftClient.getInstance()
RenderSystem.color4f(1f, 1f, 1f, 1f)
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.enableDepthTest()
client.textureManager.bindTexture(WIDGETS_LOCATION)
val k = getYImage(isHovered)
drawTexture(matrixStack, x, y, 0, 46 + k * 20, width / 2, height)
drawTexture(matrixStack, x + width / 2, y, 200 - width / 2, 46 + k * 20, width / 2, height)
client.textureManager.bindTexture(BACKGROUND)
val u: Int = when (mode) {
SortMode.COUNT_HIGH_FIRST -> 0
SortMode.COUNT_LOW_FIRST -> 16
SortMode.ALPHABETICAL -> 32
}
drawTexture(matrixStack, x + 2, y + 2, u, 230, 16, 16)
if (isHovered) {
renderToolTip(matrixStack, mouseX, mouseY)
}
}
override fun renderToolTip(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
val text = LiteralText("")
text.append("Sort by: ")
text.append(mode.tooltip)
doRenderTooltip(matrixStack, text, mouseX, mouseY)
}
} }
} }

View File

@ -8,12 +8,15 @@ import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.network.PacketByteBuf import net.minecraft.network.PacketByteBuf
import net.minecraft.screen.ScreenHandler import net.minecraft.screen.ScreenHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import net.minecraft.util.registry.Registry import net.minecraft.util.registry.Registry
import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyBlocks
import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.init.PhyScreens
import net.shadowfacts.phycon.networking.C2STerminalRequestItem import net.shadowfacts.phycon.networking.C2STerminalRequestItem
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
import net.shadowfacts.phycon.util.SortMode
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
@ -21,7 +24,7 @@ import kotlin.math.min
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId), class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val terminal: TerminalBlockEntity): ScreenHandler(PhyScreens.TERMINAL_SCREEN_HANDLER, syncId),
TerminalBlockEntity.NetItemObserver { TerminalBlockEntity.NetItemObserver {
companion object { companion object {
@ -30,6 +33,19 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
private val fakeInv = FakeInventory(this) private val fakeInv = FakeInventory(this)
private var searchQuery: String = "" private var searchQuery: String = ""
var sortMode = SortMode.COUNT_HIGH_FIRST
private set
private var itemEntries = listOf<Entry>()
set(value) {
field = value
if (terminal.world!!.isClient) {
itemsForDisplay = value.map {
val stack = it.stack.copy()
stack.count = it.amount
stack
}
}
}
var itemsForDisplay = listOf<ItemStack>() var itemsForDisplay = listOf<ItemStack>()
private set private set
@ -37,8 +53,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!) this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!)
init { init {
terminal.netItemObserver = WeakReference(this) if (!terminal.world!!.isClient) {
netItemsChanged() terminal.netItemObserver = WeakReference(this)
netItemsChanged()
}
// network // network
for (y in 0 until 6) { for (y in 0 until 6) {
@ -67,7 +85,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
} }
override fun netItemsChanged() { override fun netItemsChanged() {
itemsForDisplay = terminal.cachedNetItems.object2IntEntrySet().filter { val player = playerInv.player
assert(player is ServerPlayerEntity)
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
if (searchQuery.isBlank()) return@filter true if (searchQuery.isBlank()) return@filter true
if (searchQuery.startsWith('@')) { if (searchQuery.startsWith('@')) {
val unprefixed = searchQuery.drop(1) val unprefixed = searchQuery.drop(1)
@ -77,20 +98,34 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
} }
} }
it.key.name.string.contains(searchQuery, true) it.key.name.string.contains(searchQuery, true)
}.sortedByDescending {
it.intValue
}.map {
val stack = it.key.copy()
stack.count = it.intValue
stack
} }
val sorted =
when (sortMode) {
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
}
itemEntries = sorted.map { Entry(it.key, it.intValue) }
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode))
} }
fun search(query: String) { fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode) {
searchQuery = query this.searchQuery = query
this.sortMode = sortMode
netItemsChanged() netItemsChanged()
} }
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, sortMode: SortMode) {
assert(playerInv.player.world.isClient)
this.searchQuery = query
this.sortMode = sortMode
itemEntries = entries
}
override fun canUse(player: PlayerEntity): Boolean { override fun canUse(player: PlayerEntity): Boolean {
return true return true
} }
@ -102,28 +137,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
} }
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack { override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack {
if (isNetworkSlot(slotId)) { if (isBufferSlot(slotId)) {
// the slot clicked was one of the network stacks
if (actionType == SlotActionType.QUICK_MOVE) {
val stack = slots[slotId].stack
if (!stack.isEmpty && player.world.isClient) {
requestItem(player, stack, min(stack.count, stack.maxCount))
}
} else if (actionType == SlotActionType.PICKUP && clickData == 1) {
if (clickData == 1) {
// right click, request half stack
val stack = slots[slotId].stack
if (!stack.isEmpty && player.world.isClient) {
requestItem(player, stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
}
} else {
// todo: left click, show amount dialog
}
}
return ItemStack.EMPTY
} else if (isBufferSlot(slotId)) {
// internal buffer
// todo: why does this think it's quick_craft sometimes? // todo: why does this think it's quick_craft sometimes?
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) { if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) {
// placing cursor stack into buffer // placing cursor stack into buffer
@ -134,13 +148,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
return super.onSlotClick(slotId, clickData, actionType, player) return super.onSlotClick(slotId, clickData, actionType, player)
} }
private fun requestItem(player: PlayerEntity, stack: ItemStack, amount: Int) {
if (!player.world.isClient) return
val handler = (player as ClientPlayerEntity).networkHandler
val packet = C2STerminalRequestItem(terminal, stack, amount)
handler.sendPacket(packet)
}
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack { override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
if (isNetworkSlot(slotId)) { if (isNetworkSlot(slotId)) {
return ItemStack.EMPTY; return ItemStack.EMPTY;
@ -154,7 +161,8 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
val result = slot.stack.copy() val result = slot.stack.copy()
if (isBufferSlot(slotId)) { if (isBufferSlot(slotId)) {
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, false)) { // last boolean param is fromLast
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
return ItemStack.EMPTY return ItemStack.EMPTY
} }
if (slot.stack.isEmpty) { if (slot.stack.isEmpty) {
@ -204,4 +212,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
fun isPlayerSlot(id: Int) = id >= playerSlotsStart fun isPlayerSlot(id: Int) = id >= playerSlotsStart
data class Entry(val stack: ItemStack, val amount: Int)
} }

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

@ -33,7 +33,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
if (insertion != null) { if (insertion != null) {
insertion.results.add(packet.capacity to packet.stackReceiver) insertion.results.add(packet.capacity to packet.stackReceiver)
if (insertion.isFinishable(this)) { if (insertion.isFinishable(this)) {
finishInsertion(insertion) val remaining = finishInsertion(insertion)
// todo: do something with remaining
} }
} }
} }
@ -43,7 +44,8 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
// todo: also sort results by interface priority // todo: also sort results by interface priority
val sortedResults = insertion.results.sortedBy { it.first }.toMutableList() val sortedResults = insertion.results.sortedBy { it.first }.toMutableList()
val remaining = insertion.stack // copy the insertion stack so subclasses that override this method can still see the originally dispatched stack after the super call
val remaining = insertion.stack.copy()
while (!remaining.isEmpty && sortedResults.isNotEmpty()) { while (!remaining.isEmpty && sortedResults.isNotEmpty()) {
val (capacity, receivingInterface) = sortedResults.removeFirst() val (capacity, receivingInterface) = sortedResults.removeFirst()
if (capacity <= 0) continue if (capacity <= 0) continue
@ -56,7 +58,7 @@ interface NetworkStackDispatcher<Insertion: NetworkStackDispatcher.PendingInsert
} }
open class PendingInsertion<Self: PendingInsertion<Self>>( open class PendingInsertion<Self: PendingInsertion<Self>>(
val stack: ItemStack, var stack: ItemStack,
val timestamp: Long val timestamp: Long
) { ) {
val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>() val results = mutableSetOf<Pair<Int, NetworkStackReceiver>>()
@ -77,5 +79,6 @@ fun <Self, Insertion: NetworkStackDispatcher.PendingInsertion<Insertion>> Self.f
val finishable = pendingInsertions.filter { it.isFinishable(this) } val finishable = pendingInsertions.filter { it.isFinishable(this) }
// finishInsertion removes the object from pendingInsertions // finishInsertion removes the object from pendingInsertions
finishable.forEach(::finishInsertion) finishable.forEach(::finishInsertion)
// todo: do something with remaining?
// todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?) // todo: if a timed-out insertion can't be finished, we should probably retry after some time (exponential backoff?)
} }

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

@ -0,0 +1,50 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.minecraft.server.MinecraftServer
import net.minecraft.server.network.ServerPlayNetworkHandler
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.util.Identifier
import net.shadowfacts.phycon.PhysicalConnectivity
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
import net.shadowfacts.phycon.util.SortMode
/**
* @author shadowfacts
*/
object C2STerminalUpdateDisplayedItems: ServerReceiver {
override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_update_displayed")
operator fun invoke(terminal: TerminalBlockEntity, query: String, sortMode: SortMode): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeString(query)
buf.writeVarInt(sortMode.ordinal)
return ClientPlayNetworking.createC2SPacket(CHANNEL, buf)
}
override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val query = buf.readString()
val sortMode = SortMode.values()[buf.readVarInt()]
server.execute {
if (player.world.registryKey.value != dimID) return@execute
val screenHandler = player.currentScreenHandler
if (screenHandler !is TerminalScreenHandler) return@execute
if (screenHandler.terminal.pos != pos) return@execute
screenHandler.sendUpdatedItemsToClient(player, query, sortMode)
}
}
}

View File

@ -0,0 +1,11 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking
import net.minecraft.util.Identifier
/**
* @author shadowfacts
*/
interface ClientReceiver: ClientPlayNetworking.PlayChannelHandler {
val CHANNEL: Identifier
}

View File

@ -0,0 +1,57 @@
package net.shadowfacts.phycon.networking
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs
import net.fabricmc.fabric.api.networking.v1.PacketSender
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
import net.minecraft.client.MinecraftClient
import net.minecraft.client.network.ClientPlayNetworkHandler
import net.minecraft.network.Packet
import net.minecraft.network.PacketByteBuf
import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity
import net.shadowfacts.phycon.network.block.terminal.TerminalScreenHandler
import net.shadowfacts.phycon.util.SortMode
/**
* @author shadowfacts
*/
object S2CTerminalUpdateDisplayedItems: ClientReceiver {
override val CHANNEL = C2STerminalUpdateDisplayedItems.CHANNEL
operator fun invoke(terminal: TerminalBlockEntity, entries: List<TerminalScreenHandler.Entry>, query: String, sortMode: SortMode): Packet<*> {
val buf = PacketByteBufs.create()
buf.writeIdentifier(terminal.world!!.registryKey.value)
buf.writeBlockPos(terminal.pos)
buf.writeVarInt(entries.size)
for (e in entries) {
buf.writeItemStack(e.stack)
buf.writeVarInt(e.amount)
}
buf.writeString(query)
buf.writeVarInt(sortMode.ordinal)
return ServerPlayNetworking.createS2CPacket(CHANNEL, buf)
}
override fun receive(client: MinecraftClient, handler: ClientPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) {
val dimID = buf.readIdentifier()
val pos = buf.readBlockPos()
val entryCount = buf.readVarInt()
val entries = ArrayList<TerminalScreenHandler.Entry>(entryCount)
for (i in 0 until entryCount) {
entries.add(TerminalScreenHandler.Entry(buf.readItemStack(), buf.readVarInt()))
}
val query = buf.readString()
val sortMode = SortMode.values()[buf.readVarInt()]
client.execute {
if (client.player!!.world.registryKey.value != dimID) return@execute
val screenHandler = client.player!!.currentScreenHandler
if (screenHandler !is TerminalScreenHandler) return@execute
if (screenHandler.terminal.pos != pos) return@execute
screenHandler.receivedUpdatedItemsFromServer(entries, query, sortMode)
}
}
}

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,64 @@
package net.shadowfacts.phycon.screen
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.util.math.MatrixStack
import net.shadowfacts.phycon.network.DeviceBlockEntity
import net.shadowfacts.phycon.network.component.ActivationController
import net.shadowfacts.phycon.networking.C2SConfigureActivationMode
import net.shadowfacts.phycon.util.ActivationMode
import net.shadowfacts.phycon.util.next
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class ActivatableDeviceConsoleScreen<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
val mode = EnumButton(device.controller::activationMode, minX + 5, minY + 25, 55, 20) {
client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device))
}
addButton(mode)
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose()
return true
}
return super.keyPressed(key, j, k)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
super.render(matrixStack, mouseX, mouseY, delta)
textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
}
}

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)
} }
@ -35,4 +37,4 @@ class DeviceConsoleScreen(
drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff) drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff)
drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff) drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff)
} }
} }

View File

@ -0,0 +1,49 @@
package net.shadowfacts.phycon.screen
import net.minecraft.client.gui.widget.AbstractPressableButtonWidget
import net.minecraft.text.Text
import net.shadowfacts.phycon.util.FriendlyNameable
import net.shadowfacts.phycon.util.RotatableEnum
import net.shadowfacts.phycon.util.next
import net.shadowfacts.phycon.util.prev
import kotlin.reflect.KMutableProperty
/**
* @author shadowfacts
*/
class EnumButton<E>(
val prop: KMutableProperty<E>,
x: Int,
y: Int,
width: Int,
height: Int,
val onChange: () -> Unit
): AbstractPressableButtonWidget(
x,
y,
width,
height,
prop.getter.call().friendlyName
) where E: Enum<E>, E: RotatableEnum, E: FriendlyNameable {
private var currentButton: Int? = null
override fun mouseClicked(d: Double, e: Double, button: Int): Boolean {
currentButton = button
val res = super.mouseClicked(d, e, button)
currentButton = null
return res
}
override fun isValidClickButton(i: Int): Boolean {
return i == 0 || i == 1
}
override fun onPress() {
val newVal = if ((currentButton ?: 0) == 0) prop.getter.call().next else prop.getter.call().prev
prop.setter.call(newVal)
message = newVal.friendlyName
onChange()
}
}

View File

@ -0,0 +1,96 @@
package net.shadowfacts.phycon.screen
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.widget.ButtonWidget
import net.minecraft.client.gui.widget.TextFieldWidget
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.LiteralText
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity
import net.shadowfacts.phycon.networking.C2SConfigureRedstoneController
import net.shadowfacts.phycon.util.next
import org.lwjgl.glfw.GLFW
/**
* @author shadowfacts
*/
class RedstoneControllerConsoleScreen(
val device: RedstoneControllerBlockEntity
): Screen(device.cachedState.block.name) {
private val backgroundWidth = 252
private val backgroundHeight = 222
private val ipAddressTextFields = mutableListOf<TextFieldWidget>()
override fun init() {
super.init()
buttons.clear()
ipAddressTextFields.clear()
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
val mode = EnumButton(device::redstoneMode, minX + 5, minY + 25, 75, 20) {
client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
addButton(mode)
for (i in 0 until 5) {
// todo: field name
val field = TextFieldWidget(textRenderer, minX + 5, minY + 50 + 22 * i, backgroundWidth / 2, 20, LiteralText(""))
field.setMaxLength(15)
field.setHasBorder(true)
field.isVisible = true
field.setEditableColor(0xffffff)
field.text = device.managedDevices[i]?.toString()
field.setChangedListener { newVal ->
device.managedDevices[i] = IPAddress.parse(newVal)
client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device))
}
addChild(field)
ipAddressTextFields.add(field)
}
}
override fun isPauseScreen() = false
override fun keyPressed(key: Int, j: Int, k: Int): Boolean {
if (key == GLFW.GLFW_KEY_E) {
onClose()
return true
}
return super.keyPressed(key, j, k)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val clickedField = ipAddressTextFields.find { it.x <= mouseX && it.x + it.width >= mouseX && it.y <= mouseY && it.y + it.height >= mouseY }
if (clickedField != null) {
ipAddressTextFields.forEach {
if (it !== clickedField) it.setSelected(false)
}
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
renderBackground(matrixStack)
val minX = (width - backgroundWidth) / 2
val minY = (height - backgroundHeight) / 2
RenderSystem.color4f(1f, 1f, 1f, 1f)
client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND)
drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight)
super.render(matrixStack, mouseX, mouseY, delta)
ipAddressTextFields.forEach { it.render(matrixStack, mouseX, mouseY, delta) }
textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040)
textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040)
}
}

View File

@ -0,0 +1,15 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
/**
* @author shadowfacts
*/
enum class ActivationMode: RotatableEnum, FriendlyNameable {
AUTOMATIC,
MANAGED;
override val friendlyName: Text
get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}")
}

View File

@ -0,0 +1,10 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
interface FriendlyNameable {
val friendlyName: Text
}

View File

@ -0,0 +1,30 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.Text
import net.minecraft.text.TranslatableText
/**
* @author shadowfacts
*/
enum class RedstoneMode: RotatableEnum, FriendlyNameable {
HIGH,
LOW,
TOGGLE,
RISING_EDGE,
FALLING_EDGE;
val isDiscrete: Boolean
get() = when (this) {
TOGGLE, RISING_EDGE, FALLING_EDGE -> true
else -> false
}
val isContinuous: Boolean
get() = when (this) {
HIGH, LOW -> true
else -> false
}
override val friendlyName: Text
get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}")
}

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

@ -0,0 +1,21 @@
package net.shadowfacts.phycon.util
import net.minecraft.text.LiteralText
import net.minecraft.text.Text
/**
* @author shadowfacts
*/
enum class SortMode: RotatableEnum {
COUNT_HIGH_FIRST,
COUNT_LOW_FIRST,
ALPHABETICAL;
val tooltip: Text
get() = when (this) {
COUNT_HIGH_FIRST -> LiteralText("Count, highest first")
COUNT_LOW_FIRST -> LiteralText("Count, lowest first")
ALPHABETICAL -> LiteralText("Name")
}
}

View File

@ -140,7 +140,6 @@
}, },
{ {
"when": {"cable_connection": "down", "facing": "east"}, "when": {"cable_connection": "down", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 } "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
}, },

View File

@ -0,0 +1,183 @@
{
"multipart": [
{
"apply": { "model": "phycon:block/cable_center" }
},
{
"when": { "facing": "down", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off" }
},
{
"when": { "facing": "up", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 180 }
},
{
"when": { "facing": "north", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 270 }
},
{
"when": { "facing": "south", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90 }
},
{
"when": { "facing": "west", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 90 }
},
{
"when": { "facing": "east", "lit": "false" },
"apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 270 }
},
{
"when": { "facing": "down", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on" }
},
{
"when": { "facing": "up", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 180 }
},
{
"when": { "facing": "north", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 270 }
},
{
"when": { "facing": "south", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90 }
},
{
"when": { "facing": "west", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 90 }
},
{
"when": { "facing": "east", "lit": "true" },
"apply": { "model": "phycon:block/redstone_controller_side_on", "x": 90, "y": 270 }
},
{
"when": { "cable_connection": "up", "facing": "down" },
"apply": { "model": "phycon:block/interface_cable_straight" }
},
{
"when": { "cable_connection": "down", "facing": "up" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 180 }
},
{
"when": { "cable_connection": "north", "facing": "south" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90 }
},
{
"when": { "cable_connection": "south", "facing": "north" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 270 }
},
{
"when": { "cable_connection": "west", "facing": "east" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 270 }
},
{
"when": { "cable_connection": "east", "facing": "west" },
"apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 90 }
},
{
"when": {"cable_connection": "north", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner" }
},
{
"when": {"cable_connection": "east", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 180 }
},
{
"when": {"cable_connection": "west", "facing": "down"},
"apply": { "model": "phycon:block/interface_cable_corner", "y": 270 }
},
{
"when": {"cable_connection": "north", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "east", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 270 }
},
{
"when": {"cable_connection": "south", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180 }
},
{
"when": {"cable_connection": "west", "facing": "up"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 90 }
},
{
"when": {"cable_connection": "down", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 180 }
},
{
"when": {"cable_connection": "up", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270 }
},
{
"when": {"cable_connection": "west", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner_2" }
},
{
"when": {"cable_connection": "east", "facing": "north"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "down", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90 }
},
{
"when": {"cable_connection": "up", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 180 }
},
{
"when": {"cable_connection": "west", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner_3" }
},
{
"when": {"cable_connection": "east", "facing": "south"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 180 }
},
{
"when": {"cable_connection": "down", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 90 }
},
{
"when": {"cable_connection": "up", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 270 }
},
{
"when": {"cable_connection": "north", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "west"},
"apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 270 }
},
{
"when": {"cable_connection": "down", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 }
},
{
"when": {"cable_connection": "up", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 90 }
},
{
"when": {"cable_connection": "north", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "y": 90 }
},
{
"when": {"cable_connection": "south", "facing": "east"},
"apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 270 }
}
]
}

View File

@ -5,7 +5,17 @@
"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",
}
"gui.phycon.terminal_buffer": "Buffer",
"gui.phycon.redstone_mode.high": "High",
"gui.phycon.redstone_mode.low": "Low",
"gui.phycon.redstone_mode.toggle": "Toggle",
"gui.phycon.redstone_mode.rising_edge": "Rising Edge",
"gui.phycon.redstone_mode.falling_edge": "Falling Edge",
"gui.phycon.activation_mode.automatic": "Automatic",
"gui.phycon.activation_mode.managed": "Managed"
}

View File

@ -1,6 +1,7 @@
{ {
"textures": { "textures": {
"center": "phycon:block/cable_cap_end" "center": "phycon:block/cable_cap_end",
"particle": "#center"
}, },
"elements": [ "elements": [
{ {
@ -39,4 +40,4 @@
} }
} }
] ]
} }

View File

@ -0,0 +1,33 @@
{
"parent": "block/block",
"textures": {
"cable": "phycon:block/cable_straight",
"front": "phycon:block/redstone_controller_front_off",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 3, 16],
"faces": {
"down": { "texture": "#front" },
"up": { "texture": "#back" },
"north": { "texture": "#side" },
"south": { "texture": "#side" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
},
{
"from": [6, 3, 6],
"to": [10, 6, 10],
"faces": {
"north": { "texture": "#cable" },
"south": { "texture": "#cable" },
"west": { "texture": "#cable" },
"east": { "texture": "#cable" }
}
}
]
}

View File

@ -0,0 +1,33 @@
{
"parent": "block/block",
"textures": {
"cable": "phycon:block/cable_straight",
"front": "phycon:block/redstone_controller_front_on",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back"
},
"elements": [
{
"from": [0, 0, 0],
"to": [16, 3, 16],
"faces": {
"down": { "texture": "#front" },
"up": { "texture": "#back" },
"north": { "texture": "#side" },
"south": { "texture": "#side" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
},
{
"from": [6, 3, 6],
"to": [10, 6, 10],
"faces": {
"north": { "texture": "#cable" },
"south": { "texture": "#cable" },
"west": { "texture": "#cable" },
"east": { "texture": "#cable" }
}
}
]
}

View File

@ -0,0 +1,51 @@
{
"parent": "block/block",
"textures": {
"front": "phycon:block/redstone_controller_front_off",
"back": "phycon:block/redstone_controller_back",
"side": "phycon:block/redstone_controller_back",
"cable_end": "phycon:block/cable_cap_end",
"cable_side": "phycon:block/cable_straight"
},
"elements": [
{
"_comment": "cable center",
"from": [6, 6, 3],
"to": [10, 10, 16],
"faces": {
"down": { "texture": "#cable_side" },
"up": { "texture": "#cable_side" },
"south": { "texture": "#cable_end" },
"west": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] },
"east": { "texture": "#cable_side", "rotation": 90, "uv": [6, 0, 10, 16] }
}
},
{
"_comment": "redstone controller side",
"from": [0, 0, 0],
"to": [16, 16, 3],
"faces": {
"down": { "texture": "#side" },
"up": { "texture": "#side" },
"north": { "texture": "#front" },
"south": { "texture": "#back" },
"west": { "texture": "#side" },
"east": { "texture": "#side" }
}
}
],
"_test": [
{
"_comment": "cable center",
"from": [6, 6, 3],
"to": [10, 10, 16],
"faces": {
"down": { "texture": "#cable_side", "rotation": 270, "uv": [4, 6, 10, 10] },
"up": { "texture": "#cable_side", "rotation": 90, "uv": [4, 6, 10, 10] },
"south": { "texture": "#cable_end" },
"west": { "texture": "#cable_side", "uv": [4, 6, 10, 10] },
"east": { "texture": "#cable_side", "rotation": 180, "uv": [4, 6, 10, 10] }
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB