Merge branch 'main' into cacao
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration>
|
||||||
|
<Loggers>
|
||||||
|
<Logger name="PhyNet">
|
||||||
|
<AppenderRef ref="SysOut" level="debug"/>
|
||||||
|
</Logger>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()}")
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package net.shadowfacts.phycon.util
|
||||||
|
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
interface FriendlyNameable {
|
||||||
|
val friendlyName: Text
|
||||||
|
}
|
|
@ -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()}")
|
||||||
|
}
|
|
@ -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]
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 }
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
|
|
@ -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 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 831 B |
After Width: | Height: | Size: 986 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 7.7 KiB |