From 03c9c2ab830600433d311560d291595ab98365d9 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 20 Feb 2021 14:17:44 -0500 Subject: [PATCH 01/13] Fix not being able to extract more than max stack size from interface --- .../block/netinterface/InterfaceBlockEntity.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt index 85500f6..841324e 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt @@ -14,6 +14,7 @@ import net.shadowfacts.phycon.network.component.NetworkStackProvider import net.shadowfacts.phycon.network.component.NetworkStackReceiver import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.packet.* +import kotlin.math.min /** * @author shadowfacts @@ -67,8 +68,16 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), private fun handleExtractStack(packet: ExtractStackPacket) { getInventory()?.also { inv -> - val extracted = inv.extract(packet.stack, packet.amount) - sendPacket(ItemStackPacket(extracted, ipAddress, packet.source)) + var amount = packet.amount + 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)) + } + } } } From 2981bdcb07c931f1d50504c6aafffbd5183dc734 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 20 Feb 2021 14:24:40 -0500 Subject: [PATCH 02/13] Add request specific amount dialog to terminal GUI --- .../network/block/terminal/TerminalScreen.kt | 263 ++++++++++++++++-- .../block/terminal/TerminalScreenHandler.kt | 2 +- .../phycon/textures/gui/terminal_amount.png | Bin 0 -> 7933 bytes 3 files changed, 240 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/assets/phycon/textures/gui/terminal_amount.png diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 372a17e..077eff2 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -1,11 +1,15 @@ 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.screen.ingame.HandledScreen +import net.minecraft.client.gui.widget.AbstractButtonWidget +import net.minecraft.client.gui.widget.ButtonWidget import net.minecraft.client.gui.widget.TextFieldWidget import net.minecraft.client.util.math.MatrixStack import net.minecraft.entity.player.PlayerInventory +import net.minecraft.item.ItemStack import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType import net.minecraft.text.LiteralText @@ -13,6 +17,9 @@ import net.minecraft.text.Text import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity import org.lwjgl.glfw.GLFW +import java.lang.NumberFormatException +import kotlin.math.ceil +import kotlin.math.floor /** * @author shadowfacts @@ -20,10 +27,31 @@ import org.lwjgl.glfw.GLFW // todo: translate title class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, title: Text): HandledScreen(handler, playerInv, title) { 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 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() + + private val dialogWidth = 158 + private val dialogHeight = 62 init { backgroundWidth = 252 @@ -33,6 +61,9 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, override fun init() { super.init() + children.clear() + dialogChildren.clear() + client!!.keyboard.setRepeatEvents(true) searchBox = TextFieldWidget(textRenderer, x + 138, y + 6, 80, 9, LiteralText("Search")) @@ -43,11 +74,68 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, searchBox.setSelected(true) searchBox.setEditableColor(0xffffff) children.add(searchBox) + + 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) } override fun tick() { super.tick() searchBox.tick() + amountBox.tick() } override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { @@ -58,21 +146,50 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } 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) val x = (width - backgroundWidth) / 2 val y = (height - backgroundHeight) / 2 drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight) } + @ExperimentalUnsignedTypes 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) - 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 @@ -90,37 +207,107 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, 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?) { - super.onMouseClick(slot, i, j, slotActionType) + override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) { + super.onMouseClick(slot, invSlot, clickData, type) // don't unfocus the search box on mouse click searchBox.setSelected(true) + + if (type == SlotActionType.PICKUP && clickData == 0 && slot != null && handler.isNetworkSlot(slot.id) && !slot.stack.isEmpty) { + dialogStack = slot.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 { - val oldText = searchBox.text - if (searchBox.charTyped(c, i)) { - if (searchBox.text != oldText) { - search() + if (showingAmountDialog) { + return amountBox.charTyped(c, i) + } else { + val oldText = searchBox.text + if (searchBox.charTyped(c, i)) { + if (searchBox.text != oldText) { + search() + } + return true } - return true - } - return super.charTyped(c, i) + return super.charTyped(c, i) + } } override fun keyPressed(key: Int, j: Int, k: Int): Boolean { - val oldText = searchBox.text - if (searchBox.keyPressed(key, j, k)) { - if (searchBox.text != oldText) { - search() + if (showingAmountDialog) { + return when (key) { + GLFW.GLFW_KEY_ESCAPE -> { + 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 { - super.keyPressed(key, j, k) + val oldText = searchBox.text + if (searchBox.keyPressed(key, j, k)) { + if (searchBox.text != oldText) { + search() + } + return true + } + return if (searchBox.isFocused && searchBox.isVisible && key != GLFW.GLFW_KEY_ESCAPE) { + true + } else { + super.keyPressed(key, j, k) + } } } @@ -128,4 +315,32 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, screenHandler.search(searchBox.text) } + private fun doDialogRequest() { + showingAmountDialog = false + handler.requestItem(client!!.player!!, dialogStack, amountBox.intValue) + } + + 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()) + } + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index e648f07..bd5b3ff 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -134,7 +134,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina return super.onSlotClick(slotId, clickData, actionType, player) } - private fun requestItem(player: PlayerEntity, stack: ItemStack, amount: Int) { + 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) diff --git a/src/main/resources/assets/phycon/textures/gui/terminal_amount.png b/src/main/resources/assets/phycon/textures/gui/terminal_amount.png new file mode 100644 index 0000000000000000000000000000000000000000..3999831eb3004243fc25f5b1d7e4e62acd9dddb0 GIT binary patch literal 7933 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 Date: Sat, 20 Feb 2021 15:28:55 -0500 Subject: [PATCH 03/13] Fix shift-clicking stacks from buffer not matching vanilla slot insertion order --- .../phycon/network/block/terminal/TerminalScreenHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index bd5b3ff..9b38b6b 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -154,7 +154,8 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina val result = slot.stack.copy() 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 } if (slot.stack.isEmpty) { From 3ebafc062f5e359e02cdca9a8446b815a417447a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 21 Feb 2021 11:13:49 -0500 Subject: [PATCH 04/13] Add Terminal GUI sort mode --- .../network/block/terminal/TerminalScreen.kt | 64 +++++++++++++++++- .../block/terminal/TerminalScreenHandler.kt | 17 +++-- .../net/shadowfacts/phycon/util/SortMode.kt | 27 ++++++++ .../assets/phycon/textures/gui/terminal.png | Bin 10143 -> 10735 bytes 4 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 077eff2..298e588 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -5,6 +5,7 @@ import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.DrawableHelper 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.util.math.MatrixStack @@ -16,6 +17,7 @@ import net.minecraft.text.LiteralText import net.minecraft.text.Text import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.util.SortMode import org.lwjgl.glfw.GLFW import java.lang.NumberFormatException import kotlin.math.ceil @@ -73,7 +75,13 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, searchBox.isVisible = true searchBox.setSelected(true) searchBox.setEditableColor(0xffffff) - children.add(searchBox) + addChild(searchBox) + + val sortButton = SortButton(x + 256, y + 0, handler.sortMode, { + handler.sortMode = it + handler.netItemsChanged() + }, ::renderTooltip) + addButton(sortButton) val dialogMinX = width / 2 - dialogWidth / 2 val dialogMinY = height / 2 - dialogHeight / 2 @@ -343,4 +351,58 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } } + 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) + } + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index 9b38b6b..7fe7b2c 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -14,6 +14,7 @@ import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.C2STerminalRequestItem +import net.shadowfacts.phycon.util.SortMode import java.lang.ref.WeakReference import kotlin.math.ceil import kotlin.math.min @@ -30,6 +31,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina private val fakeInv = FakeInventory(this) private var searchQuery: String = "" + var sortMode = SortMode.COUNT_HIGH_FIRST var itemsForDisplay = listOf() private set @@ -67,7 +69,7 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina } override fun netItemsChanged() { - itemsForDisplay = terminal.cachedNetItems.object2IntEntrySet().filter { + val filtered = terminal.cachedNetItems.object2IntEntrySet().filter { if (searchQuery.isBlank()) return@filter true if (searchQuery.startsWith('@')) { val unprefixed = searchQuery.drop(1) @@ -77,9 +79,16 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina } } it.key.name.string.contains(searchQuery, true) - }.sortedByDescending { - it.intValue - }.map { + } + + 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 } + } + + itemsForDisplay = sorted.map { val stack = it.key.copy() stack.count = it.intValue stack diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt new file mode 100644 index 0000000..ad4ad54 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt @@ -0,0 +1,27 @@ +package net.shadowfacts.phycon.util + +import net.minecraft.text.LiteralText +import net.minecraft.text.Text + +/** + * @author shadowfacts + */ +enum class SortMode { + COUNT_HIGH_FIRST, + COUNT_LOW_FIRST, + ALPHABETICAL; + + val prev: SortMode + get() = values()[(ordinal - 1) % values().size] + + val next: SortMode + get() = values()[(ordinal + 1) % values().size] + + val tooltip: Text + get() = when (this) { + COUNT_HIGH_FIRST -> LiteralText("Count, highest first") + COUNT_LOW_FIRST -> LiteralText("Count, lowest first") + ALPHABETICAL -> LiteralText("Name") + } + +} \ No newline at end of file diff --git a/src/main/resources/assets/phycon/textures/gui/terminal.png b/src/main/resources/assets/phycon/textures/gui/terminal.png index 7fd4d1386faa2e45945bf101536365de0bf05a18..c2d10a7cb8b6d8b3774c58e67ef5ffbdad526f6a 100644 GIT binary patch delta 10486 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 literal 10143 zcma)i18`>Fx8)b7!;WpcW83U6w(aDLZQJRf)3G|XZFOwhHhS`VGxMMK>b;qHr|Q;T z7klq@_pWnMXGH)NBoX0o;Q#;tqO_El5&!`93Bdrc(4R&JRAL4IkidGXXgDhwx)D2o z?9D7~Oo^R6988H#-7P;)y05HfX{M5K$BO@LiPQo=G~~vgDkZqPc@1P6p>UJlR@4++ z-~K^@j5r9^-RDItXZ11FmG-jO^@}``72yxUwtf zr~0ElLtLz+n_l?si^~4<)qTKT)#2*xE$#hn)Wl+l9og#bmWj!;k4{oMDi?@jz<+-BR6z2LxX z^Y)hCDR`ap!KZ7%u&izD+B`ecECv=Nt?Oiuw zbrbBHB2f$tYck~okH?*1gkC2tX?kGY7ZO15_d)=9Qo`Z`8$+m?NG z4adS1bb|xRqm}2Et-Co!%$Qm5Xx($PCa?I}BiX}Po z-42YghzaK!1#T?*Is~IE!+s-;Ki`e}Slm{9G%qUF(c4~|T zuAGO=ORPJbHU^NxEh~SWLGi6?yjWv1JtjOEC?)mQM7`OY1>%E+D_o#&9DX(Ftk!QD)>J&0J`VT6=qR%>ew5fG2~6>|)QX=ZD&M_ZIVovP4_OSuA_9yu-a*K)j{zlD?ePFRKZgh`&Ev_x`43+G>SY z=5ftrp)iJhryM4%&hWYf3Tj&q3*6u&1z3QFvOI&|n7Qyj?9MD|y1AnDC0Ym#!hF${ zVk-nD1|+u#9;Ol+b%^hO(i_jS6c8G*_Zxzjnk_GE@ei6b7DeMnTv&L@nH{Bruxc4K zDO@V8=re@^ZEQP*%KLl6S2cm*-u_gW?6@cA7Nh-y*;!9me5RB(U4OeeS>JBpTeLj zP*}xSrnw5pIXRFKIhXDqP|!@YZK(Cawi=xo_7~+c4&rdQuO+{=b#P+gmW$K$DF=HM zOEk9VU{KLC2WM;&Zj8Hff?`Qg#pR6|K;WJW-Bm7)BaI4jKjZ(cRy}IPVcXRSV6Ve6 z3zZ!o4h9fROyB&Dix%1$TI_|L4F#MAu!O_1MJ2EK{9FQq{&p?V6xPjfWeNHlD3;FN zbV$zo>fj?}O6tcMOq-lr+eA5uBi4smN1?ygI`XvoP)_t}zlKY5H6z6uez>1Nj52PC zu*z&)vJh*F5w22K6t=9bj`(}1_`R2=8DXyAU;uBg)~+FgqKVw^fdR4$_0-@8GXX8L zTQhPwXpH^)4!!4DB41Kp+y1~FHZAr8j}h+9FPOz>O$|*(spxbjzd0(?oRkQCc}Gc$ zqw6@^6cQa4Y)u_=8XifD#l00)6_{MQ=*vW_Mkxv7Bx}^Pp!fq7%0_dt6QWQ{VeyZS*rpq%L1u}~QEBZqKTyytjKj;j{hsGK3n)c6p#wQ? z=Ds-v*uKy#5ps&B5ZDB;*~;nko|*WHH}au+;xv&qqI$uE4LUk<-n@VRoY&bJNJJoe z*CLJUoXFJpkP0>0I|#jYtusVge1_&i6Wux)?3`v_i8&@*oWaMe5+xttAKDgt-)be$7N4)rM^%;3h+as9TS3 zoL&#SNTK2{`z7_KjMbT{kOx*4^@I*$=~k%utjuyyC-($MX7d42j`ura3fns!m;UNo zW=Sg~*vq{?nlnp=J7Nm3XtX14`UX)B14+7xjfAVgMd>t^2kUJLNzcdU#_AQ5+$j;X z1C<^I;-&EE!N<;-%ORpU-7dTm7Tp&uAkPWopHo?Oeglg`htd)BnaFnuq)N+0oht3LliIN z;VHksITkOi8Ix1myI_D1RvxjkPpT9hFFx70OY|r@i0O@uO7evOY<8_X2V{*nDapdr zV%WNzNU_GsMr9q5-_$sMVk*POZ+ zx6G+N%rza!UQ{PC!lQ4kf;U(mL*>S2aHg(zH@g!p?a&i{eTA4U(dgiyO6S}3NtpNVXD(~R%xw85ofu5i6p*| zCPEc6Vcm2YPa<`u8sYVBFnwfH13PC|u&I(p3Gf!GF$uXTIn!4~{leV~f>^0v(VkKC zV2yOm!zfhkt`4dsXkp~K=+Z}!&7_@3cYU`b$B^j$-6?jm8BK&k!vSE|$`upp9G#_PCgQ=;Xodw^xQ}<>x2EPti^d z6mw{;wiumZ50B?a2<~TV%66~Qt6V=5?)$6*!rHZ2bMcbP@3)#oVQd?7ayEF1Sv{6e zWBtwU+<4OQ7%=fqN6S0s4r=%pA(L%Kaa+1@TQcfg3h{^X^J#8q-y0qg=N*wl-DHTi-y7-oIJC)W>5f-cFl%>AQOXX%=;gj3L3Nn;iKEc2#A(5GJuzKdM zhHDz$DI)>DvXV!%m_)?y5E)%6p3*1^D^l~EAqB*xEn|NSAFaSx}_+Mh-5GN>Z#fY-8wkteP;2I>OHP)NLL z@(9I5d7QA4y7F<@58PYCmGRZ}wUc2=;%tMm5mt2L#35jv-f(g<)in%2Gz&*WrneWw zF5T?UmpOqer;ts6% zwsnAcllv$qSJ`jZVQ=5FLo(V_mb>g$CK1G1;YtTt*ICh z%PRt}3`#?5WU>*d>uMvzefbi7`lkmn`MBdxlhE@zWZ;Es!NpqX0zqb({o6$sPLh*G z<+YJh7S{vh_KrUwj z_Hm?A8k9$Cp#TUpC1a#%QwGH(4CsUxp^%A?t34Qn+2~?$zP9XfPp6 zUa94F=vzw~NM5jBLU=Z3$;3Y=eP3B;rIfUuKq!Ptl6)&bT ze&0?nqlEI*VxTC{yTB1^g^JEuz|!@ct%xK^P5QELEIM4yq_o~(5K{8_c(EieJz7NI zgKHt-*TX8%9o~BuFO%Gm5Ee8;1>P;3X>?2U2Qr3P^F^{mG{`3R>`6?6D8XnFjj`(& zXuxq&x^FM;SI~WUst0i{A;$`&IR5nZ)Nl+Pg5h$-dFhq|kq07+d-mRpk*QeadUvB; zMRs=x$B#pAQf>oisdkUp?=+5ALJO5s=M>#eo-`^Kt5T^jTyd&R9_)VgmtktoFrXUZ zY!}|w%fgC?zRdPB@$mfKa7w9{RH;I;xCnNURVPXtE)G08KX($}DjWlwQn9F}&F3*L zr0&Ek9)>dQixOmf5#!}Jj2CD;6jF*yNERFqA>df;+1$$Zgm7o~1+Q4(qBc9P*yO9ZcZ%hPADVqw^|(`RX4qvqlmzd3Z8OMl>O<;39Q2NBhD1l5n??DkO^ZItodzUB$SMXF z5c9Le7zANoX^4%lrn6NG7F_wNBzU@blc{W+f9cApDybO>g+EMY6%Rp9gB0r%UB)Xe zjE1NN)JIHA&jd>YJBo3VvTi)$0;O!*ACvJQh>k#IMw$2RM6{HW>QZ+2Jlh_TZ_Ibx znskXnTMSR7>x396`0;ksw~0oe*>T@Qjqg7cwf1qD>@lZ>hQE^1ISfUv7Tck%&GITT zZBVmEA_CjW7!z8-B9oO4R1#=JNwvs9*o-nD4kcIka<}#IN{$B>2}fPL&77DIQ>TWQ z$zKW@IHX>$qtLKfpqvg>HrV|0ERK%Vz63wD$9a=P_BfmtB>5~)1?o^X#tx!u+$-Dt z@8={V@=rhKa%LX73-1m|}{H;i{&6yN038ecMg3+W3$ z)mDB8tMHX-yQDQ;@iF1G>MEff@3)iijVjy#b}u$@Es8o z<>t3cNz8ZW%eO-pe*s~W6IN!9P$f{GNn-?B{ZD-L>{qpebBg1p76(cGqD~DwJ zNvR&GD#F5(jThv_jYhp|z?D?!YE6)(QBp(gk^U8`hvq6xA?1z77R0BJa=3}~TwmEv zgGY!!i`+69PK4U~)|`UUp`uZQe*Z#{fGBiTUs72 z{(Rk{1q^xCS{hiZHjJSe$QoiSGl+VB!Tx4BCvIXcXL90V$n0~6A-nYD+5t=AE6z8Y z)>>pcvAoguZ^NR)mFO6UoiyNDLEd1{%DOdwY0EqGidrM15Za=me2`@WHMxfvC)^+- z0n`eC^1$zQM|`WcrH=>5_qzr+w@PiziwahDt{j*Z^TbYrWyFML{E<3HDp{<19A+^F zJx(^2`@10%@9`S%d=zHt0%~u1MA@aAS=k-8PwTQ|Rh~4p_1db*6GWcMS4(AFs zvrLhbq#O0@Fq53`IOipyyH^FBE!kilY^s4j@nl;W4X-`80L`e(zr60Ww34n%Xe)fs z7#Sz7Qc~<5ReyB1lUF5ooeVKD^gOH=oTN`FGMjO}V|T?Jhp_p-i?$YS|Aly$FLMzc z>Eh;W4Z?A1KyVj zw%M`B7JNVH&aOgNyZ*A34V6c$m7(FzyBc$P_FLV?*>Zg)S*+J8o1sllXI&q25Qj<8 z<2*U_K7HQye6rcho=NA7!9ce^0&qpAQFt-}f~4CT5;)x75&I_bU^K79?YN_i+pU~= zU^M=$iq&2gI78-xdRqOCP1l%SoXW^}Hw&p|?3`dtR-{tvBBK;wQcEJTIP=o}r|+%{ z%_n74lZwvST`~$@%edKLpDh2OryWcTpYQ7%Av%STb(RmTzu`?Qor0q}sln6fR~N@j zDx{}jfVnSAAbt3xk~1y z9;(fOG$w`zZfa^o z2G6yxD3(X;P9v1JFNlwe`!c(>KILUh=5O$?#W`EWgn!}!z7yQZ3!_jH)LUrPK;t8q z1u?PDibP02-6>&fMyVv0g(ROF=8+QU_dk7cd?$q|)29?3wjJJxKVc?{W|59@=c?zY zHm$#;l`(Ze%FEaznMii@MfYXBH7lY~(inS3UmmV+5)IW+?9q6*eK#0{+0_@MEQeaU zgMG#1YFQ>5CCL0@!?Bv{Z)^bgRa2VOKx`hU?x;Ycvy14ftB^RC+p@CD-KRYQhG_WyqgGbW`BH4kwEF+Y@ z*bZE}avmPoeM|Jw6L2@oz)7vOk zD0e+;@S1rnnD6_Y!*_pBlB3_zb`9)m7!6YF%sL0L_h6VJ<#|7Ag)0#YU4l{aLQeYv z`CFJV9k(uq3dQ;$4Ay4$S zBR$bm^hcI!`w17SnB9zFy3RI8B_hkkCx7jeyAQ9I=bc9xOoD7TZ>{-b(*z!^ zI;fY+!|ZfvmLWjF%&sdHd_Tz{31E|$AK&+58%Vyr&-{8awog}nS1ad-_$Y#Y%8Rn? zu>!+yX%KF0)}0WFFecMY0Iy7GyDW+bMmo}wOTev@y7G}6st7qoFQ0R2IPrm)&5 zKk86c>9E9L*k1>S(ye;6{GIB`^eaya$5VFD?3V?h3UmM+T-E+M4b)_2@ECYz0{uSb znwW_(7{qFcOH;e>46ykGLiY6VLH5}w>2X57FPgSjcHif5@e2gq95$oz*Da7eMNYNa zMb9pe2%^~BDxpZq`uO}on54_A=_vCa-47d0{}F>-nQkM?8CN7O9zdOR3!|)_zxm>y z7?j4l&YBZ47CQsBl~QB^d_g=e-@7XBhN_bL7y8?6@>{oeypx)MUEHht8-ew1AX=u6 zi@j8%&(VhaYWsDix`=KHS>;Qv+nMp<((x*=%TC1$Fra>}K%3qqFKbG{{VOHVep4Pe zEj6!t;e6|Ak6SJtn48Kbixv;}`6Duln*;5nWj6pS3;gC~NH_x#aI6`cRHcQQ)pR8E zfQt|NIPJ@Ec(Pt#EEp3eFn!ZO7>3Q`l>HnWP}32OT@l84_ue2Lh<4%X{5&F%(>`AE z2y)BO+L6A&v+G>vIilG2ZdujP>gI!RamAPmj8=H339a0b)c~y;O(`)M{8EP2oKb<{ z3TDef^&s^ze)#IWX46Gc^8#Mbv&OckjGNsPOz#@xraaEf}a9 z4ZJAz%~i1%QO%&^5PiE2AMl^&slrjUjKNS+9SL&K^f;cUQ$l;9Ef-Kzoi{YzpXiuq z($1{#+f4M06`VW0MW$!O>oL;ba3VAIYl~9l-!v=~+z`2r@Go_S$bVdqCqh!KD+$oO zQ@Z4`@rci=;YdEmt7i%zCDE?{2ojC_JgEkQ!#ke_)$WEEq<~C#nfW`WFMPOZr}6q z$&VqyZNzPu{TVtM!#Yt!D^W|$96orR!pLXr_Rmlpv68+b8!I$hA=bL}1!xbxM#PkK z9{2}1cu=sO?&CU>&&aM)7l*7v1Hxka67PAP4kw`E)r`q9K()v=z|V{J6(ppM zKo4C!f2|1oi$JZE;I(luly4C;-}(Jj&U2G?t^8fPe2ichE%#cuO8Ri3xX`!mIcQ|w zI&)W;tmWajG0FZ^Meuyr*Z#I`#9ZmxSnzTvM4+QUVdA>IHA>cq*C}u7aga%IJ?2tS z%*!=Ls{-3kg}qz)$(-PoTp=A2FONXU?g4itbbvlj^V>2K31RxuDLEHw{|265VC0O{ zyFRrCR#tm6UU*urVW*zA>uALI$GwO@++5Saz0uoS$BfY~Ve z@(+fUZMo*oX2DE7UPaAmkHar!d&4H%@!t@^TM)vCUF3_1f=+J=eSLH0=}AOP;g&@N zKbKYx!}@y#iZZSJRAElSpJi7E`4Q}^*7TcG+=|y`nhqfgU|qtRN3d{3*ds-S6;AQX z@G6MIzDwJ+L&(749{lQMr=h9OvKMHfbj@5~uYciw7_(fb49Ra(6`s{_#}E;El49ql zBaijgm42M?U<>ue*^ckz=one4yWVSyZG65Md}WD$k?pGF^=cuPYhd!~ClOgGJADN` zD86Q}H`f#(1Fv!X4x*@&6X<5z>`b^RUvO<1G550!-6UIKWW*JcZ9>>s4u!wbMXFaV)YU4O5Tm?k7{$0ecY$48E zbPm*}*5qO*hf!t)%+^hi`>T$z?Y?>fOc!Z(Fe+oKRAH){dna*ZcgiwnG z19-_sdP#q}uZQI!u*K#~Yqe}Zy!Wq;U3#9}yVhDmmh-$-DGGcwd0*J+(Ma;kRq9Qg z!GLB}BR_1sDue{`%BWB8Yj$8{*X7=Nd)>&&IDek{y%6X%b^eiY@43>gF{ zjbDr;d)WmB%?t_B--peXP}#>Ei{AX?_i1i+kfPpYF&k}asaG4(b&4$!l@NexC*KPs z;w>HVSI)B;rGU8a%YXUk_j`Hr+Y3SocFbwNAoL&;~Xt|u1E=nmdm%6J5h_7`1)opxJoD( zLCkU_#>rNmX1KsDnDD6bq`au)r>=rOq=P}bzU^&~&0a9D_o%bKW0{3!>C9+RuvnKd zn6DyZEp7 zgHj6n<%mA^=<%4WPBFJXBxW`27gBVUngCSV@aeK7BNFjfM`XXQ-L&Dtcx#Ag{99_# zptGl*zguoiYQ9Em@^AwIlUS=C*wEl@LiuK^n?sH3Twrfg-6{m|p?Q>y}PBr7TQX+OuaXq(@s`Qjj@=>z~UlKr!Rxfcq$ zejbEzmX;TX*@YuOr2z+HSZ@RXAj71^gjL*E&ewJAR)Amqryct<+{2r;W5jM);x)pT zC8Uz&*9BA*6-8hpti8N_Cbj+8Fy_2FG)ygaa!`e0GA?~{lXvcsRXblY55{2m+^j9J_;sLVD6rt zxbHB|x>&`tH1k<;M6D(Fju~|wr#CBEid6oUrxN_totL}LJcSuf=lXTHM8({vU5xtr z`j`~IWv|Lm;)*=OoZtML&J-N~L$J}ARk?TdvmLTLXm_$0Ch;Xm=;F9izo zb5U+4UV#67TLa3lHoyV-|6WBkqeGgfSN=2O`L~YygK^ekTWv6n$jb^&^J8vVZ~V>w zxA#Bni5Xr2y^jl-Nyd|?8R680$>HJRn5ZTOWbxcM2XYN4NA7$QV*TF`E~`AK{H^`K zjF;SvCzrT6z`?))umEC!KLGGw1BV^~|9?7z@04ml23&$QQHcHYV(^B;?i<8&8l0>_ zf&F*0;=e2_`~z69Jo~|3dL1NQz%Nh}2e=13-~#M3p-p6_zad>UH}@w+3=P1=8$5R3 zg7!aAF|hwTQEeOkEvj}vE~pV4;@?7M@_zW4Vxh>-vh8~d_`PNZ@O%eM?C$29L(saM qLvJc(m&+-uf From f9befe9549229c59b5d3e8c48bffefd363082444 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 21 Feb 2021 12:00:32 -0500 Subject: [PATCH 05/13] Calculate terminal displayed items on the server Avoids sending giant S2C packets containing all items in the network when the network contains a great variety of different items --- .../phycon/PhysicalConnectivity.kt | 2 + .../phycon/PhysicalConnectivityClient.kt | 11 +++ .../block/terminal/TerminalBlockEntity.kt | 84 +++++++++---------- .../network/block/terminal/TerminalScreen.kt | 22 +++-- .../block/terminal/TerminalScreenHandler.kt | 48 ++++++++--- .../C2STerminalUpdateDisplayedItems.kt | 50 +++++++++++ .../phycon/networking/ClientReceiver.kt | 11 +++ .../S2CTerminalUpdateDisplayedItems.kt | 57 +++++++++++++ .../net/shadowfacts/phycon/util/SortMode.kt | 2 +- 9 files changed, 223 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 1a1459b..19ba8c5 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -7,6 +7,7 @@ import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.C2STerminalRequestItem +import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.networking.ServerReceiver /** @@ -23,6 +24,7 @@ object PhysicalConnectivity: ModInitializer { PhyScreens.init() registerGlobalReceiver(C2STerminalRequestItem) + registerGlobalReceiver(C2STerminalUpdateDisplayedItems) } private fun registerGlobalReceiver(receiver: ServerReceiver) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt index 4575fbb..c32c7b4 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt @@ -2,19 +2,30 @@ package net.shadowfacts.phycon import net.fabricmc.api.ClientModInitializer 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.minecraft.client.render.RenderLayer import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.network.block.terminal.TerminalScreen +import net.shadowfacts.phycon.networking.ClientReceiver +import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems /** * @author shadowfacts */ object PhysicalConnectivityClient: ClientModInitializer { + override fun onInitializeClient() { BlockRenderLayerMap.INSTANCE.putBlock(PhyBlocks.CABLE, RenderLayer.getTranslucent()) ScreenRegistry.register(PhyScreens.TERMINAL_SCREEN_HANDLER, ::TerminalScreen) + + registerGlobalReceiver(S2CTerminalUpdateDisplayedItems) } + + private fun registerGlobalReceiver(receiver: ClientReceiver) { + ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver) + } + } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt index eab52cf..3d71587 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt @@ -56,7 +56,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private var observers = 0 val cachedNetItems = ItemStackCollections.intMap() - var cachedSortedNetItems = listOf() +// var cachedSortedNetItems = listOf() var netItemObserver: WeakReference? = null @@ -86,14 +86,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private fun handleReadInventory(packet: ReadInventoryPacket) { inventoryCache[packet.source] = packet.inventory - updateNetItems() - sync() + updateAndSync() } private fun handleDeviceRemoved(packet: DeviceRemovedPacket) { inventoryCache.remove(packet.source) - updateNetItems() - sync() + updateAndSync() } private fun handleStackLocation(packet: StackLocationPacket) { @@ -113,12 +111,17 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), // 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 - updateNetItems() - sync() + updateAndSync() return remaining } + private fun updateAndSync() { + updateNetItems() + sync() + netItemObserver?.get()?.netItemsChanged() + } + private fun updateNetItems() { cachedNetItems.clear() for (inventory in inventoryCache.values) { @@ -127,12 +130,12 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), 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 - } +// // 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() { @@ -175,21 +178,15 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), finishTimedOutPendingInsertions() } - if (observers > 0) { - if (world!!.isClient) { - println(cachedNetItems) - } else { - updateNetItems() - sync() - } + if (observers > 0 && !world!!.isClient) { + updateAndSync() } } } fun onActivate(player: PlayerEntity) { if (!world!!.isClient) { - updateNetItems() - sync() + updateAndSync() inventoryCache.clear() sendPacket(RequestInventoryPacket(ipAddress)) @@ -238,8 +235,7 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), internalBuffer.setStack(insertion.bufferSlot, remaining) // as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets - updateNetItems() - sync() + updateAndSync() return remaining } @@ -263,31 +259,31 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), override fun toClientTag(tag: CompoundTag): CompoundTag { 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) - } +// 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 } override fun fromClientTag(tag: CompoundTag) { 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 - } +// 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 { diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 298e588..6dd5ee6 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -17,6 +17,7 @@ import net.minecraft.text.LiteralText import net.minecraft.text.Text import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode import org.lwjgl.glfw.GLFW import java.lang.NumberFormatException @@ -34,6 +35,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } private lateinit var searchBox: TextFieldWidget + private lateinit var sortButton: SortButton private lateinit var amountBox: TextFieldWidget private var dialogStack = ItemStack.EMPTY private var showingAmountDialog = false @@ -77,9 +79,8 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, searchBox.setEditableColor(0xffffff) addChild(searchBox) - val sortButton = SortButton(x + 256, y + 0, handler.sortMode, { - handler.sortMode = it - handler.netItemsChanged() + sortButton = SortButton(x + 256, y + 0, handler.sortMode, { + requestUpdatedItems() }, ::renderTooltip) addButton(sortButton) @@ -138,6 +139,13 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, 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() { @@ -279,7 +287,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, val oldText = searchBox.text if (searchBox.charTyped(c, i)) { if (searchBox.text != oldText) { - search() + requestUpdatedItems() } return true } @@ -307,7 +315,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, val oldText = searchBox.text if (searchBox.keyPressed(key, j, k)) { if (searchBox.text != oldText) { - search() + requestUpdatedItems() } return true } @@ -319,10 +327,6 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, } } - private fun search() { - screenHandler.search(searchBox.text) - } - private fun doDialogRequest() { showingAmountDialog = false handler.requestItem(client!!.player!!, dialogStack, amountBox.intValue) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index 7fe7b2c..33756e7 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -8,12 +8,14 @@ import net.minecraft.entity.player.PlayerInventory import net.minecraft.item.ItemStack import net.minecraft.network.PacketByteBuf import net.minecraft.screen.ScreenHandler +import net.minecraft.server.network.ServerPlayerEntity import net.minecraft.util.Identifier import net.minecraft.util.registry.Registry import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.C2STerminalRequestItem +import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode import java.lang.ref.WeakReference import kotlin.math.ceil @@ -22,7 +24,7 @@ import kotlin.math.min /** * @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 { companion object { @@ -32,6 +34,18 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina private val fakeInv = FakeInventory(this) private var searchQuery: String = "" var sortMode = SortMode.COUNT_HIGH_FIRST + private set + private var itemEntries = listOf() + set(value) { + field = value + if (terminal.world!!.isClient) { + itemsForDisplay = value.map { + val stack = it.stack.copy() + stack.count = it.amount + stack + } + } + } var itemsForDisplay = listOf() private set @@ -39,8 +53,10 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina this(syncId, playerInv, PhyBlocks.TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!) init { - terminal.netItemObserver = WeakReference(this) - netItemsChanged() + if (!terminal.world!!.isClient) { + terminal.netItemObserver = WeakReference(this) + netItemsChanged() + } // network for (y in 0 until 6) { @@ -69,6 +85,9 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina } override fun netItemsChanged() { + val player = playerInv.player + assert(player is ServerPlayerEntity) + val filtered = terminal.cachedNetItems.object2IntEntrySet().filter { if (searchQuery.isBlank()) return@filter true if (searchQuery.startsWith('@')) { @@ -88,18 +107,25 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string } } - itemsForDisplay = sorted.map { - val stack = it.key.copy() - stack.count = it.intValue - stack - } + itemEntries = sorted.map { Entry(it.key, it.intValue) } + + (player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, sortMode)) } - fun search(query: String) { - searchQuery = query + fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, sortMode: SortMode) { + this.searchQuery = query + this.sortMode = sortMode netItemsChanged() } + fun receivedUpdatedItemsFromServer(entries: List, query: String, sortMode: SortMode) { + assert(playerInv.player.world.isClient) + + this.searchQuery = query + this.sortMode = sortMode + itemEntries = entries + } + override fun canUse(player: PlayerEntity): Boolean { return true } @@ -214,4 +240,6 @@ class TerminalScreenHandler(syncId: Int, playerInv: PlayerInventory, val termina fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart fun isPlayerSlot(id: Int) = id >= playerSlotsStart + + data class Entry(val stack: ItemStack, val amount: Int) } diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt new file mode 100644 index 0000000..eed71b9 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalUpdateDisplayedItems.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt new file mode 100644 index 0000000..9fa0cf6 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/ClientReceiver.kt @@ -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 +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt new file mode 100644 index 0000000..db0efe9 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/S2CTerminalUpdateDisplayedItems.kt @@ -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, 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(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) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt index ad4ad54..7b61025 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt @@ -12,7 +12,7 @@ enum class SortMode { ALPHABETICAL; val prev: SortMode - get() = values()[(ordinal - 1) % values().size] + get() = values()[(ordinal - 1 + values().size) % values().size] val next: SortMode get() = values()[(ordinal + 1) % values().size] From 7abdb69b8779c63aadd0473a788baf29446e929a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 21 Feb 2021 12:13:06 -0500 Subject: [PATCH 06/13] Move terminal stack requesting logic to TerminalScreen --- .../block/terminal/TerminalBlockEntity.kt | 27 ----------------- .../network/block/terminal/TerminalScreen.kt | 30 +++++++++++++++---- .../block/terminal/TerminalScreenHandler.kt | 30 +------------------ 3 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt index 3d71587..e17af71 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalBlockEntity.kt @@ -56,7 +56,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), private var observers = 0 val cachedNetItems = ItemStackCollections.intMap() -// var cachedSortedNetItems = listOf() var netItemObserver: WeakReference? = null @@ -130,12 +129,6 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), 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() { @@ -259,31 +252,11 @@ class TerminalBlockEntity: DeviceBlockEntity(PhyBlockEntities.TERMINAL), override fun toClientTag(tag: CompoundTag): CompoundTag { 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 } override fun fromClientTag(tag: CompoundTag) { 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 { diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 6dd5ee6..e245942 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -17,12 +17,14 @@ import net.minecraft.text.LiteralText import net.minecraft.text.Text import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.networking.C2STerminalRequestItem import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode import org.lwjgl.glfw.GLFW import java.lang.NumberFormatException import kotlin.math.ceil import kotlin.math.floor +import kotlin.math.min /** * @author shadowfacts @@ -229,10 +231,22 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, // don't unfocus the search box on mouse click searchBox.setSelected(true) - if (type == SlotActionType.PICKUP && clickData == 0 && slot != null && handler.isNetworkSlot(slot.id) && !slot.stack.isEmpty) { - dialogStack = slot.stack - showingAmountDialog = true - searchBox.setSelected(false) + 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) + } + } } } @@ -329,7 +343,13 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, private fun doDialogRequest() { showingAmountDialog = false - handler.requestItem(client!!.player!!, dialogStack, amountBox.intValue) + 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 diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt index 33756e7..e361c85 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreenHandler.kt @@ -137,28 +137,7 @@ class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val ter } override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity): ItemStack { - if (isNetworkSlot(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 + if (isBufferSlot(slotId)) { // todo: why does this think it's quick_craft sometimes? if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !player.inventory.cursorStack.isEmpty) { // placing cursor stack into buffer @@ -169,13 +148,6 @@ class TerminalScreenHandler(syncId: Int, val playerInv: PlayerInventory, val ter return super.onSlotClick(slotId, clickData, actionType, player) } - 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 { if (isNetworkSlot(slotId)) { return ItemStack.EMPTY; From 700817919ab4a2537c838b6ea390f3ca32f9ee5e Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 21 Feb 2021 15:35:20 -0500 Subject: [PATCH 07/13] Move face device block logic to separate base class The extractor currently isn't considered a "face block" because its network connection is always in the opposite direction of its facing, unlike the interace. --- .../phycon/network/FaceDeviceBlock.kt | 102 ++++++++++++++++++ .../block/netinterface/InterfaceBlock.kt | 97 ++--------------- .../netinterface/InterfaceBlockEntity.kt | 3 +- .../phycon/blockstates/network_interface.json | 1 - 4 files changed, 115 insertions(+), 88 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt new file mode 100644 index 0000000..e81c754 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt @@ -0,0 +1,102 @@ +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(settings: Settings): DeviceBlock(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 + private val centerShapes: Map 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, 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 { + 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) { + 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.playerFacing.opposite + val cableConnection = getCableConnectedSide(context.world, context.blockPos) ?: facing.opposite + return defaultState + .with(FACING, facing) + .with(CABLE_CONNECTION, cableConnection) + } + + protected fun getCableConnectedSide(world: World, pos: BlockPos): Direction? { + for (side in Direction.values()) { + val offsetPos = pos.offset(side) + if (world.getBlockState(offsetPos) is NetworkComponentBlock) { + 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]) + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt index cab6826..0a091ff 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlock.kt @@ -4,103 +4,39 @@ import alexiil.mc.lib.attributes.AttributeList import alexiil.mc.lib.attributes.AttributeProvider import net.minecraft.block.* import net.minecraft.entity.LivingEntity -import net.minecraft.item.ItemPlacementContext 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.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.PhysicalConnectivity import net.shadowfacts.phycon.api.NetworkComponentBlock -import net.shadowfacts.phycon.api.Interface -import net.shadowfacts.phycon.network.DeviceBlock -import net.shadowfacts.phycon.network.block.cable.CableBlock -import java.util.* +import net.shadowfacts.phycon.network.FaceDeviceBlock /** * @author shadowfacts */ -class InterfaceBlock: DeviceBlock(Settings.of(Material.METAL)), +class InterfaceBlock: FaceDeviceBlock(Settings.of(Material.METAL)), NetworkComponentBlock, AttributeProvider { companion object { 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.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.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, 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 { - val set = EnumSet.of(state[CABLE_CONNECTION]) - set.remove(state[FACING]) - return set - } - - 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) { - super.appendProperties(builder) - builder.add(FACING) - builder.add(CABLE_CONNECTION) - } + override val faceThickness = 2.0 + override val faceShapes = mapOf( + 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) + ) 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) { if (!world.isClient) { getBlockEntity(world, pos)!!.updateInventory() @@ -113,19 +49,8 @@ class InterfaceBlock: DeviceBlock(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<*>) { to.offer(getBlockEntity(world, pos)) } - override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape { - return getShape(state[FACING], state[CABLE_CONNECTION]) - } - } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt index 841324e..9cb58ae 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netinterface/InterfaceBlockEntity.kt @@ -9,6 +9,7 @@ import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.init.PhyBlockEntities 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.NetworkStackProvider import net.shadowfacts.phycon.network.component.NetworkStackReceiver @@ -25,7 +26,7 @@ class InterfaceBlockEntity: DeviceBlockEntity(PhyBlockEntities.INTERFACE), NetworkStackReceiver { private val facing: Direction - get() = cachedState[InterfaceBlock.FACING] + get() = cachedState[FaceDeviceBlock.FACING] // todo: should this be a weak ref? private var inventory: GroupedItemInv? = null diff --git a/src/main/resources/assets/phycon/blockstates/network_interface.json b/src/main/resources/assets/phycon/blockstates/network_interface.json index b1166ed..6d851e4 100644 --- a/src/main/resources/assets/phycon/blockstates/network_interface.json +++ b/src/main/resources/assets/phycon/blockstates/network_interface.json @@ -140,7 +140,6 @@ }, { - "when": {"cable_connection": "down", "facing": "east"}, "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 } }, From d3a0f279da7d5b757ab02b255683fded4aaa0073 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 23 Feb 2021 22:05:05 -0500 Subject: [PATCH 08/13] Add Redstone Controller --- .../phycon/api/util/IPAddress.java | 17 ++ .../phycon/PhysicalConnectivity.kt | 6 +- .../phycon/init/PhyBlockEntities.kt | 4 + .../net/shadowfacts/phycon/init/PhyBlocks.kt | 3 + .../net/shadowfacts/phycon/init/PhyItems.kt | 3 + .../shadowfacts/phycon/item/ConsoleItem.kt | 12 +- .../phycon/network/DeviceBlockEntity.kt | 14 +- .../phycon/network/FaceDeviceBlock.kt | 17 +- .../block/extractor/ExtractorBlockEntity.kt | 61 +++++-- .../block/redstone/RedstoneControllerBlock.kt | 83 +++++++++ .../redstone/RedstoneControllerBlockEntity.kt | 75 +++++++++ .../network/block/terminal/TerminalScreen.kt | 2 + .../network/component/ActivationController.kt | 60 +++++++ .../network/packet/RemoteActivationPacket.kt | 14 ++ .../networking/C2SConfigureActivationMode.kt | 49 ++++++ .../C2SConfigureRedstoneController.kt | 59 +++++++ .../phycon/networking/ServerReceiver.kt | 9 +- .../screen/ActivatableDeviceConsoleScreen.kt | 75 +++++++++ .../phycon/screen/DeviceConsoleScreen.kt | 14 +- .../screen/RedstoneControllerConsoleScreen.kt | 111 ++++++++++++ .../shadowfacts/phycon/util/ActivationMode.kt | 9 + .../shadowfacts/phycon/util/RedstoneMode.kt | 24 +++ .../shadowfacts/phycon/util/RotatableEnum.kt | 13 ++ .../net/shadowfacts/phycon/util/SortMode.kt | 10 +- .../blockstates/redstone_controller.json | 159 ++++++++++++++++++ .../resources/assets/phycon/lang/en_us.json | 3 +- .../assets/phycon/textures/gui/console.png | Bin 0 -> 3333 bytes 27 files changed, 866 insertions(+), 40 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt create mode 100644 src/main/resources/assets/phycon/blockstates/redstone_controller.json create mode 100644 src/main/resources/assets/phycon/textures/gui/console.png diff --git a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java index 737b239..e15ed87 100644 --- a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java +++ b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java @@ -1,8 +1,11 @@ package net.shadowfacts.phycon.api.util; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * @author shadowfacts @@ -25,6 +28,20 @@ public final class IPAddress { return random(ipAddressRandom); } + private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"); + @Nullable + public static IPAddress parse(String s) { + Matcher matcher = IP_PATTERN.matcher(s); + if (!matcher.matches()) { + return null; + } + int a = Integer.parseInt(matcher.group(1)); + int b = Integer.parseInt(matcher.group(2)); + int c = Integer.parseInt(matcher.group(3)); + int d = Integer.parseInt(matcher.group(4)); + return new IPAddress(a, b, c, d); + } + public static final IPAddress BROADCAST = new IPAddress(0xff_ff_ff_ff); public final int address; diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 19ba8c5..961dbee 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -6,9 +6,7 @@ import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyScreens -import net.shadowfacts.phycon.networking.C2STerminalRequestItem -import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems -import net.shadowfacts.phycon.networking.ServerReceiver +import net.shadowfacts.phycon.networking.* /** * @author shadowfacts @@ -25,6 +23,8 @@ object PhysicalConnectivity: ModInitializer { registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalUpdateDisplayedItems) + registerGlobalReceiver(C2SConfigureActivationMode) + registerGlobalReceiver(C2SConfigureRedstoneController) } private fun registerGlobalReceiver(receiver: ServerReceiver) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt index 8f9bd00..a092601 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt @@ -13,6 +13,8 @@ import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlockEntity import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlockEntity +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlockEntity import net.shadowfacts.phycon.network.block.test.DestBlock @@ -30,6 +32,7 @@ object PhyBlockEntities { val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH) val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR) val MINER = create(::MinerBlockEntity, PhyBlocks.MINER) + val REDSTONE_CONTROLLER = create(::RedstoneControllerBlockEntity, PhyBlocks.REDSTONE_CONTROLLER) val SOURCE = create(::SourceBlockEntity, PhyBlocks.SOURCE) val DEST = create(::DestBlockEntity, PhyBlocks.DEST) @@ -44,6 +47,7 @@ object PhyBlockEntities { register(SwitchBlock.ID, SWITCH) register(ExtractorBlock.ID, EXTRACTOR) register(MinerBlock.ID, MINER) + register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) register(SourceBlock.ID, SOURCE) register(DestBlock.ID, DEST) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt index 9a90e4f..caabd63 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt @@ -8,6 +8,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock import net.shadowfacts.phycon.network.block.miner.MinerBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.test.DestBlock import net.shadowfacts.phycon.network.block.test.SourceBlock @@ -23,6 +24,7 @@ object PhyBlocks { val CABLE = CableBlock() val EXTRACTOR = ExtractorBlock() val MINER = MinerBlock() + val REDSTONE_CONTROLLER = RedstoneControllerBlock() val SOURCE = SourceBlock() val DEST = DestBlock() @@ -34,6 +36,7 @@ object PhyBlocks { register(CableBlock.ID, CABLE) register(ExtractorBlock.ID, EXTRACTOR) register(MinerBlock.ID, MINER) + register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) register(SourceBlock.ID, SOURCE) register(DestBlock.ID, DEST) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt index a25f779..ff75216 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt @@ -11,6 +11,7 @@ import net.shadowfacts.phycon.network.block.extractor.ExtractorBlock import net.shadowfacts.phycon.network.block.miner.MinerBlock import net.shadowfacts.phycon.network.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.network.block.netswitch.SwitchBlock +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlock import net.shadowfacts.phycon.network.block.terminal.TerminalBlock import net.shadowfacts.phycon.network.block.test.DestBlock import net.shadowfacts.phycon.network.block.test.SourceBlock @@ -26,6 +27,7 @@ object PhyItems { val CABLE = BlockItem(PhyBlocks.CABLE, Item.Settings()) val EXTRACTOR = BlockItem(PhyBlocks.EXTRACTOR, Item.Settings()) val MINER = BlockItem(PhyBlocks.MINER, Item.Settings()) + val REDSTONE_CONTROLLER = BlockItem(PhyBlocks.REDSTONE_CONTROLLER, Item.Settings()) val SOURCE = BlockItem(PhyBlocks.SOURCE, Item.Settings()) val DEST = BlockItem(PhyBlocks.DEST , Item.Settings()) @@ -40,6 +42,7 @@ object PhyItems { register(CableBlock.ID, CABLE) register(ExtractorBlock.ID, EXTRACTOR) register(MinerBlock.ID, MINER) + register(RedstoneControllerBlock.ID, REDSTONE_CONTROLLER) register(SourceBlock.ID, SOURCE) register(DestBlock.ID, DEST) diff --git a/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt b/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt index a6570af..e3657e4 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/item/ConsoleItem.kt @@ -8,7 +8,11 @@ import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.network.DeviceBlock import net.shadowfacts.phycon.network.DeviceBlockEntity +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity +import net.shadowfacts.phycon.network.component.ActivationController +import net.shadowfacts.phycon.screen.ActivatableDeviceConsoleScreen import net.shadowfacts.phycon.screen.DeviceConsoleScreen +import net.shadowfacts.phycon.screen.RedstoneControllerConsoleScreen /** * @author shadowfacts @@ -36,8 +40,12 @@ class ConsoleItem: Item(Settings()) { } private fun openScreen(be: DeviceBlockEntity) { - val screen = DeviceConsoleScreen(be) + val screen = when (be) { + is ActivationController.ActivatableDevice -> ActivatableDeviceConsoleScreen(be) + is RedstoneControllerBlockEntity -> RedstoneControllerConsoleScreen(be) + else -> DeviceConsoleScreen(be) + } MinecraftClient.getInstance().openScreen(screen) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt index 42a68b9..4ce619a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt @@ -18,8 +18,7 @@ import net.shadowfacts.phycon.api.util.MACAddress import net.shadowfacts.phycon.network.frame.ARPQueryFrame import net.shadowfacts.phycon.network.frame.ARPResponseFrame import net.shadowfacts.phycon.network.frame.BasePacketFrame -import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket -import java.lang.RuntimeException +import net.shadowfacts.phycon.network.packet.* import java.util.* /** @@ -52,6 +51,15 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), abstract override fun handle(packet: Packet) + private fun doHandlePacket(packet: Packet) { + when (packet) { + is DeviceRemovedPacket -> { + arpTable.remove(packet.source) + } + } + handle(packet) + } + override fun send(frame: EthernetFrame) { findDestination()?.receive(frame) } @@ -64,7 +72,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), is PacketFrame -> { if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) { println("$this ($ipAddress) received packet: ${frame.packet}") - handle(frame.packet) + doHandlePacket(frame.packet) } } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt index e81c754..b787008 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/FaceDeviceBlock.kt @@ -71,18 +71,23 @@ abstract class FaceDeviceBlock(settings: Settings): Device builder.add(CABLE_CONNECTION) } - override fun getPlacementState(context: ItemPlacementContext): BlockState? { - val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite - val cableConnection = getCableConnectedSide(context.world, context.blockPos) ?: facing.opposite + override fun getPlacementState(context: ItemPlacementContext): BlockState { + val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite + val cableConnection = getCableConnectedSide(context.world, context.blockPos, facing) ?: facing.opposite return defaultState .with(FACING, facing) .with(CABLE_CONNECTION, cableConnection) } - protected fun getCableConnectedSide(world: World, pos: BlockPos): Direction? { + protected fun getCableConnectedSide(world: World, pos: BlockPos, facing: Direction): Direction? { for (side in Direction.values()) { + if (side == facing) { + continue + } val offsetPos = pos.offset(side) - if (world.getBlockState(offsetPos) is NetworkComponentBlock) { + val state = world.getBlockState(offsetPos) + val block = state.block + if (block is NetworkComponentBlock && block.getNetworkConnectedSides(state, world, offsetPos).contains(side.opposite)) { return side } } @@ -99,4 +104,4 @@ abstract class FaceDeviceBlock(settings: Settings): Device override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape { return getShape(state[FACING], state[CABLE_CONNECTION]) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt index 90f0de9..59fe56f 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt @@ -1,28 +1,38 @@ package net.shadowfacts.phycon.network.block.extractor import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.item.FixedItemInv import alexiil.mc.lib.attributes.item.GroupedItemInv import alexiil.mc.lib.attributes.item.ItemAttributes +import net.minecraft.block.BlockState +import net.minecraft.nbt.CompoundTag import net.minecraft.util.math.Direction import net.shadowfacts.phycon.api.packet.Packet import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity +import net.shadowfacts.phycon.network.component.ActivationController import net.shadowfacts.phycon.network.packet.CapacityPacket import net.shadowfacts.phycon.network.packet.CheckCapacityPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket +import net.shadowfacts.phycon.network.packet.RemoteActivationPacket +import net.shadowfacts.phycon.util.ActivationMode /** * @author shadowfacts */ -class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { +class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), + ActivationController.ActivatableDevice { + + companion object { + val SLEEP_TIME = 40L + } private val facing: Direction get() = cachedState[ExtractorBlock.FACING] private var inventory: GroupedItemInv? = null private var shouldExtract = false + override val controller = ActivationController(SLEEP_TIME, this) fun updateInventory() { val offsetPos = pos.offset(facing) @@ -36,7 +46,14 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { } override fun handle(packet: Packet) { - if (packet is CapacityPacket && shouldExtract) { + when (packet) { + is CapacityPacket -> handleCapacity(packet) + is RemoteActivationPacket -> controller.handleRemoteActivation(packet) + } + } + + private fun handleCapacity(packet: CapacityPacket) { + if (shouldExtract && packet.capacity > 0) { getInventory()?.also { inv -> shouldExtract = false val extracted = inv.extract(packet.stack, packet.capacity) @@ -48,12 +65,36 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR) { override fun tick() { super.tick() - if (!world!!.isClient && counter % 40 == 0L) { - getInventory()?.also { - val stack = it.storedStacks.firstOrNull() ?: return - shouldExtract = true - sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) - } + if (!world!!.isClient) { + controller.tick() } } -} \ No newline at end of file + + override fun activate(): Boolean { + val inventory = getInventory() ?: return false + val stack = inventory.storedStacks.firstOrNull() ?: return false + shouldExtract = true + sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) + return true + } + + override fun toTag(tag: CompoundTag): CompoundTag { + tag.putString("ActivationMode", controller.activationMode.name) + return super.toTag(tag) + } + + override fun fromTag(state: BlockState, tag: CompoundTag) { + super.fromTag(state, tag) + controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) + } + + override fun toClientTag(tag: CompoundTag): CompoundTag { + tag.putString("ActivationMode", controller.activationMode.name) + return super.toClientTag(tag) + } + + override fun fromClientTag(tag: CompoundTag) { + super.fromClientTag(tag) + controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt new file mode 100644 index 0000000..5a9cb6e --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt @@ -0,0 +1,83 @@ +package net.shadowfacts.phycon.network.block.redstone + +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.block.Material +import net.minecraft.item.ItemPlacementContext +import net.minecraft.server.world.ServerWorld +import net.minecraft.state.StateManager +import net.minecraft.state.property.Properties +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.world.BlockView +import net.minecraft.world.World +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.network.FaceDeviceBlock +import java.util.* + +/** + * @author shadowfacts + */ +class RedstoneControllerBlock: FaceDeviceBlock(Settings.of(Material.METAL)) { + + companion object { + val ID = Identifier(PhysicalConnectivity.MODID, "redstone_controller") + val LIT = Properties.LIT + } + + // todo: don't just copy this from the Interface block + override val faceThickness = 2.0 + override val faceShapes = mapOf( + Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 2.0, 16.0), + Direction.UP to createCuboidShape(0.0, 14.0, 0.0, 16.0, 16.0, 16.0), + Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 2.0), + Direction.SOUTH to createCuboidShape(0.0, 0.0, 14.0, 16.0, 16.0, 16.0), + Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 2.0, 16.0, 16.0), + Direction.EAST to createCuboidShape(14.0, 0.0, 0.0, 16.0, 16.0, 16.0) + ) + + override fun appendProperties(builder: StateManager.Builder) { + super.appendProperties(builder) + builder.add(LIT) + } + + override fun createBlockEntity(world: BlockView) = RedstoneControllerBlockEntity() + + override fun getPlacementState(context: ItemPlacementContext): BlockState { + val state = super.getPlacementState(context) + return state.with(LIT, isPowered(context.world, context.blockPos, state[FACING])) + } + + // todo: does this need to be separate from getStateForNeighborUpdate? + override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) { + if (!world.isClient) { + val wasLit = state[LIT] + val isLit = isPowered(world, pos, state[FACING]) + if (wasLit != isLit) { + if (wasLit) { + world.blockTickScheduler.schedule(pos, this, 4) + } else { + toggleLit(state, world, pos) + } + } + } + } + + override fun scheduledTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) { + if (state[LIT] && !isPowered(world, pos, state[FACING])) { + toggleLit(state, world, pos) + } + } + + private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean { + val offset = pos.offset(facing) + return world.getEmittedRedstonePower(offset, facing) > 0 + } + + private fun toggleLit(state: BlockState, world: World, pos: BlockPos) { + world.setBlockState(pos, state.cycle(LIT), 2) + getBlockEntity(world, pos)!!.redstoneStateChanged() + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt new file mode 100644 index 0000000..d9bbfbb --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlockEntity.kt @@ -0,0 +1,75 @@ +package net.shadowfacts.phycon.network.block.redstone + +import net.minecraft.block.BlockState +import net.minecraft.nbt.CompoundTag +import net.shadowfacts.phycon.api.packet.Packet +import net.shadowfacts.phycon.api.util.IPAddress +import net.shadowfacts.phycon.init.PhyBlockEntities +import net.shadowfacts.phycon.network.DeviceBlockEntity +import net.shadowfacts.phycon.network.packet.RemoteActivationPacket +import net.shadowfacts.phycon.util.RedstoneMode + +/** + * @author shadowfacts + */ +class RedstoneControllerBlockEntity: DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER) { + + var managedDevices = Array(5) { null } + var redstoneMode = RedstoneMode.HIGH + + private var redstonePowered = false + + override fun handle(packet: Packet) { + } + + fun redstoneStateChanged() { + val oldPowered = redstonePowered + redstonePowered = cachedState[RedstoneControllerBlock.LIT] + + val mode: RemoteActivationPacket.Mode? = when (redstoneMode) { + RedstoneMode.TOGGLE -> if (oldPowered != redstonePowered) RemoteActivationPacket.Mode.SINGLE else null + RedstoneMode.RISING_EDGE -> if (!oldPowered && redstonePowered) RemoteActivationPacket.Mode.SINGLE else null + RedstoneMode.FALLING_EDGE -> if (oldPowered && !redstonePowered) RemoteActivationPacket.Mode.SINGLE else null + RedstoneMode.HIGH -> if (redstonePowered) RemoteActivationPacket.Mode.ENABLE else RemoteActivationPacket.Mode.DISABLE + RedstoneMode.LOW -> if (redstonePowered) RemoteActivationPacket.Mode.DISABLE else RemoteActivationPacket.Mode.ENABLE + } + + if (mode != null) { + sendActivatePacket(mode) + } + } + + private fun sendActivatePacket(mode: RemoteActivationPacket.Mode) { + for (ip in managedDevices) { + if (ip == null) continue + sendPacket(RemoteActivationPacket(mode, ipAddress, ip)) + } + } + + override fun toTag(tag: CompoundTag): CompoundTag { + tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address }) + tag.putString("RedstoneMode", redstoneMode.name) + return super.toTag(tag) + } + + override fun fromTag(state: BlockState, tag: CompoundTag) { + super.fromTag(state, tag) + val addresses = tag.getIntArray("ManagedDevices") + managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray() + redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode")) + } + + override fun toClientTag(tag: CompoundTag): CompoundTag { + tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address }) + tag.putString("RedstoneMode", redstoneMode.name) + return super.toClientTag(tag) + } + + override fun fromClientTag(tag: CompoundTag) { + super.fromClientTag(tag) + val addresses = tag.getIntArray("ManagedDevices") + managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray() + redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode")) + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index e245942..376e194 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -20,6 +20,8 @@ import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.networking.C2STerminalRequestItem import net.shadowfacts.phycon.networking.C2STerminalUpdateDisplayedItems import net.shadowfacts.phycon.util.SortMode +import net.shadowfacts.phycon.util.next +import net.shadowfacts.phycon.util.prev import org.lwjgl.glfw.GLFW import java.lang.NumberFormatException import kotlin.math.ceil diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt new file mode 100644 index 0000000..5ec6b2c --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/ActivationController.kt @@ -0,0 +1,60 @@ +package net.shadowfacts.phycon.network.component + +import net.minecraft.block.entity.BlockEntity +import net.shadowfacts.phycon.network.packet.RemoteActivationPacket +import net.shadowfacts.phycon.util.ActivationMode + +/** + * @author shadowfacts + */ +class ActivationController( + private val sleepInterval: Long, + private val device: T +) where T: ActivationController.ActivatableDevice, T: BlockEntity { + + var activationMode = ActivationMode.AUTOMATIC + set(value) { + field = value + // when the activation mode changes, reset the remote enabled status + remotelyEnabled = false + } + + private var lastActivation = -sleepInterval + private var remotelyEnabled = false + + fun tick() { + if (activationMode == ActivationMode.AUTOMATIC || remotelyEnabled) { + tryActivate() + } + } + + fun handleRemoteActivation(packet: RemoteActivationPacket) { + if (activationMode != ActivationMode.MANAGED) { + return + } + + when (packet.mode) { + RemoteActivationPacket.Mode.SINGLE -> tryActivate() + RemoteActivationPacket.Mode.ENABLE -> remotelyEnabled = true + RemoteActivationPacket.Mode.DISABLE -> remotelyEnabled = false + } + } + + private fun tryActivate() { + assert(!device.world!!.isClient) + if ((device.counter - lastActivation) < sleepInterval) { + return + } + if (device.activate()) { + lastActivation = device.counter + } + } + + interface ActivatableDevice { + val controller: ActivationController<*> + + val counter: Long + + fun activate(): Boolean + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt b/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt new file mode 100644 index 0000000..4a92ba8 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/network/packet/RemoteActivationPacket.kt @@ -0,0 +1,14 @@ +package net.shadowfacts.phycon.network.packet + +import net.shadowfacts.phycon.api.util.IPAddress + +/** + * @author shadowfacts + */ +class RemoteActivationPacket(val mode: Mode, source: IPAddress, destination: IPAddress): BasePacket(source, destination) { + enum class Mode { + SINGLE, + ENABLE, + DISABLE + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt new file mode 100644 index 0000000..3abd003 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureActivationMode.kt @@ -0,0 +1,49 @@ +package net.shadowfacts.phycon.networking + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs +import net.fabricmc.fabric.api.networking.v1.PacketSender +import net.minecraft.network.Packet +import net.minecraft.network.PacketByteBuf +import net.minecraft.server.MinecraftServer +import net.minecraft.server.network.ServerPlayNetworkHandler +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.util.Identifier +import net.minecraft.util.registry.Registry +import net.minecraft.util.registry.RegistryKey +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.network.DeviceBlockEntity +import net.shadowfacts.phycon.network.component.ActivationController +import net.shadowfacts.phycon.util.ActivationMode + +/** + * @author shadowfacts + */ +object C2SConfigureActivationMode: ServerReceiver { + override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_activation_mode") + + operator fun invoke(be: T): Packet<*> where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(be.world!!.registryKey.value) + buf.writeBlockPos(be.pos) + buf.writeString(be.controller.activationMode.name) + + return createPacket(buf) + } + + override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) { + val dimID = buf.readIdentifier() + val pos = buf.readBlockPos() + val mode = ActivationMode.valueOf(buf.readString()) + + server.execute { + // todo: check the player is close enough + val key = RegistryKey.of(Registry.DIMENSION, dimID) + val world = server.getWorld(key) ?: return@execute + val device = world.getBlockEntity(pos) ?: return@execute + if (device !is ActivationController.ActivatableDevice) return@execute + device.controller.activationMode = mode + device.markDirty() + } + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt new file mode 100644 index 0000000..f51ec0c --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2SConfigureRedstoneController.kt @@ -0,0 +1,59 @@ +package net.shadowfacts.phycon.networking + +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs +import net.fabricmc.fabric.api.networking.v1.PacketSender +import net.minecraft.network.Packet +import net.minecraft.network.PacketByteBuf +import net.minecraft.server.MinecraftServer +import net.minecraft.server.network.ServerPlayNetworkHandler +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.util.Identifier +import net.minecraft.util.registry.Registry +import net.minecraft.util.registry.RegistryKey +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.api.util.IPAddress +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity +import net.shadowfacts.phycon.util.RedstoneMode + +/** + * @author shadowfacts + */ +object C2SConfigureRedstoneController: ServerReceiver { + // todo: it would be nice if there wasn't so much duplication with C2SConfigureActivationMode + + override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "configure_redstone_controller") + + operator fun invoke(be: RedstoneControllerBlockEntity): Packet<*> { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(be.world!!.registryKey.value) + buf.writeBlockPos(be.pos) + buf.writeString(be.redstoneMode.name) + be.managedDevices.forEach { + buf.writeInt(it?.address ?: 0) + } + + return createPacket(buf) + } + + override fun receive(server: MinecraftServer, player: ServerPlayerEntity, handler: ServerPlayNetworkHandler, buf: PacketByteBuf, responseSender: PacketSender) { + val dimID = buf.readIdentifier() + val pos = buf.readBlockPos() + val mode = RedstoneMode.valueOf(buf.readString()) + val managedDevices = Array(5) { null } + (0..4).map { + val v = buf.readInt() + managedDevices[it] = if (v == 0) null else IPAddress(v) + } + + server.execute { + // todo: check if the player is close enough + val key = RegistryKey.of(Registry.DIMENSION, dimID) + val world = server.getWorld(key) ?: return@execute + val device = world.getBlockEntity(pos) as? RedstoneControllerBlockEntity ?: return@execute + device.redstoneMode = mode + device.managedDevices = managedDevices + device.markDirty() + } + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt index 0ea41e9..f940b49 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/ServerReceiver.kt @@ -1,6 +1,9 @@ package net.shadowfacts.phycon.networking +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking +import net.minecraft.network.Packet +import net.minecraft.network.PacketByteBuf import net.minecraft.util.Identifier /** @@ -8,4 +11,8 @@ import net.minecraft.util.Identifier */ interface ServerReceiver: ServerPlayNetworking.PlayChannelHandler { val CHANNEL: Identifier -} \ No newline at end of file + + fun createPacket(buf: PacketByteBuf): Packet<*> { + return ClientPlayNetworking.createC2SPacket(CHANNEL, buf) + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt new file mode 100644 index 0000000..609dc5e --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt @@ -0,0 +1,75 @@ +package net.shadowfacts.phycon.screen + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.text.LiteralText +import net.minecraft.text.Text +import net.shadowfacts.phycon.network.DeviceBlockEntity +import net.shadowfacts.phycon.network.component.ActivationController +import net.shadowfacts.phycon.networking.C2SConfigureActivationMode +import net.shadowfacts.phycon.util.ActivationMode +import net.shadowfacts.phycon.util.next +import org.lwjgl.glfw.GLFW + +/** + * @author shadowfacts + */ +class ActivatableDeviceConsoleScreen( + val device: T +): Screen(device.cachedState.block.name) where T: DeviceBlockEntity, T: ActivationController.ActivatableDevice { + + private val backgroundWidth = 252 + private val backgroundHeight = 222 + + override fun init() { + super.init() + + buttons.clear() + + val minX = (width - backgroundWidth) / 2 + val minY = (height - backgroundHeight) / 2 + + lateinit var mode: ButtonWidget + mode = ButtonWidget(minX + 5, minY + 25, 55, 20, device.controller.activationMode.friendlyName) { + device.controller.activationMode = device.controller.activationMode.next + mode.message = device.controller.activationMode.friendlyName + client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device)) + } + addButton(mode) + } + + override fun isPauseScreen() = false + + override fun keyPressed(key: Int, j: Int, k: Int): Boolean { + if (key == GLFW.GLFW_KEY_E) { + onClose() + return true + } + return super.keyPressed(key, j, k) + } + + override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { + renderBackground(matrixStack) + + val minX = (width - backgroundWidth) / 2 + val minY = (height - backgroundHeight) / 2 + + RenderSystem.color4f(1f, 1f, 1f, 1f) + client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND) + drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight) + + super.render(matrixStack, mouseX, mouseY, delta) + + textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040) + textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040) + } + + private val ActivationMode.friendlyName: Text + get() = when (this) { + ActivationMode.AUTOMATIC -> LiteralText("Automatic") + ActivationMode.MANAGED -> LiteralText("Managed") + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt index 37f9751..2336157 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/DeviceConsoleScreen.kt @@ -3,6 +3,8 @@ package net.shadowfacts.phycon.screen import net.minecraft.client.gui.screen.Screen import net.minecraft.client.util.math.MatrixStack import net.minecraft.text.TranslatableText +import net.minecraft.util.Identifier +import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.network.DeviceBlockEntity import org.lwjgl.glfw.GLFW @@ -11,18 +13,18 @@ import org.lwjgl.glfw.GLFW */ class DeviceConsoleScreen( val device: DeviceBlockEntity, -): Screen(TranslatableText("item.phycon.onsole")) { +): Screen(TranslatableText("item.phycon.console")) { - override fun init() { - super.init() + companion object { + val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/console.png") } override fun isPauseScreen() = false override fun keyPressed(key: Int, j: Int, k: Int): Boolean { if (key == GLFW.GLFW_KEY_E) { - onClose(); - return true; + onClose() + return true } return super.keyPressed(key, j, k) } @@ -35,4 +37,4 @@ class DeviceConsoleScreen( drawCenteredString(matrixStack, textRenderer, device.macAddress.toString(), width / 2, height / 2 - 5, 0xffffff) drawCenteredString(matrixStack, textRenderer, device.ipAddress.toString(), width / 2, height / 2 + 5, 0xffffff) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt new file mode 100644 index 0000000..b327a80 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt @@ -0,0 +1,111 @@ +package net.shadowfacts.phycon.screen + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.font.TextRenderer +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.gui.widget.ButtonWidget +import net.minecraft.client.gui.widget.TextFieldWidget +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.text.LiteralText +import net.minecraft.text.Text +import net.shadowfacts.phycon.api.util.IPAddress +import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity +import net.shadowfacts.phycon.networking.C2SConfigureRedstoneController +import net.shadowfacts.phycon.util.RedstoneMode +import net.shadowfacts.phycon.util.next +import org.lwjgl.glfw.GLFW + +/** + * @author shadowfacts + */ +class RedstoneControllerConsoleScreen( + val device: RedstoneControllerBlockEntity +): Screen(device.cachedState.block.name) { + + private val backgroundWidth = 252 + private val backgroundHeight = 222 + + private val ipAddressTextFields = mutableListOf() + + override fun init() { + super.init() + + buttons.clear() + ipAddressTextFields.clear() + + val minX = (width - backgroundWidth) / 2 + val minY = (height - backgroundHeight) / 2 + + lateinit var mode: ButtonWidget + mode = ButtonWidget(minX + 5, minY + 25, 75, 20, device.redstoneMode.friendlyName) { + device.redstoneMode = device.redstoneMode.next + mode.message = device.redstoneMode.friendlyName + client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device)) + } + addButton(mode) + + for (i in 0 until 5) { + // todo: field name + val field = TextFieldWidget(textRenderer, minX + 5, minY + 50 + 22 * i, backgroundWidth / 2, 20, LiteralText("")) + field.setMaxLength(15) + field.setHasBorder(true) + field.isVisible = true + field.setEditableColor(0xffffff) + field.text = device.managedDevices[i]?.toString() + field.setChangedListener { newVal -> + device.managedDevices[i] = IPAddress.parse(newVal) + client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device)) + } + addChild(field) + ipAddressTextFields.add(field) + } + } + + override fun isPauseScreen() = false + + override fun keyPressed(key: Int, j: Int, k: Int): Boolean { + if (key == GLFW.GLFW_KEY_E) { + onClose() + return true + } + return super.keyPressed(key, j, k) + } + + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + val clickedField = ipAddressTextFields.find { it.x <= mouseX && it.x + it.width >= mouseX && it.y <= mouseY && it.y + it.height >= mouseY } + if (clickedField != null) { + ipAddressTextFields.forEach { + if (it !== clickedField) it.setSelected(false) + } + } + return super.mouseClicked(mouseX, mouseY, button) + } + + override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { + renderBackground(matrixStack) + + val minX = (width - backgroundWidth) / 2 + val minY = (height - backgroundHeight) / 2 + + RenderSystem.color4f(1f, 1f, 1f, 1f) + client!!.textureManager.bindTexture(DeviceConsoleScreen.BACKGROUND) + drawTexture(matrixStack, minX, minY, 0, 0, backgroundWidth, backgroundHeight) + + super.render(matrixStack, mouseX, mouseY, delta) + + ipAddressTextFields.forEach { it.render(matrixStack, mouseX, mouseY, delta) } + + textRenderer.draw(matrixStack, "IP Address: ${device.ipAddress}", minX + 5f, minY + 5f, 0x404040) + textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040) + } + + private val RedstoneMode.friendlyName: Text + get() = LiteralText(when (this) { + RedstoneMode.HIGH -> "High" + RedstoneMode.LOW -> "Low" + RedstoneMode.TOGGLE -> "Toggle" + RedstoneMode.RISING_EDGE -> "Rising Edge" + RedstoneMode.FALLING_EDGE -> "Falling Edge" + }) + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt new file mode 100644 index 0000000..28fa901 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt @@ -0,0 +1,9 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +enum class ActivationMode: RotatableEnum { + AUTOMATIC, + MANAGED, +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt new file mode 100644 index 0000000..cf2c7f3 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt @@ -0,0 +1,24 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +enum class RedstoneMode: RotatableEnum { + HIGH, + LOW, + TOGGLE, + RISING_EDGE, + FALLING_EDGE; + + val isDiscrete: Boolean + get() = when (this) { + TOGGLE, RISING_EDGE, FALLING_EDGE -> true + else -> false + } + + val isContinuous: Boolean + get() = when (this) { + HIGH, LOW -> true + else -> false + } +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt new file mode 100644 index 0000000..75d40a3 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/RotatableEnum.kt @@ -0,0 +1,13 @@ +package net.shadowfacts.phycon.util + +/** + * @author shadowfacts + */ +interface RotatableEnum { +} + +val E.prev: E where E: Enum, E: RotatableEnum + get() = javaClass.enumConstants[(ordinal - 1 + javaClass.enumConstants.size) % javaClass.enumConstants.size] + +val E.next: E where E: Enum, E: RotatableEnum + get() = javaClass.enumConstants[(ordinal + 1) % javaClass.enumConstants.size] diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt index 7b61025..fbcde94 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/SortMode.kt @@ -6,17 +6,11 @@ import net.minecraft.text.Text /** * @author shadowfacts */ -enum class SortMode { +enum class SortMode: RotatableEnum { COUNT_HIGH_FIRST, COUNT_LOW_FIRST, ALPHABETICAL; - val prev: SortMode - get() = values()[(ordinal - 1 + values().size) % values().size] - - val next: SortMode - get() = values()[(ordinal + 1) % values().size] - val tooltip: Text get() = when (this) { COUNT_HIGH_FIRST -> LiteralText("Count, highest first") @@ -24,4 +18,4 @@ enum class SortMode { ALPHABETICAL -> LiteralText("Name") } -} \ No newline at end of file +} diff --git a/src/main/resources/assets/phycon/blockstates/redstone_controller.json b/src/main/resources/assets/phycon/blockstates/redstone_controller.json new file mode 100644 index 0000000..32abdb2 --- /dev/null +++ b/src/main/resources/assets/phycon/blockstates/redstone_controller.json @@ -0,0 +1,159 @@ +{ + "multipart": [ + { + "apply": { "model": "phycon:block/cable_center" } + }, + { + "when": { "facing": "down", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side" } + }, + { + "when": { "facing": "up", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 180 } + }, + { + "when": { "facing": "north", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 270 } + }, + { + "when": { "facing": "south", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 90 } + }, + { + "when": { "facing": "west", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 90, "y": 90 } + }, + { + "when": { "facing": "east", "lit": "true|false" }, + "apply": { "model": "phycon:block/interface_side", "x": 90, "y": 270 } + }, + + { + "when": { "cable_connection": "up", "facing": "down" }, + "apply": { "model": "phycon:block/interface_cable_straight" } + }, + { + "when": { "cable_connection": "down", "facing": "up" }, + "apply": { "model": "phycon:block/interface_cable_straight", "x": 180 } + }, + { + "when": { "cable_connection": "north", "facing": "south" }, + "apply": { "model": "phycon:block/interface_cable_straight", "x": 90 } + }, + { + "when": { "cable_connection": "south", "facing": "north" }, + "apply": { "model": "phycon:block/interface_cable_straight", "x": 270 } + }, + { + "when": { "cable_connection": "west", "facing": "east" }, + "apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 270 } + }, + { + "when": { "cable_connection": "east", "facing": "west" }, + "apply": { "model": "phycon:block/interface_cable_straight", "x": 90, "y": 90 } + }, + + { + "when": {"cable_connection": "north", "facing": "down"}, + "apply": { "model": "phycon:block/interface_cable_corner" } + }, + { + "when": {"cable_connection": "east", "facing": "down"}, + "apply": { "model": "phycon:block/interface_cable_corner", "y": 90 } + }, + { + "when": {"cable_connection": "south", "facing": "down"}, + "apply": { "model": "phycon:block/interface_cable_corner", "y": 180 } + }, + { + "when": {"cable_connection": "west", "facing": "down"}, + "apply": { "model": "phycon:block/interface_cable_corner", "y": 270 } + }, + + { + "when": {"cable_connection": "north", "facing": "up"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 180 } + }, + { + "when": {"cable_connection": "east", "facing": "up"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 270 } + }, + { + "when": {"cable_connection": "south", "facing": "up"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 180 } + }, + { + "when": {"cable_connection": "west", "facing": "up"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 180, "y": 90 } + }, + + { + "when": {"cable_connection": "down", "facing": "north"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 180 } + }, + { + "when": {"cable_connection": "up", "facing": "north"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 270 } + }, + { + "when": {"cable_connection": "west", "facing": "north"}, + "apply": { "model": "phycon:block/interface_cable_corner_2" } + }, + { + "when": {"cable_connection": "east", "facing": "north"}, + "apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 180 } + }, + + { + "when": {"cable_connection": "down", "facing": "south"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 90 } + }, + { + "when": {"cable_connection": "up", "facing": "south"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 180 } + }, + { + "when": {"cable_connection": "west", "facing": "south"}, + "apply": { "model": "phycon:block/interface_cable_corner_3" } + }, + { + "when": {"cable_connection": "east", "facing": "south"}, + "apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 180 } + }, + + { + + "when": {"cable_connection": "down", "facing": "west"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 90 } + }, + { + "when": {"cable_connection": "up", "facing": "west"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 270 } + }, + { + "when": {"cable_connection": "north", "facing": "west"}, + "apply": { "model": "phycon:block/interface_cable_corner_3", "y": 90 } + }, + { + "when": {"cable_connection": "south", "facing": "west"}, + "apply": { "model": "phycon:block/interface_cable_corner_3", "x": 180, "y": 270 } + }, + + { + "when": {"cable_connection": "down", "facing": "east"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 90, "y": 270 } + }, + { + "when": {"cable_connection": "up", "facing": "east"}, + "apply": { "model": "phycon:block/interface_cable_corner", "x": 270, "y": 90 } + }, + { + "when": {"cable_connection": "north", "facing": "east"}, + "apply": { "model": "phycon:block/interface_cable_corner_2", "y": 90 } + }, + { + "when": {"cable_connection": "south", "facing": "east"}, + "apply": { "model": "phycon:block/interface_cable_corner_2", "x": 180, "y": 270 } + } + ] +} diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json index 0ae03a5..d88e438 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -5,7 +5,8 @@ "block.phycon.cable": "Cable", "block.phycon.extractor": "Inventory Extractor", "block.phycon.miner": "Block Miner", + "block.phycon.redstone_controller": "Redstone Controller", "item.phycon.screwdriver": "Screwdriver", "item.phycon.console": "Console" -} \ No newline at end of file +} diff --git a/src/main/resources/assets/phycon/textures/gui/console.png b/src/main/resources/assets/phycon/textures/gui/console.png new file mode 100644 index 0000000000000000000000000000000000000000..b92218c9652c96bb815a43844682dd543742665d GIT binary patch literal 3333 zcmeH}XHXMt630U*L7IXzkt!fXsubzHN()6=lo~)ts3L?OKoBVbkETe6fKnp@Qk8)8 zAXQÿ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 Date: Tue, 23 Feb 2021 22:39:51 -0500 Subject: [PATCH 09/13] Localize more things --- .../phycon/network/block/terminal/TerminalScreen.kt | 4 ++-- .../phycon/screen/ActivatableDeviceConsoleScreen.kt | 9 --------- .../phycon/screen/RedstoneControllerConsoleScreen.kt | 12 ------------ .../net/shadowfacts/phycon/util/ActivationMode.kt | 8 +++++++- .../net/shadowfacts/phycon/util/RedstoneMode.kt | 6 ++++++ src/main/resources/assets/phycon/lang/en_us.json | 11 ++++++++++- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt index 376e194..762c0e9 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/terminal/TerminalScreen.kt @@ -15,6 +15,7 @@ import net.minecraft.screen.slot.Slot import net.minecraft.screen.slot.SlotActionType import net.minecraft.text.LiteralText import net.minecraft.text.Text +import net.minecraft.text.TranslatableText import net.minecraft.util.Identifier import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.networking.C2STerminalRequestItem @@ -161,8 +162,7 @@ class TerminalScreen(handler: TerminalScreenHandler, playerInv: PlayerInventory, override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { textRenderer.draw(matrixStack, title, 65f, 6f, 0x404040) textRenderer.draw(matrixStack, playerInventory.displayName, 65f, backgroundHeight - 94f, 0x404040) - // todo: translate this - textRenderer.draw(matrixStack, "Buffer", 7f, 6f, 0x404040) + textRenderer.draw(matrixStack, TranslatableText("gui.phycon.terminal_buffer"), 7f, 6f, 0x404040) } override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) { diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt index 609dc5e..dedec57 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt @@ -4,12 +4,9 @@ import com.mojang.blaze3d.systems.RenderSystem import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.widget.ButtonWidget import net.minecraft.client.util.math.MatrixStack -import net.minecraft.text.LiteralText -import net.minecraft.text.Text import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.component.ActivationController import net.shadowfacts.phycon.networking.C2SConfigureActivationMode -import net.shadowfacts.phycon.util.ActivationMode import net.shadowfacts.phycon.util.next import org.lwjgl.glfw.GLFW @@ -66,10 +63,4 @@ class ActivatableDeviceConsoleScreen( textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040) } - private val ActivationMode.friendlyName: Text - get() = when (this) { - ActivationMode.AUTOMATIC -> LiteralText("Automatic") - ActivationMode.MANAGED -> LiteralText("Managed") - } - } diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt index b327a80..1a6bea6 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt @@ -1,17 +1,14 @@ package net.shadowfacts.phycon.screen import com.mojang.blaze3d.systems.RenderSystem -import net.minecraft.client.font.TextRenderer import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.widget.ButtonWidget import net.minecraft.client.gui.widget.TextFieldWidget import net.minecraft.client.util.math.MatrixStack import net.minecraft.text.LiteralText -import net.minecraft.text.Text import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.network.block.redstone.RedstoneControllerBlockEntity import net.shadowfacts.phycon.networking.C2SConfigureRedstoneController -import net.shadowfacts.phycon.util.RedstoneMode import net.shadowfacts.phycon.util.next import org.lwjgl.glfw.GLFW @@ -99,13 +96,4 @@ class RedstoneControllerConsoleScreen( textRenderer.draw(matrixStack, "MAC Address: ${device.macAddress}", minX + 5f, minY + 15f, 0x404040) } - private val RedstoneMode.friendlyName: Text - get() = LiteralText(when (this) { - RedstoneMode.HIGH -> "High" - RedstoneMode.LOW -> "Low" - RedstoneMode.TOGGLE -> "Toggle" - RedstoneMode.RISING_EDGE -> "Rising Edge" - RedstoneMode.FALLING_EDGE -> "Falling Edge" - }) - } diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt index 28fa901..3ab9b3d 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt @@ -1,9 +1,15 @@ package net.shadowfacts.phycon.util +import net.minecraft.text.Text +import net.minecraft.text.TranslatableText + /** * @author shadowfacts */ enum class ActivationMode: RotatableEnum { AUTOMATIC, - MANAGED, + MANAGED; + + val friendlyName: Text + get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}") } diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt index cf2c7f3..a7882bf 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt @@ -1,5 +1,8 @@ package net.shadowfacts.phycon.util +import net.minecraft.text.Text +import net.minecraft.text.TranslatableText + /** * @author shadowfacts */ @@ -21,4 +24,7 @@ enum class RedstoneMode: RotatableEnum { HIGH, LOW -> true else -> false } + + val friendlyName: Text + get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}") } diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json index d88e438..ea4ce28 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -8,5 +8,14 @@ "block.phycon.redstone_controller": "Redstone Controller", "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" } From d621e9e089013a440325ef91dec1038e9d36ce87 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 24 Feb 2021 18:58:22 -0500 Subject: [PATCH 10/13] Add actual Redstone Controller model --- .../block/redstone/RedstoneControllerBlock.kt | 29 ++++------ .../blockstates/redstone_controller.json | 48 ++++++++++++----- .../phycon/models/block/cable_center.json | 5 +- .../block/redstone_controller_side_off.json | 33 ++++++++++++ .../block/redstone_controller_side_on.json | 33 ++++++++++++ .../models/item/redstone_controller.json | 51 ++++++++++++++++++ .../block/redstone_controller_back.png | Bin 0 -> 831 bytes .../block/redstone_controller_front_off.png | Bin 0 -> 986 bytes .../block/redstone_controller_front_on.png | Bin 0 -> 1053 bytes 9 files changed, 166 insertions(+), 33 deletions(-) create mode 100644 src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json create mode 100644 src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json create mode 100644 src/main/resources/assets/phycon/models/item/redstone_controller.json create mode 100644 src/main/resources/assets/phycon/textures/block/redstone_controller_back.png create mode 100644 src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png create mode 100644 src/main/resources/assets/phycon/textures/block/redstone_controller_front_on.png diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt index 5a9cb6e..51d4773 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/redstone/RedstoneControllerBlock.kt @@ -12,6 +12,7 @@ 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.* @@ -27,14 +28,14 @@ class RedstoneControllerBlock: FaceDeviceBlock(Se } // todo: don't just copy this from the Interface block - override val faceThickness = 2.0 + override val faceThickness = 3.0 override val faceShapes = mapOf( - Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 2.0, 16.0), - Direction.UP to createCuboidShape(0.0, 14.0, 0.0, 16.0, 16.0, 16.0), - Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 2.0), - Direction.SOUTH to createCuboidShape(0.0, 0.0, 14.0, 16.0, 16.0, 16.0), - Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 2.0, 16.0, 16.0), - Direction.EAST to createCuboidShape(14.0, 0.0, 0.0, 16.0, 16.0, 16.0) + 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) { @@ -49,27 +50,17 @@ class RedstoneControllerBlock: FaceDeviceBlock(Se return state.with(LIT, isPowered(context.world, context.blockPos, state[FACING])) } - // todo: does this need to be separate from getStateForNeighborUpdate? override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) { + // 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) { - if (wasLit) { - world.blockTickScheduler.schedule(pos, this, 4) - } else { - toggleLit(state, world, pos) - } + toggleLit(state, world, pos) } } } - override fun scheduledTick(state: BlockState, world: ServerWorld, pos: BlockPos, random: Random) { - if (state[LIT] && !isPowered(world, pos, state[FACING])) { - toggleLit(state, world, pos) - } - } - private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean { val offset = pos.offset(facing) return world.getEmittedRedstonePower(offset, facing) > 0 diff --git a/src/main/resources/assets/phycon/blockstates/redstone_controller.json b/src/main/resources/assets/phycon/blockstates/redstone_controller.json index 32abdb2..196adf8 100644 --- a/src/main/resources/assets/phycon/blockstates/redstone_controller.json +++ b/src/main/resources/assets/phycon/blockstates/redstone_controller.json @@ -4,28 +4,52 @@ "apply": { "model": "phycon:block/cable_center" } }, { - "when": { "facing": "down", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side" } + "when": { "facing": "down", "lit": "false" }, + "apply": { "model": "phycon:block/redstone_controller_side_off" } }, { - "when": { "facing": "up", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side", "x": 180 } + "when": { "facing": "up", "lit": "false" }, + "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 180 } }, { - "when": { "facing": "north", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side", "x": 270 } + "when": { "facing": "north", "lit": "false" }, + "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 270 } }, { - "when": { "facing": "south", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side", "x": 90 } + "when": { "facing": "south", "lit": "false" }, + "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90 } }, { - "when": { "facing": "west", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side", "x": 90, "y": 90 } + "when": { "facing": "west", "lit": "false" }, + "apply": { "model": "phycon:block/redstone_controller_side_off", "x": 90, "y": 90 } }, { - "when": { "facing": "east", "lit": "true|false" }, - "apply": { "model": "phycon:block/interface_side", "x": 90, "y": 270 } + "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 } }, { diff --git a/src/main/resources/assets/phycon/models/block/cable_center.json b/src/main/resources/assets/phycon/models/block/cable_center.json index 05ecfd8..cfda031 100644 --- a/src/main/resources/assets/phycon/models/block/cable_center.json +++ b/src/main/resources/assets/phycon/models/block/cable_center.json @@ -1,6 +1,7 @@ { "textures": { - "center": "phycon:block/cable_cap_end" + "center": "phycon:block/cable_cap_end", + "particle": "#center" }, "elements": [ { @@ -39,4 +40,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json b/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json new file mode 100644 index 0000000..2c26a3b --- /dev/null +++ b/src/main/resources/assets/phycon/models/block/redstone_controller_side_off.json @@ -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" } + } + } + ] +} diff --git a/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json b/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json new file mode 100644 index 0000000..65b44a4 --- /dev/null +++ b/src/main/resources/assets/phycon/models/block/redstone_controller_side_on.json @@ -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" } + } + } + ] +} diff --git a/src/main/resources/assets/phycon/models/item/redstone_controller.json b/src/main/resources/assets/phycon/models/item/redstone_controller.json new file mode 100644 index 0000000..e4bd117 --- /dev/null +++ b/src/main/resources/assets/phycon/models/item/redstone_controller.json @@ -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] } + } + } + ] +} diff --git a/src/main/resources/assets/phycon/textures/block/redstone_controller_back.png b/src/main/resources/assets/phycon/textures/block/redstone_controller_back.png new file mode 100644 index 0000000000000000000000000000000000000000..14643d158138fceaa6407fe2e6e2aab6a055c3fb GIT binary patch literal 831 zcmV-F1Hk-=P)EX>4Tx04R}tkv&MmKpe$iQ>7vm5sMUY$WWauh>D1lR-p(LLaorMgUR(1nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0YbgZRI_6YP&La) z#$#eSvnmE&As~za%%D$ZramW033!gLd-(Wz7v)*r=l&c$*__D$pGZ8*bi*RvAfDQ^ zbk6(4Ay$;L#OK7L23?T&k?V@fZ=4Gb3p_Jqq>}T*A!4!6#!4HrqM;H`5=XMCM)^Y8 zu?swklh3sG7%QcNUiKjz^dbo>&z6mpfo z$gzM5RLG7W{11M2Yvw1$-K1a)=zOv5k6|FN3p8rB{e5iPjT6BC3|wg~f29sgf0ABn zX^|tKcN@64ZfVLMaJd77pLE%f9m!8q$mfCgGy0|s(0>bbuerT7_i_3Fq^PUJ4RCM> zj1(w)-Q(R|?Y;ebrrF;QpdoU(C6l;200006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNlirucW`GZ_6eD8(Wfvetv$E1W6>pHc6N@=dXj_pIpC_Br&N;Zn7i*D(OA2)&l4P zf3N5its)6kg(UV`1PJgv&$qxEq9sxdPZE+E>RLRhxj-zc&fYe*FHEb0oNs{7=fm*Z zd*ieo7s0?B*Z`@*HlOFgQlo<|ezfy+U3-&*W}uIFnt}&ij+m)pOJdv9^K@s6sI|Dd z*uT2YI0ZP#RrCfc_rg2#Cb4*TZ;Hq>AMQ`t8uCs=GJT?}SK` zZCeVfVJcq=?Tzt)*?S)$i#_VvJq?W}^GeU-qS{0p*H#r?i$92X{K431)qem0002ov JPDHLkV1j;xdv^c; literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png b/src/main/resources/assets/phycon/textures/block/redstone_controller_front_off.png new file mode 100644 index 0000000000000000000000000000000000000000..7099eb9148db90a07acda57f12c2e94a61206544 GIT binary patch literal 986 zcmV<0110>4P)EX>4Tx04R}tkv&MmKpe$iQ>7vm5sMUY$WWauh>D1lR-p(LLaorMgUR(1nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0YbgZRI_6YP&La) z#$#eSvnmE&As~za%%D$ZramW033!gLd-(Wz7v)*r=l&c$*__D$pGZ8*bi*RvAfDQ^ zbk6(4Ay$;L#OK7L23?T&k?V@fZ=4Gb3p_Jqq>}T*A!4!6#!4HrqM;H`5=XMCM)^Y8 zu?swklh3sG7%QcNUiKjz^dbo>&z6mpfo z$gzM5RLG7W{11M2Yvw1$-K1a)=zOv5k6|FN3p8rB{e5iPjT6BC3|wg~f29sgf0ABn zX^|tKcN@64ZfVLMaJd77pLE%f9m!8q$mfCgGy0|s(0>bbuerT7_i_3Fq^PUJ4RCM> zj1(w)-Q(R|?Y;ebrrF;QpdoU(C6l;200006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru$dl!dJ-mB1YhhXSzzvqI<^HP6EMVQIziQ?`<1N*d`*d&8bfFH-oj` zu<;foi4)ZJ87v8avezESOaK~h>fzUKmcB4a3>yciV45eYFH{ADdA!>(#&}g=n}_dz zsJ?J9Yn8Ibg{``SMRMn~=BB&g+De36$^umBN3qYpkrVMzAVOb5e@qB zbrVezEmp}4F#+1IQVkfBHv@MFAS%giodUu(V+*}SC-g7z`-lkoy#rav7hI?hBIFdDpc#^wPwk(o$h{xDJdg{2C&q{o5z-Ar8? z##kSv;|qwk8YqbEX>4Tx04R}tkv&MmKpe$iQ>7vm5sMUY$WWauh>D1lR-p(LLaorMgUR(1nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0YbgZRI_6YP&La) z#$#eSvnmE&As~za%%D$ZramW033!gLd-(Wz7v)*r=l&c$*__D$pGZ8*bi*RvAfDQ^ zbk6(4Ay$;L#OK7L23?T&k?V@fZ=4Gb3p_Jqq>}T*A!4!6#!4HrqM;H`5=XMCM)^Y8 zu?swklh3sG7%QcNUiKjz^dbo>&z6mpfo z$gzM5RLG7W{11M2Yvw1$-K1a)=zOv5k6|FN3p8rB{e5iPjT6BC3|wg~f29sgf0ABn zX^|tKcN@64ZfVLMaJd77pLE%f9m!8q$mfCgGy0|s(0>bbuerT7_i_3Fq^PUJ4RCM> zj1(w)-Q(R|?Y;ebrrF;QpdoU(C6l;200006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru>C!MbZ%GxEwEh8 z$!$w65kleWVoNFIKLdB?-N$Q&eZ!MYjhr}9VIBgDXG-2VxhO`)iu2*~CtL+H8yTo7 zr5>0qD)zs6AfQ#jQlR_UBVLDbgdiJ(57XOzt36 z5m6yVJqjF;;t-MVN<&WyJ7NWL4)rcjtin$Qx*wUCpnK4x6H!Ens%TYU5SD=4j8q*M zaO&qBxgEDvxh73^3ZU*zDFus;JsCi1IuxA+>N5x!_IpmcA8n&RM@3M@0TmGtAxR=> zg=~7}LFxB}a<2qt$ZF3pcLEh&Wt0bOx(78gRD`bS=>LN6z}A^rgs?H{GsTKQ>u4o# z8W3ujt~2^{T~1)}s-}E%taX$%-Pb!ZEnd~A^e{x^wr4+SfIAvOKtx!*T$BGav^nD` z(S2X5dAjx><1b z=7Ixr*>1O^8qP6HI)%v0P}M1g;O2-bL3MP_jp#6L(9sQ Date: Wed, 24 Feb 2021 22:09:24 -0500 Subject: [PATCH 11/13] Convert extractor to use NetworkStackDispatcher --- .../block/extractor/ExtractorBlockEntity.kt | 64 +++++++++++++------ .../component/NetworkStackDispatcher.kt | 9 ++- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt index 59fe56f..7d83833 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/extractor/ExtractorBlockEntity.kt @@ -1,26 +1,31 @@ package net.shadowfacts.phycon.network.block.extractor import alexiil.mc.lib.attributes.SearchOptions -import alexiil.mc.lib.attributes.item.GroupedItemInv +import alexiil.mc.lib.attributes.Simulation +import alexiil.mc.lib.attributes.item.FixedItemInv 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.shadowfacts.phycon.api.packet.Packet -import net.shadowfacts.phycon.api.util.IPAddress import net.shadowfacts.phycon.init.PhyBlockEntities import net.shadowfacts.phycon.network.DeviceBlockEntity import net.shadowfacts.phycon.network.component.ActivationController +import net.shadowfacts.phycon.network.component.NetworkStackDispatcher +import net.shadowfacts.phycon.network.component.handleItemStack import net.shadowfacts.phycon.network.packet.CapacityPacket -import net.shadowfacts.phycon.network.packet.CheckCapacityPacket import net.shadowfacts.phycon.network.packet.ItemStackPacket import net.shadowfacts.phycon.network.packet.RemoteActivationPacket import net.shadowfacts.phycon.util.ActivationMode +import kotlin.properties.Delegates /** * @author shadowfacts */ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), + NetworkStackDispatcher, ActivationController.ActivatableDevice { companion object { @@ -30,17 +35,18 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), private val facing: Direction get() = cachedState[ExtractorBlock.FACING] - private var inventory: GroupedItemInv? = null - private var shouldExtract = false + private var inventory: FixedItemInv? = null + override val pendingInsertions = mutableListOf() + override val dispatchStackTimeout = 40L override val controller = ActivationController(SLEEP_TIME, this) fun updateInventory() { val offsetPos = pos.offset(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() return inventory } @@ -48,18 +54,27 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), override fun handle(packet: Packet) { when (packet) { is CapacityPacket -> handleCapacity(packet) + is ItemStackPacket -> handleItemStack(packet) is RemoteActivationPacket -> controller.handleRemoteActivation(packet) } } - private fun handleCapacity(packet: CapacityPacket) { - if (shouldExtract && packet.capacity > 0) { - getInventory()?.also { inv -> - shouldExtract = false - val extracted = inv.extract(packet.stack, packet.capacity) - 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() { @@ -72,10 +87,16 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), override fun activate(): Boolean { val inventory = getInventory() ?: return false - val stack = inventory.storedStacks.firstOrNull() ?: return false - shouldExtract = true - sendPacket(CheckCapacityPacket(stack, ipAddress, IPAddress.BROADCAST)) - return true + 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 { @@ -97,4 +118,9 @@ class ExtractorBlockEntity: DeviceBlockEntity(PhyBlockEntities.EXTRACTOR), super.fromClientTag(tag) controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode")) } + + class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion(stack, timestamp) { + lateinit var inventory: FixedItemInv + var inventorySlot by Delegates.notNull() + } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt index 1b6d7b8..6d2f3f6 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/component/NetworkStackDispatcher.kt @@ -33,7 +33,8 @@ interface NetworkStackDispatcher>( - val stack: ItemStack, + var stack: ItemStack, val timestamp: Long ) { val results = mutableSetOf>() @@ -77,5 +79,6 @@ fun > Self.f val finishable = pendingInsertions.filter { it.isFinishable(this) } // finishInsertion removes the object from pendingInsertions 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?) } From 1f078c671f45988f89d4cecbc9a6ffe584da4943 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 24 Feb 2021 22:47:07 -0500 Subject: [PATCH 12/13] Add EnumButton --- .../screen/ActivatableDeviceConsoleScreen.kt | 6 +-- .../shadowfacts/phycon/screen/EnumButton.kt | 49 +++++++++++++++++++ .../screen/RedstoneControllerConsoleScreen.kt | 5 +- .../shadowfacts/phycon/util/ActivationMode.kt | 4 +- .../phycon/util/FriendlyNameable.kt | 10 ++++ .../shadowfacts/phycon/util/RedstoneMode.kt | 4 +- 6 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt create mode 100644 src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt index dedec57..b1ffa81 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/ActivatableDeviceConsoleScreen.kt @@ -7,6 +7,7 @@ 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 @@ -28,10 +29,7 @@ class ActivatableDeviceConsoleScreen( val minX = (width - backgroundWidth) / 2 val minY = (height - backgroundHeight) / 2 - lateinit var mode: ButtonWidget - mode = ButtonWidget(minX + 5, minY + 25, 55, 20, device.controller.activationMode.friendlyName) { - device.controller.activationMode = device.controller.activationMode.next - mode.message = device.controller.activationMode.friendlyName + val mode = EnumButton(device.controller::activationMode, minX + 5, minY + 25, 55, 20) { client!!.player!!.networkHandler.sendPacket(C2SConfigureActivationMode(device)) } addButton(mode) diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt new file mode 100644 index 0000000..79e2608 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/EnumButton.kt @@ -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( + val prop: KMutableProperty, + x: Int, + y: Int, + width: Int, + height: Int, + val onChange: () -> Unit +): AbstractPressableButtonWidget( + x, + y, + width, + height, + prop.getter.call().friendlyName +) where E: Enum, 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() + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt index 1a6bea6..9f30544 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/screen/RedstoneControllerConsoleScreen.kt @@ -33,10 +33,7 @@ class RedstoneControllerConsoleScreen( val minX = (width - backgroundWidth) / 2 val minY = (height - backgroundHeight) / 2 - lateinit var mode: ButtonWidget - mode = ButtonWidget(minX + 5, minY + 25, 75, 20, device.redstoneMode.friendlyName) { - device.redstoneMode = device.redstoneMode.next - mode.message = device.redstoneMode.friendlyName + val mode = EnumButton(device::redstoneMode, minX + 5, minY + 25, 75, 20) { client!!.player!!.networkHandler.sendPacket(C2SConfigureRedstoneController(device)) } addButton(mode) diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt index 3ab9b3d..2db1810 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/ActivationMode.kt @@ -6,10 +6,10 @@ import net.minecraft.text.TranslatableText /** * @author shadowfacts */ -enum class ActivationMode: RotatableEnum { +enum class ActivationMode: RotatableEnum, FriendlyNameable { AUTOMATIC, MANAGED; - val friendlyName: Text + override val friendlyName: Text get() = TranslatableText("gui.phycon.activation_mode.${name.toLowerCase()}") } diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt b/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt new file mode 100644 index 0000000..978d21a --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/util/FriendlyNameable.kt @@ -0,0 +1,10 @@ +package net.shadowfacts.phycon.util + +import net.minecraft.text.Text + +/** + * @author shadowfacts + */ +interface FriendlyNameable { + val friendlyName: Text +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt index a7882bf..0fe895a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/util/RedstoneMode.kt @@ -6,7 +6,7 @@ import net.minecraft.text.TranslatableText /** * @author shadowfacts */ -enum class RedstoneMode: RotatableEnum { +enum class RedstoneMode: RotatableEnum, FriendlyNameable { HIGH, LOW, TOGGLE, @@ -25,6 +25,6 @@ enum class RedstoneMode: RotatableEnum { else -> false } - val friendlyName: Text + override val friendlyName: Text get() = TranslatableText("gui.phycon.redstone_mode.${name.toLowerCase()}") } From 4c5b7daf9e971bcea1c04d7f62d57af059a540bb Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 26 Feb 2021 17:04:18 -0500 Subject: [PATCH 13/13] Use an actual logger --- PhyConDebugLogging.xml | 8 ++++++++ build.gradle | 3 ++- .../net/shadowfacts/phycon/PhysicalConnectivity.kt | 3 +++ .../shadowfacts/phycon/network/DeviceBlockEntity.kt | 13 +++++++------ .../network/block/netswitch/SwitchBlockEntity.kt | 5 +++-- 5 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 PhyConDebugLogging.xml diff --git a/PhyConDebugLogging.xml b/PhyConDebugLogging.xml new file mode 100644 index 0000000..62f2153 --- /dev/null +++ b/PhyConDebugLogging.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build.gradle b/build.gradle index 58d0ef1..bca3339 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id "fabric-loom" version "0.5-SNAPSHOT" + id "fabric-loom" version "0.6.49" id "maven-publish" id "org.jetbrains.kotlin.jvm" version "1.4.30" } @@ -12,6 +12,7 @@ version = project.mod_version group = project.maven_group minecraft { + log4jConfigs.from "PhyConDebugLogging.xml" } repositories { diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 961dbee..d43573f 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -7,6 +7,7 @@ import net.shadowfacts.phycon.init.PhyBlocks import net.shadowfacts.phycon.init.PhyItems import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.networking.* +import org.apache.logging.log4j.LogManager /** * @author shadowfacts @@ -15,6 +16,8 @@ object PhysicalConnectivity: ModInitializer { val MODID = "phycon" + val NETWORK_LOGGER = LogManager.getLogger("PhyNet") + override fun onInitialize() { PhyBlocks.init() PhyBlockEntities.init() diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt index 4ce619a..b30553b 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt @@ -6,6 +6,7 @@ import net.minecraft.block.entity.BlockEntity import net.minecraft.block.entity.BlockEntityType import net.minecraft.nbt.CompoundTag import net.minecraft.util.Tickable +import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.api.PacketSink import net.shadowfacts.phycon.api.PacketSource import net.shadowfacts.phycon.api.Interface @@ -52,6 +53,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), 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) @@ -65,13 +67,12 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), } 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) { is ARPQueryFrame -> handleARPQuery(frame) is ARPResponseFrame -> handleARPResponse(frame) is PacketFrame -> { if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) { - println("$this ($ipAddress) received packet: ${frame.packet}") doHandlePacket(frame.packet) } } @@ -79,17 +80,17 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), } 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 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)) } } private fun handleARPResponse(frame: ARPResponseFrame) { 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, _) -> if (packet.destination == frame.query) { @@ -110,7 +111,7 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type), } else { 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)) } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt index 51d0d51..23c54b8 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/network/block/netswitch/SwitchBlockEntity.kt @@ -7,6 +7,7 @@ import net.minecraft.entity.ItemEntity import net.minecraft.nbt.CompoundTag import net.minecraft.util.Tickable import net.minecraft.util.math.Direction +import net.shadowfacts.phycon.PhysicalConnectivity import net.shadowfacts.phycon.api.Interface import net.shadowfacts.phycon.api.frame.EthernetFrame 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)) { 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) } else { - println("$this (${fromItf.side}, ${fromItf.macAddress}) flooding $frame") + PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, fromItf.side, fromItf.macAddress, frame) flood(frame, fromItf) } }