diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt index 62e55aa..e784c5b 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivity.kt @@ -26,9 +26,10 @@ object PhysicalConnectivity: ModInitializer { PhyItems.init() PhyScreens.init() + registerGlobalReceiver(C2SConfigureDevice) + registerGlobalReceiver(C2STerminalCraftingButton) registerGlobalReceiver(C2STerminalRequestItem) registerGlobalReceiver(C2STerminalUpdateDisplayedItems) - registerGlobalReceiver(C2SConfigureDevice) for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) { it.initializePhyCon(PhyConAPIImpl) diff --git a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt index 5ed45f9..1afd68d 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/PhysicalConnectivityClient.kt @@ -8,6 +8,7 @@ import net.fabricmc.fabric.api.renderer.v1.RendererAccess import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial import net.shadowfacts.phycon.block.inserter.InserterScreen import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen +import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen import net.shadowfacts.phycon.init.PhyScreens import net.shadowfacts.phycon.block.terminal.TerminalScreen import net.shadowfacts.phycon.client.PhyExtendedModelProvider @@ -40,6 +41,7 @@ object PhysicalConnectivityClient: ClientModInitializer { } ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen) + ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen) ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen) ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen) diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt index deee792..2de4207 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalBlockEntity.kt @@ -52,7 +52,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc protected val inventoryCache = mutableMapOf() val internalBuffer = TerminalBufferInventory(18) - private val pendingRequests = LinkedList() + protected val pendingRequests = LinkedList() override val pendingInsertions = mutableListOf() override val dispatchStackTimeout = INSERTION_TIMEOUT @@ -190,7 +190,7 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc sendPacket(LocateStackPacket(stack, ipAddress)) } - private fun stackLocateRequestCompleted(request: StackLocateRequest) { + protected open fun stackLocateRequestCompleted(request: StackLocateRequest) { pendingRequests.remove(request) val sortedResults = request.results.toMutableList() @@ -252,19 +252,20 @@ abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>): DeviceBloc var bufferSlot by Delegates.notNull() } - data class StackLocateRequest( + open class StackLocateRequest( val stack: ItemStack, val amount: Int, val timestamp: Long, - var results: MutableSet> = mutableSetOf() ) { + var results: MutableSet> = mutableSetOf() + val totalResultAmount: Int get() = results.fold(0) { acc, (amount, _) -> acc + amount } fun isFinishable(currentTimestamp: Long): Boolean { // we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to // correctly sort by priority - return currentTimestamp - timestamp >= AbstractTerminalBlockEntity.LOCATE_REQUEST_TIMEOUT + return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalScreenHandler.kt index 41743d2..e52621a 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalScreenHandler.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/AbstractTerminalScreenHandler.kt @@ -56,6 +56,8 @@ abstract class AbstractTerminalScreenHandler( var itemsForDisplay = listOf() private set + open val xOffset: Int = 0 + init { if (!terminal.world!!.isClient) { assert(terminal.netItemObserver?.get() === null) @@ -63,29 +65,31 @@ abstract class AbstractTerminalScreenHandler( // intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings } + val xOffset = xOffset + // network for (y in 0 until 6) { for (x in 0 until 9) { - addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, 66 + x * 18, 18 + y * 18)) + addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, xOffset + 66 + x * 18, 18 + y * 18)) } } // internal buffer for (y in 0 until 6) { for (x in 0 until 3) { - addSlot(Slot(terminal.internalBuffer, y * 3 + x, 8 + x * 18, 18 + y * 18)) + addSlot(Slot(terminal.internalBuffer, y * 3 + x, xOffset + 8 + x * 18, 18 + y * 18)) } } // player inv for (y in 0 until 3) { for (x in 0 until 9) { - addSlot(Slot(playerInv, x + y * 9 + 9, 66 + x * 18, 140 + y * 18)) + addSlot(Slot(playerInv, x + y * 9 + 9, xOffset + 66 + x * 18, 140 + y * 18)) } } // hotbar for (x in 0 until 9) { - addSlot(Slot(playerInv, x, 66 + x * 18, 198)) + addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198)) } } diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlock.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlock.kt new file mode 100644 index 0000000..83afaf6 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlock.kt @@ -0,0 +1,18 @@ +package net.shadowfacts.phycon.block.terminal + +import net.minecraft.util.Identifier +import net.minecraft.world.BlockView +import net.shadowfacts.phycon.PhysicalConnectivity + +/** + * @author shadowfacts + */ +class CraftingTerminalBlock: AbstractTerminalBlock() { + + companion object { + val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal") + } + + override fun createBlockEntity(world: BlockView) = CraftingTerminalBlockEntity() + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlockEntity.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlockEntity.kt new file mode 100644 index 0000000..c71aabf --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalBlockEntity.kt @@ -0,0 +1,141 @@ +package net.shadowfacts.phycon.block.terminal + +import alexiil.mc.lib.attributes.item.ItemStackCollections +import alexiil.mc.lib.attributes.item.ItemStackUtil +import it.unimi.dsi.fastutil.objects.Object2IntMap +import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.inventory.CraftingInventory +import net.minecraft.inventory.SimpleInventory +import net.minecraft.item.ItemStack +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.PacketByteBuf +import net.minecraft.screen.ScreenHandler +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.text.Text +import net.minecraft.text.TranslatableText +import net.shadowfacts.phycon.init.PhyBlockEntities +import net.shadowfacts.phycon.packet.ItemStackPacket +import net.shadowfacts.phycon.packet.LocateStackPacket +import net.shadowfacts.phycon.packet.RequestInventoryPacket +import net.shadowfacts.phycon.util.fromTag +import net.shadowfacts.phycon.util.toTag +import java.util.LinkedList +import kotlin.math.min + +/** + * @author shadowfacts + */ +class CraftingTerminalBlockEntity: AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL) { + + val craftingInv = SimpleInventory(9) + + private val completedCraftingStackRequests = LinkedList() + + override fun onActivate(player: PlayerEntity) { + if (!world!!.isClient) { + updateAndSync() + + inventoryCache.clear() + sendPacket(RequestInventoryPacket(ipAddress)) + val factory = object: ExtendedScreenHandlerFactory { + override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? { + return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity) + } + + override fun getDisplayName() = TranslatableText("block.phycon.crafting_terminal") + + override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) { + buf.writeBlockPos(this@CraftingTerminalBlockEntity.pos) + } + } + player.openHandledScreen(factory) + } + } + + fun requestItemsForCrafting(maxAmount: Int) { + val amounts = ItemStackCollections.map() + + for (i in 0 until craftingInv.size()) { + val craftingInvStack = craftingInv.getStack(i) + if (craftingInvStack.isEmpty) continue + if (craftingInvStack.count >= craftingInvStack.maxCount) continue + + if (craftingInvStack !in amounts) amounts[craftingInvStack] = IntArray(9) { 0 } + amounts[craftingInvStack]!![i] = min(maxAmount, craftingInvStack.maxCount - craftingInvStack.count) + } + + for ((stack, amountPerSlot) in amounts) { + val total = amountPerSlot.sum() + val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot) + pendingRequests.add(request) + sendPacket(LocateStackPacket(stack, ipAddress)) + } + } + + override fun stackLocateRequestCompleted(request: StackLocateRequest) { + if (request is CraftingStackLocateRequest) { + completedCraftingStackRequests.add(request) + } + + super.stackLocateRequestCompleted(request) + } + + override fun doHandleItemStack(packet: ItemStackPacket): ItemStack { + val craftingReq = completedCraftingStackRequests.find { ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) } + if (craftingReq != null) { + var remaining = packet.stack.copy() + + for (i in 0 until craftingInv.size()) { + val currentStack = craftingInv.getStack(i) + if (currentStack.count >= currentStack.maxCount) continue + if (!ItemStackUtil.areEqualIgnoreAmounts(currentStack, remaining)) continue + + val toInsert = minOf(remaining.count, currentStack.maxCount - currentStack.count, craftingReq.amountPerSlot[i]) + currentStack.count += toInsert + remaining.count -= toInsert + craftingReq.amountPerSlot[i] -= toInsert + craftingReq.received += toInsert + + if (remaining.isEmpty) { + break + } + } + + if (craftingReq.amountPerSlot.sum() == 0 || craftingReq.received >= craftingReq.totalResultAmount) { + completedCraftingStackRequests.remove(craftingReq) + } + + if (!remaining.isEmpty) { + remaining = internalBuffer.insert(remaining, TerminalBufferInventory.Mode.FROM_NETWORK) + } + + updateAndSync() + + return remaining + } else { + return super.doHandleItemStack(packet) + } + } + + override fun toCommonTag(tag: CompoundTag) { + super.toCommonTag(tag) + tag.put("CraftingInv", craftingInv.toTag()) + } + + override fun fromCommonTag(tag: CompoundTag) { + super.fromCommonTag(tag) + craftingInv.fromTag(tag.getList("CraftingInv", 10)) + } + + class CraftingStackLocateRequest( + stack: ItemStack, + amount: Int, + timestamp: Long, + val amountPerSlot: IntArray, + ): StackLocateRequest(stack, amount, timestamp) { + var received: Int = 0 + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreen.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreen.kt new file mode 100644 index 0000000..106a375 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreen.kt @@ -0,0 +1,48 @@ +package net.shadowfacts.phycon.block.terminal + +import com.mojang.blaze3d.systems.RenderSystem +import net.minecraft.client.util.math.MatrixStack +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.shadowfacts.phycon.PhysicalConnectivity + +/** + * @author shadowfacts + */ +class CraftingTerminalScreen( + handler: CraftingTerminalScreenHandler, + playerInv: PlayerInventory, + title: Text, +): AbstractTerminalScreen( + handler, + playerInv, + title, + 259, + 252, +) { + + companion object { + private val BACKGROUND_1 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_1.png") + private val BACKGROUND_2 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_2.png") + } + + override val backgroundTexture = BACKGROUND_1 + + override fun createViewController(): AbstractTerminalViewController<*, *, *> { + return CraftingTerminalViewController(this, handler) + } + + override fun drawBackgroundTexture(matrixStack: MatrixStack) { + RenderSystem.color4f(1f, 1f, 1f, 1f) + client!!.textureManager.bindTexture(BACKGROUND_1) + val x = (width - backgroundWidth) / 2 + val y = (height - backgroundHeight) / 2 + drawTexture(matrixStack, x, y, 0, 0, 256, 252) + + client!!.textureManager.bindTexture(BACKGROUND_2) + drawTexture(matrixStack, x + 256, y, 0, 0, 3, 252) + } + + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreenHandler.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreenHandler.kt new file mode 100644 index 0000000..6998c63 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalScreenHandler.kt @@ -0,0 +1,130 @@ +package net.shadowfacts.phycon.block.terminal + +import alexiil.mc.lib.attributes.item.ItemStackCollections +import net.minecraft.entity.player.PlayerInventory +import net.minecraft.inventory.CraftingInventory +import net.minecraft.inventory.CraftingResultInventory +import net.minecraft.inventory.Inventory +import net.minecraft.item.ItemStack +import net.minecraft.network.PacketByteBuf +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket +import net.minecraft.recipe.RecipeFinder +import net.minecraft.recipe.RecipeType +import net.minecraft.screen.slot.CraftingResultSlot +import net.minecraft.screen.slot.Slot +import net.minecraft.server.network.ServerPlayerEntity +import net.shadowfacts.phycon.init.PhyBlocks +import net.shadowfacts.phycon.init.PhyScreens + +/** + * @author shadowfacts + */ +class CraftingTerminalScreenHandler( + syncId: Int, + playerInv: PlayerInventory, + terminal: CraftingTerminalBlockEntity, +): AbstractTerminalScreenHandler(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) { + + val craftingInv = CraftingInv(this) + val result = CraftingResultInventory() + val resultSlot: CraftingResultSlot + + override val xOffset: Int + get() = 5 + + constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf): + this( + syncId, + playerInv, + PhyBlocks.CRAFTING_TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!! + ) + + init { + for (y in 0 until 3) { + for (x in 0 until 3) { + this.addSlot(Slot(craftingInv, x + y * 3, 13 + x * 18, 140 + y * 18)) + } + } + + resultSlot = CraftingResultSlot(playerInv.player, craftingInv, result, 0, 31, 224) + addSlot(resultSlot) + + updateCraftingResult() + } + + override fun onContentChanged(inventory: Inventory?) { + updateCraftingResult() + } + + private fun updateCraftingResult() { + val world = playerInv.player.world + if (!world.isClient) { + val player = playerInv.player as ServerPlayerEntity + val recipe = world.server!!.recipeManager.getFirstMatch(RecipeType.CRAFTING, craftingInv, world) + val resultStack = + if (recipe.isPresent && result.shouldCraftRecipe(world, player, recipe.get())) { + recipe.get().craft(craftingInv) + } else { + ItemStack.EMPTY + } + result.setStack(0, resultStack) + player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, resultSlot.id, resultStack)) + } + } + + fun clearCraftingGrid() { + assert(!playerInv.player.world.isClient) + for (i in 0 until terminal.craftingInv.size()) { + val craftingInvStack = terminal.craftingInv.getStack(i) + if (craftingInvStack.isEmpty) continue + val remainder = terminal.internalBuffer.insert(craftingInvStack, TerminalBufferInventory.Mode.TO_NETWORK) + terminal.craftingInv.setStack(i, remainder) + } + updateCraftingResult() + sendContentUpdates() + } + + fun requestMoreCraftingIngredients(maxAmount: Int) { + assert(!playerInv.player.world.isClient) + terminal.requestItemsForCrafting(maxAmount) + } + + // RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so... + class CraftingInv(val handler: CraftingTerminalScreenHandler): CraftingInventory(handler, 3, 3) { + private val backing = handler.terminal.craftingInv + + override fun isEmpty(): Boolean { + return backing.isEmpty + } + + override fun getStack(i: Int): ItemStack { + return backing.getStack(i) + } + + override fun removeStack(i: Int): ItemStack { + return backing.removeStack(i) + } + + override fun removeStack(i: Int, j: Int): ItemStack { + val res = backing.removeStack(i, j) + if (!res.isEmpty) { + handler.onContentChanged(this) + } + return res + } + + override fun setStack(i: Int, itemStack: ItemStack?) { + backing.setStack(i, itemStack) + handler.onContentChanged(this) + } + + override fun clear() { + backing.clear() + } + + override fun provideRecipeInputs(finder: RecipeFinder) { + TODO() + } + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalViewController.kt b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalViewController.kt new file mode 100644 index 0000000..4489dd7 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/block/terminal/CraftingTerminalViewController.kt @@ -0,0 +1,100 @@ +package net.shadowfacts.phycon.block.terminal + +import net.minecraft.client.MinecraftClient +import net.minecraft.client.util.InputUtil +import net.minecraft.text.TranslatableText +import net.minecraft.util.Identifier +import net.shadowfacts.cacao.geometry.Size +import net.shadowfacts.cacao.util.Color +import net.shadowfacts.cacao.util.LayoutGuide +import net.shadowfacts.cacao.util.texture.Texture +import net.shadowfacts.cacao.view.Label +import net.shadowfacts.cacao.view.TextureView +import net.shadowfacts.cacao.view.button.Button +import net.shadowfacts.kiwidsl.dsl +import net.shadowfacts.phycon.PhysicalConnectivity +import net.shadowfacts.phycon.networking.C2STerminalCraftingButton +import org.lwjgl.glfw.GLFW + +/** + * @author shadowfacts + */ +class CraftingTerminalViewController( + screen: CraftingTerminalScreen, + handler: CraftingTerminalScreenHandler, +): AbstractTerminalViewController( + screen, + handler, +) { + + companion object { + val SMALL_BUTTON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 0, 48) + val SMALL_BUTTON_HOVERED = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 16, 48) + val CLEAR_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 32, 48) + val PLUS_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 48, 48) + } + + lateinit var craftingInv: LayoutGuide + + override fun viewDidLoad() { + super.viewDidLoad() + + craftingInv = view.addLayoutGuide() + view.solver.dsl { + craftingInv.leftAnchor equalTo buffer.leftAnchor + craftingInv.topAnchor equalTo playerInv.topAnchor + craftingInv.widthAnchor equalTo buffer.widthAnchor + craftingInv.heightAnchor equalTo 54 + } + + val craftingLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_crafting"))).apply { + textColor = Color.TEXT + } + view.solver.dsl { + craftingLabel.leftAnchor equalTo craftingInv.leftAnchor + craftingLabel.topAnchor equalTo playerInvLabel.topAnchor + } + + val clearIcon = TextureView(CLEAR_ICON).apply { + intrinsicContentSize = Size(3.0,3.0) + } + val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply { + background = TextureView(SMALL_BUTTON) + hoveredBackground = TextureView(SMALL_BUTTON_HOVERED) + tooltip = TranslatableText("gui.phycon.terminal.clear_crafting") + } + view.solver.dsl { + clearButton.topAnchor equalTo craftingInv.topAnchor + clearButton.leftAnchor equalTo (pane.leftAnchor + 4) + } + + val plusIcon = TextureView(PLUS_ICON).apply { + intrinsicContentSize = Size(3.0, 3.0) + } + val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply { + background= TextureView(SMALL_BUTTON) + hoveredBackground = TextureView(SMALL_BUTTON_HOVERED) + tooltip = TranslatableText("gui.phycon.terminal.more_crafting") + } + view.solver.dsl { + plusButton.topAnchor equalTo (clearButton.bottomAnchor + 2) + plusButton.leftAnchor equalTo clearButton.leftAnchor + } + } + + private fun clearPressed(button: Button) { + MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, C2STerminalCraftingButton.Action.CLEAR_GRID)) + } + + private fun plusPressed(button: Button) { + val client = MinecraftClient.getInstance() + val action = + if (InputUtil.isKeyPressed(client.window.handle, GLFW.GLFW_KEY_LEFT_SHIFT) || InputUtil.isKeyPressed(client.window.handle, GLFW.GLFW_KEY_RIGHT_SHIFT)) { + C2STerminalCraftingButton.Action.REQUEST_MAX_MORE + } else { + C2STerminalCraftingButton.Action.REQUEST_ONE_MORE + } + client.player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, action)) + } + +} diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt index 1bb1e11..9261c26 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlockEntities.kt @@ -19,6 +19,8 @@ import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlockEntity import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlockEntity +import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock +import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlockEntity import net.shadowfacts.phycon.block.terminal.TerminalBlock import net.shadowfacts.phycon.block.terminal.TerminalBlockEntity @@ -29,6 +31,7 @@ object PhyBlockEntities { val INTERFACE = create(::InterfaceBlockEntity, PhyBlocks.INTERFACE) val TERMINAL = create(::TerminalBlockEntity, PhyBlocks.TERMINAL) + val CRAFTING_TERMINAL = create(::CraftingTerminalBlockEntity, PhyBlocks.CRAFTING_TERMINAL) val SWITCH = create(::SwitchBlockEntity, PhyBlocks.SWITCH) val EXTRACTOR = create(::ExtractorBlockEntity, PhyBlocks.EXTRACTOR) val INSERTER = create(::InserterBlockEntity, PhyBlocks.INSERTER) @@ -43,6 +46,7 @@ object PhyBlockEntities { fun init() { register(InterfaceBlock.ID, INTERFACE) register(TerminalBlock.ID, TERMINAL) + register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL) register(SwitchBlock.ID, SWITCH) register(ExtractorBlock.ID, EXTRACTOR) register(InserterBlock.ID, INSERTER) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt index c59d5d4..1a0cefa 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyBlocks.kt @@ -13,6 +13,7 @@ import net.shadowfacts.phycon.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.block.netswitch.SwitchBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock +import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock import net.shadowfacts.phycon.block.terminal.TerminalBlock /** @@ -24,6 +25,7 @@ object PhyBlocks { val INTERFACE = InterfaceBlock() val TERMINAL = TerminalBlock() + val CRAFTING_TERMINAL = CraftingTerminalBlock() val SWITCH = SwitchBlock() val EXTRACTOR = ExtractorBlock() val INSERTER = InserterBlock() @@ -38,6 +40,7 @@ object PhyBlocks { register(InterfaceBlock.ID, INTERFACE) register(TerminalBlock.ID, TERMINAL) + register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL) register(SwitchBlock.ID, SWITCH) register(ExtractorBlock.ID, EXTRACTOR) register(InserterBlock.ID, INSERTER) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt index 87598dd..dad46de 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyItems.kt @@ -14,6 +14,7 @@ import net.shadowfacts.phycon.block.netinterface.InterfaceBlock import net.shadowfacts.phycon.block.netswitch.SwitchBlock import net.shadowfacts.phycon.block.redstone_controller.RedstoneControllerBlock import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterBlock +import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock import net.shadowfacts.phycon.block.terminal.TerminalBlock import net.shadowfacts.phycon.item.DeviceBlockItem import net.shadowfacts.phycon.item.FaceDeviceBlockItem @@ -29,6 +30,7 @@ object PhyItems { val INTERFACE = FaceDeviceBlockItem(PhyBlocks.INTERFACE, Item.Settings()) val TERMINAL = DeviceBlockItem(PhyBlocks.TERMINAL, Item.Settings()) + val CRAFTING_TERMINAL = DeviceBlockItem(PhyBlocks.CRAFTING_TERMINAL, Item.Settings()) val SWITCH = BlockItem(PhyBlocks.SWITCH, Item.Settings()) val EXTRACTOR = FaceDeviceBlockItem(PhyBlocks.EXTRACTOR, Item.Settings()) val INSERTER = FaceDeviceBlockItem(PhyBlocks.INSERTER, Item.Settings()) @@ -52,6 +54,7 @@ object PhyItems { register(InterfaceBlock.ID, INTERFACE) register(TerminalBlock.ID, TERMINAL) + register(CraftingTerminalBlock.ID, CRAFTING_TERMINAL) register(SwitchBlock.ID, SWITCH) register(ExtractorBlock.ID, EXTRACTOR) register(InserterBlock.ID, INSERTER) diff --git a/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt b/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt index dc228cc..91a84f4 100644 --- a/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt +++ b/src/main/kotlin/net/shadowfacts/phycon/init/PhyScreens.kt @@ -4,6 +4,8 @@ import net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry import net.minecraft.screen.ScreenHandlerType import net.shadowfacts.phycon.block.inserter.InserterScreenHandler import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreenHandler +import net.shadowfacts.phycon.block.terminal.CraftingTerminalBlock +import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler import net.shadowfacts.phycon.block.terminal.TerminalBlock import net.shadowfacts.phycon.block.terminal.TerminalScreenHandler @@ -11,6 +13,8 @@ object PhyScreens { lateinit var TERMINAL: ScreenHandlerType private set + lateinit var CRAFTING_TERMINAL: ScreenHandlerType + private set lateinit var INSERTER: ScreenHandlerType private set lateinit var REDSTONE_EMITTER: ScreenHandlerType @@ -18,6 +22,7 @@ object PhyScreens { fun init() { TERMINAL = ScreenHandlerRegistry.registerExtended(TerminalBlock.ID, ::TerminalScreenHandler) + CRAFTING_TERMINAL = ScreenHandlerRegistry.registerExtended(CraftingTerminalBlock.ID, ::CraftingTerminalScreenHandler) INSERTER = ScreenHandlerRegistry.registerExtended(InserterScreenHandler.ID, ::InserterScreenHandler) REDSTONE_EMITTER = ScreenHandlerRegistry.registerExtended(RedstoneEmitterScreenHandler.ID, ::RedstoneEmitterScreenHandler) } diff --git a/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalCraftingButton.kt b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalCraftingButton.kt new file mode 100644 index 0000000..978d649 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/phycon/networking/C2STerminalCraftingButton.kt @@ -0,0 +1,56 @@ +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.block.terminal.CraftingTerminalBlockEntity +import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreenHandler + +/** + * @author shadowfacts + */ +object C2STerminalCraftingButton: ServerReceiver { + override val CHANNEL = Identifier(PhysicalConnectivity.MODID, "terminal_crafting_button") + + enum class Action { + CLEAR_GRID, + REQUEST_ONE_MORE, + REQUEST_MAX_MORE, + } + + operator fun invoke(terminal: CraftingTerminalBlockEntity, action: Action): Packet<*> { + val buf = PacketByteBufs.create() + + buf.writeIdentifier(terminal.world!!.registryKey.value) + buf.writeBlockPos(terminal.pos) + buf.writeByte(action.ordinal) + + 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 action = Action.values()[buf.readByte().toInt()] + + server.execute { + val key = RegistryKey.of(Registry.DIMENSION, dimID) + val screenHandler = player.currentScreenHandler as? CraftingTerminalScreenHandler ?: return@execute + if (screenHandler.terminal.pos != pos || screenHandler.terminal.world!!.registryKey != key) return@execute + + when (action) { + Action.CLEAR_GRID -> screenHandler.clearCraftingGrid() + Action.REQUEST_ONE_MORE -> screenHandler.requestMoreCraftingIngredients(1) + Action.REQUEST_MAX_MORE -> screenHandler.requestMoreCraftingIngredients(64) + } + } + } +} diff --git a/src/main/resources/assets/phycon/lang/en_us.json b/src/main/resources/assets/phycon/lang/en_us.json index 3cf0cf9..6f84be5 100644 --- a/src/main/resources/assets/phycon/lang/en_us.json +++ b/src/main/resources/assets/phycon/lang/en_us.json @@ -2,6 +2,7 @@ "block.phycon.switch": "Network Switch", "block.phycon.network_interface": "Inventory Interface", "block.phycon.terminal": "Terminal", + "block.phycon.crafting_terminal": "Crafting Terminal", "block.phycon.cable_white": "White Cable", "block.phycon.cable_orange": "Orange Cable", "block.phycon.cable_magenta": "Magenta Cable", @@ -35,6 +36,9 @@ "gui.phycon.terminal_buffer": "Buffer", "gui.phycon.terminal_network": "Network", + "gui.phycon.terminal_crafting": "Crafting", + "gui.phycon.terminal.clear_crafting": "Clear crafting table", + "gui.phycon.terminal.more_crafting": "Request more ingredients", "gui.phycon.console.details": "Device Details", "gui.phycon.console.details.ip": "IP Address: %s", "gui.phycon.console.details.mac": "MAC Address: %s", diff --git a/src/main/resources/assets/phycon/textures/gui/crafting_terminal_1.png b/src/main/resources/assets/phycon/textures/gui/crafting_terminal_1.png new file mode 100644 index 0000000..7e15698 Binary files /dev/null and b/src/main/resources/assets/phycon/textures/gui/crafting_terminal_1.png differ diff --git a/src/main/resources/assets/phycon/textures/gui/crafting_terminal_2.png b/src/main/resources/assets/phycon/textures/gui/crafting_terminal_2.png new file mode 100644 index 0000000..f8a037d Binary files /dev/null and b/src/main/resources/assets/phycon/textures/gui/crafting_terminal_2.png differ diff --git a/src/main/resources/assets/phycon/textures/gui/icons.png b/src/main/resources/assets/phycon/textures/gui/icons.png index 7e7d627..541afe7 100644 Binary files a/src/main/resources/assets/phycon/textures/gui/icons.png and b/src/main/resources/assets/phycon/textures/gui/icons.png differ