From 4cf57ee42f05d75ed12ac113d3b17003527bee2e Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Tue, 10 Jun 2025 03:01:34 -0400 Subject: [PATCH 1/5] Add Keybind to pull looked-at item into inventory from AE2 System --- .../github/loader/ChannelLoader.java | 6 + .../github/loader/KeybindLoader.java | 41 +++ .../network/CPacketPickBlockWithdraw.java | 300 ++++++++++++++++++ .../resources/assets/ae2fc/lang/en_US.lang | 1 + 4 files changed, 348 insertions(+) create mode 100644 src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java diff --git a/src/main/java/com/glodblock/github/loader/ChannelLoader.java b/src/main/java/com/glodblock/github/loader/ChannelLoader.java index 87fd2f96..69fa9088 100644 --- a/src/main/java/com/glodblock/github/loader/ChannelLoader.java +++ b/src/main/java/com/glodblock/github/loader/ChannelLoader.java @@ -15,6 +15,7 @@ import com.glodblock.github.network.CPacketLevelMaintainer; import com.glodblock.github.network.CPacketLevelTerminalCommands; import com.glodblock.github.network.CPacketPatternMultiSet; import com.glodblock.github.network.CPacketPatternValueSet; +import com.glodblock.github.network.CPacketPickBlockWithdraw; import com.glodblock.github.network.CPacketRenamer; import com.glodblock.github.network.CPacketSwitchGuis; import com.glodblock.github.network.CPacketTransferRecipe; @@ -93,6 +94,11 @@ public class ChannelLoader implements Runnable { SPacketLevelMaintainerGuiUpdate.class, id++, Side.CLIENT); + netHandler.registerMessage( + new CPacketPickBlockWithdraw.Handler(), + CPacketPickBlockWithdraw.class, + id++, + Side.SERVER); } public static void sendPacketToAllPlayers(Packet packet, World world) { diff --git a/src/main/java/com/glodblock/github/loader/KeybindLoader.java b/src/main/java/com/glodblock/github/loader/KeybindLoader.java index 81654539..3d4f9629 100644 --- a/src/main/java/com/glodblock/github/loader/KeybindLoader.java +++ b/src/main/java/com/glodblock/github/loader/KeybindLoader.java @@ -4,12 +4,15 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityClientPlayerMP; import net.minecraft.client.settings.KeyBinding; import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.MovingObjectPosition; import org.apache.commons.lang3.tuple.ImmutablePair; import org.lwjgl.input.Keyboard; import com.glodblock.github.FluidCraft; import com.glodblock.github.common.item.ItemWirelessUltraTerminal; +import com.glodblock.github.network.CPacketPickBlockWithdraw; import com.glodblock.github.network.CPacketValueConfig; import com.glodblock.github.util.Util; @@ -25,13 +28,16 @@ public class KeybindLoader implements Runnable { public static KeyBinding openTerminal; public static KeyBinding restock; + public static KeyBinding pickBlock; @Override public void run() { openTerminal = new KeyBinding(FluidCraft.MODID + ".key.OpenTerminal", Keyboard.CHAR_NONE, "itemGroup.ae2fc"); restock = new KeyBinding(FluidCraft.MODID + ".key.Restock", Keyboard.CHAR_NONE, "itemGroup.ae2fc"); + pickBlock = new KeyBinding(FluidCraft.MODID + ".key.PickBlock", Keyboard.CHAR_NONE, "itemGroup.ae2fc"); ClientRegistry.registerKeyBinding(openTerminal); ClientRegistry.registerKeyBinding(restock); + ClientRegistry.registerKeyBinding(pickBlock); FMLCommonHandler.instance().bus().register(this); } @@ -42,6 +48,8 @@ public class KeybindLoader implements Runnable { handleOpenTerminalKey(); } else if (restock.isPressed()) { handleRestockKey(); + } else if (pickBlock.isPressed()) { + handlePickBlockKey(); } } @@ -52,6 +60,8 @@ public class KeybindLoader implements Runnable { handleOpenTerminalKey(); } else if (restock.isPressed()) { handleRestockKey(); + } else if (pickBlock.isPressed()) { + handlePickBlockKey(); } } @@ -82,4 +92,35 @@ public class KeybindLoader implements Runnable { FluidCraft.proxy.netHandler.sendToServer(new CPacketValueConfig(0, 1)); } } + + private void handlePickBlockKey() { + Minecraft minecraft = Minecraft.getMinecraft(); + if (minecraft.currentScreen != null) return; // Don't act if a GUI is open + + EntityClientPlayerMP player = minecraft.thePlayer; + if (player == null) return; + + // Ensure the player has the wireless terminal + ImmutablePair terminalAndInventorySlot = Util.getUltraWirelessTerm(player); + if (terminalAndInventorySlot == null) { + player.addChatMessage(new ChatComponentText("Could not find wireless terminal.")); + return; + } + + ItemStack terminal = terminalAndInventorySlot.getRight(); + if (terminal == null || !(terminal.getItem() instanceof ItemWirelessUltraTerminal)) { + player.addChatMessage(new ChatComponentText("Terminal must be universal version.")); + return; + } + + // Get the block the player is currently looking at + MovingObjectPosition movingObject = minecraft.objectMouseOver; + if (movingObject == null || movingObject.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) { + return; + } + + // Send packet to server with block coordinates + FluidCraft.proxy.netHandler.sendToServer( + new CPacketPickBlockWithdraw(movingObject.blockX, movingObject.blockY, movingObject.blockZ)); + } } diff --git a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java new file mode 100644 index 00000000..3db8e0aa --- /dev/null +++ b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java @@ -0,0 +1,300 @@ +package com.glodblock.github.network; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.init.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.server.S09PacketHeldItemChange; +import net.minecraft.world.World; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +import com.glodblock.github.common.item.ItemWirelessUltraTerminal; +import com.glodblock.github.inventory.item.WirelessCraftingTerminalInventory; +import com.glodblock.github.util.Util; + +import appeng.api.AEApi; +import appeng.api.networking.IGridNode; +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IAEStack; +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import io.netty.buffer.ByteBuf; + +/** + * Network packet sent from client to server when a player looks at a block and requests to withdraw that block type + * from their wireless terminal's ME network. + * + *

+ * This packet handles the entire withdrawal flow including: + *

+ * + *

+ * The packet carries the coordinates of the block the player is looking at, which determines what item type to withdraw + * from the ME network. + */ +public class CPacketPickBlockWithdraw implements IMessage { + + private int blockX; + private int blockY; + private int blockZ; + + public CPacketPickBlockWithdraw() { + // Required for FML + } + + public CPacketPickBlockWithdraw(int x, int y, int z) { + this.blockX = x; + this.blockY = y; + this.blockZ = z; + } + + @Override + public void fromBytes(ByteBuf buf) { + this.blockX = buf.readInt(); + this.blockY = buf.readInt(); + this.blockZ = buf.readInt(); + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(this.blockX); + buf.writeInt(this.blockY); + buf.writeInt(this.blockZ); + } + + public static class Handler implements IMessageHandler { + + @Override + public IMessage onMessage(CPacketPickBlockWithdraw message, MessageContext ctx) { + EntityPlayerMP player = ctx.getServerHandler().playerEntity; + if (player == null || player.inventory == null) { + return null; + } + World world = player.worldObj; + + // Ensure the player has the wireless terminal + ImmutablePair terminalAndInventorySlot = Util.getUltraWirelessTerm(player); + if (terminalAndInventorySlot == null) { + return null; + } + + ItemStack terminalStack = terminalAndInventorySlot.getRight(); + if (terminalStack == null || !(terminalStack.getItem() instanceof ItemWirelessUltraTerminal)) { + return null; + } + + IGridNode wirelessGrid = Util.getWirelessGrid(terminalStack); + if (wirelessGrid == null) { + return null; + } + + // Create the terminal inventory handler + WirelessCraftingTerminalInventory terminalInventory = new WirelessCraftingTerminalInventory( + terminalStack, + terminalAndInventorySlot.getLeft(), + wirelessGrid, + player); + + // Get the target block + Block targetBlock = world.getBlock(message.blockX, message.blockY, message.blockZ); + if (targetBlock == Blocks.air) { + return null; // Don't try to withdraw air + } + + Item targetItem = Item.getItemFromBlock(targetBlock); + if (targetItem == null) { + return null; // Should not happen for non-air blocks + } + + // Check player inventory for existing stacks to determine how much to withdraw. + ItemStack itemToFind = new ItemStack( + targetItem, + 1, + world.getBlockMetadata(message.blockX, message.blockY, message.blockZ)); + + // 1. Initial Scan for full/partial stacks + int fullStackSlot = -1; + List partialStackSlotsList = new ArrayList<>(); + for (int i = 0; i < player.inventory.mainInventory.length; i++) { + ItemStack stackInSlot = player.inventory.mainInventory[i]; + if (stackInSlot != null && stackInSlot.isItemEqual(itemToFind) + && ItemStack.areItemStackTagsEqual(stackInSlot, itemToFind)) { + if (stackInSlot.stackSize >= stackInSlot.getMaxStackSize()) { + fullStackSlot = i; + break; // Found a full stack, no need to do anything. + } + partialStackSlotsList.add(i); + } + } + + // 2. If a full stack already exists, put in active slot and return. + if (fullStackSlot >= 0) { + setSlotAsActiveSlot(player, fullStackSlot); + return null; + } + + // 3. If there are no partial stacks and the player's inventory is full, + // then return since we cannot add a retrieved stack to a full inventory + int nextEmptySlot = getFirstEmptyStackReverse(player.inventory); + if (partialStackSlotsList.isEmpty() && nextEmptySlot == -1) { + return null; + } + + // 4. Consolidate if multiple partial stacks exist. + ItemStack consolidatedStack = null; + int consolidatedStackSlot = -1; + for (Integer partialStackSlot : partialStackSlotsList) { + if (consolidatedStack == null) { + consolidatedStack = player.inventory.getStackInSlot(partialStackSlot); + consolidatedStackSlot = partialStackSlot; + } else { + consolidateItemStacks(player.inventory, partialStackSlot, consolidatedStackSlot); + } + + // Check if we created a full stack of items + if (consolidatedStack.stackSize == consolidatedStack.getMaxStackSize()) { + setSlotAsActiveSlot(player, consolidatedStackSlot); + return null; + } + } + + // Ensure the wireless terminal is within network range, + // if not, move consolidated stack to the active slot and return + if (!Util.rangeCheck(terminalStack, player, wirelessGrid)) { + setSlotAsActiveSlot(player, consolidatedStackSlot); + + // TODO: Return out of network range player message. + return null; + } + + // 5. Calculate withdrawal amount + int amountToWithdraw = consolidatedStack == null ? itemToFind.getMaxStackSize() + : itemToFind.getMaxStackSize() - consolidatedStack.stackSize; + if (amountToWithdraw <= 0) { + return null; + } + + // Create an IAEItemStack for the target block with the calculated amount + ItemStack targetItemStack = itemToFind.copy(); + targetItemStack.stackSize = amountToWithdraw; + IAEItemStack targetAeItemStack = AEApi.instance().storage().createItemStack(targetItemStack); + if (targetAeItemStack == null) { + return null; + } + + // 6. Extract items from the network + IAEStack extractedStack = terminalInventory.extractItems(targetAeItemStack); + if (extractedStack instanceof IAEItemStack extractedAeItemStack && extractedStack.getStackSize() > 0) { + ItemStack itemsToGive = extractedAeItemStack.getItemStack(); + // Update the player's inventory with the withdrawn items + if (itemsToGive != null && itemsToGive.stackSize > 0) { + if (consolidatedStack == null) { + player.inventory.setInventorySlotContents(nextEmptySlot, itemsToGive); + } else { + consolidatedStack.stackSize += itemsToGive.stackSize; + } + player.inventory.markDirty(); + } + } + + // If the target stack is already in the player's hotbar, set that as the active slot. + // Otherwise, move the target stack to the active slot. + int slotToSwap = consolidatedStack == null ? nextEmptySlot : consolidatedStackSlot; + setSlotAsActiveSlot(player, slotToSwap); + + return null; // No reply packet needed + } + + private int getFirstEmptyStackReverse(InventoryPlayer inventory) { + for (int i = inventory.mainInventory.length - 1; i >= 0; --i) { + if (inventory.mainInventory[i] == null) { + return i; + } + } + + return -1; + } + + private void setSlotAsActiveSlot(EntityPlayerMP player, int slot) { + if (slot >= 0 && slot <= 8) { + player.inventory.currentItem = slot; + player.playerNetServerHandler.sendPacket(new S09PacketHeldItemChange(slot)); + } else { + swapInventorySlots(player.inventory, player.inventory.currentItem, slot); + } + player.inventory.markDirty(); + } + + /** + * Moves an ItemStack from a source slot to a destination slot in the player's inventory. If the destination + * slot is occupied, the items are swapped. + * + * @param inventory The player's inventory. + * @param slot1 The index of the first ItemStack to move. + * @param slot2 The index of the second ItemStack to move. + */ + private void swapInventorySlots(InventoryPlayer inventory, int slot1, int slot2) { + if (slot1 == slot2) { + return; + } + + // Get the stacks from both slots + ItemStack sourceStack = inventory.getStackInSlot(slot1); + ItemStack destinationStack = inventory.getStackInSlot(slot2); + + // Set the destination slot with the source stack (even if it's null) + inventory.setInventorySlotContents(slot2, sourceStack); + + // Set the source slot with the original destination stack + inventory.setInventorySlotContents(slot1, destinationStack); + + // Mark the inventory as dirty to ensure changes are saved and synced + inventory.markDirty(); + } + + /** + * Consolidate ItemStacks from a source slot to a destination slot in the player's inventory. + * + * @param inventory The player's inventory. + * @param sourceSlot The index of the slot to move the item from. + * @param destinationSlot The index of the slot to move the item to. + */ + private void consolidateItemStacks(InventoryPlayer inventory, int sourceSlot, int destinationSlot) { + ItemStack sourceStack = inventory.getStackInSlot(sourceSlot); + ItemStack destinationStack = inventory.getStackInSlot(destinationSlot); + + if (!sourceStack.isItemEqual(destinationStack) + || !ItemStack.areItemStackTagsEqual(sourceStack, destinationStack)) { + return; + } + + int missingQuantity = destinationStack.getMaxStackSize() - destinationStack.stackSize; + if (missingQuantity >= sourceStack.stackSize) { + destinationStack.stackSize = destinationStack.stackSize + sourceStack.stackSize; + sourceStack = null; + } else { + sourceStack.stackSize -= missingQuantity; + destinationStack.stackSize += missingQuantity; + } + + // Update the inventory stacks + inventory.setInventorySlotContents(destinationSlot, destinationStack); + inventory.setInventorySlotContents(sourceSlot, sourceStack); + + // Mark the inventory as dirty to ensure changes are saved and synced + inventory.markDirty(); + } + } +} diff --git a/src/main/resources/assets/ae2fc/lang/en_US.lang b/src/main/resources/assets/ae2fc/lang/en_US.lang index 8a17a5ad..8cf25054 100644 --- a/src/main/resources/assets/ae2fc/lang/en_US.lang +++ b/src/main/resources/assets/ae2fc/lang/en_US.lang @@ -219,6 +219,7 @@ error.unknown=Unknown ae2fc.key.OpenTerminal=Open Wireless Universal Terminal ae2fc.key.Restock=Toggle Restock +ae2fc.key.PickBlock=AE2 Pick Block itemGroup.ae2fc=Fluid Craft For AE2 From aea1ce8dd0b6bf81a56f13caa53be552d0e81231 Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Thu, 4 Dec 2025 17:02:08 -0500 Subject: [PATCH 2/5] Create and use Util function to get any wireless terminal --- .../github/loader/KeybindLoader.java | 6 +-- .../network/CPacketPickBlockWithdraw.java | 44 ++++++++----------- .../java/com/glodblock/github/util/Util.java | 26 +++++++++++ 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/glodblock/github/loader/KeybindLoader.java b/src/main/java/com/glodblock/github/loader/KeybindLoader.java index 3d4f9629..585f215b 100644 --- a/src/main/java/com/glodblock/github/loader/KeybindLoader.java +++ b/src/main/java/com/glodblock/github/loader/KeybindLoader.java @@ -101,15 +101,15 @@ public class KeybindLoader implements Runnable { if (player == null) return; // Ensure the player has the wireless terminal - ImmutablePair terminalAndInventorySlot = Util.getUltraWirelessTerm(player); + ImmutablePair terminalAndInventorySlot = Util.getWirelessTerminal(player); if (terminalAndInventorySlot == null) { player.addChatMessage(new ChatComponentText("Could not find wireless terminal.")); return; } ItemStack terminal = terminalAndInventorySlot.getRight(); - if (terminal == null || !(terminal.getItem() instanceof ItemWirelessUltraTerminal)) { - player.addChatMessage(new ChatComponentText("Terminal must be universal version.")); + if (terminal == null) { + player.addChatMessage(new ChatComponentText("Could not find wireless terminal.")); return; } diff --git a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java index 3db8e0aa..1049e62e 100644 --- a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java +++ b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java @@ -14,12 +14,13 @@ import net.minecraft.world.World; import org.apache.commons.lang3.tuple.ImmutablePair; -import com.glodblock.github.common.item.ItemWirelessUltraTerminal; -import com.glodblock.github.inventory.item.WirelessCraftingTerminalInventory; import com.glodblock.github.util.Util; import appeng.api.AEApi; -import appeng.api.networking.IGridNode; +import appeng.api.config.Actionable; +import appeng.api.networking.security.PlayerSource; +import appeng.api.storage.IMEInventoryHandler; +import appeng.api.storage.StorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IAEStack; import cpw.mods.fml.common.network.simpleimpl.IMessage; @@ -84,29 +85,28 @@ public class CPacketPickBlockWithdraw implements IMessage { } World world = player.worldObj; - // Ensure the player has the wireless terminal - ImmutablePair terminalAndInventorySlot = Util.getUltraWirelessTerm(player); + // Ensure the player has a wireless terminal + ImmutablePair terminalAndInventorySlot = Util.getWirelessTerminal(player); if (terminalAndInventorySlot == null) { return null; } - ItemStack terminalStack = terminalAndInventorySlot.getRight(); - if (terminalStack == null || !(terminalStack.getItem() instanceof ItemWirelessUltraTerminal)) { + ItemStack terminalItemStack = terminalAndInventorySlot.getRight(); + if (terminalItemStack == null) { return null; } - IGridNode wirelessGrid = Util.getWirelessGrid(terminalStack); - if (wirelessGrid == null) { + // Obtain the inventory handler for the AE2 wireless terminal. + // + // We can safely suppress this warning, as we are the one providing the storage type in + // the function call. + @SuppressWarnings("unchecked") + IMEInventoryHandler terminalInventory = (IMEInventoryHandler) Util + .getWirelessInv(terminalItemStack, player, StorageChannel.ITEMS); + if (terminalInventory == null) { return null; } - // Create the terminal inventory handler - WirelessCraftingTerminalInventory terminalInventory = new WirelessCraftingTerminalInventory( - terminalStack, - terminalAndInventorySlot.getLeft(), - wirelessGrid, - player); - // Get the target block Block targetBlock = world.getBlock(message.blockX, message.blockY, message.blockZ); if (targetBlock == Blocks.air) { @@ -170,15 +170,6 @@ public class CPacketPickBlockWithdraw implements IMessage { } } - // Ensure the wireless terminal is within network range, - // if not, move consolidated stack to the active slot and return - if (!Util.rangeCheck(terminalStack, player, wirelessGrid)) { - setSlotAsActiveSlot(player, consolidatedStackSlot); - - // TODO: Return out of network range player message. - return null; - } - // 5. Calculate withdrawal amount int amountToWithdraw = consolidatedStack == null ? itemToFind.getMaxStackSize() : itemToFind.getMaxStackSize() - consolidatedStack.stackSize; @@ -195,7 +186,8 @@ public class CPacketPickBlockWithdraw implements IMessage { } // 6. Extract items from the network - IAEStack extractedStack = terminalInventory.extractItems(targetAeItemStack); + PlayerSource source = new PlayerSource(player, null); + IAEStack extractedStack = terminalInventory.extractItems(targetAeItemStack, Actionable.MODULATE, source); if (extractedStack instanceof IAEItemStack extractedAeItemStack && extractedStack.getStackSize() > 0) { ItemStack itemsToGive = extractedAeItemStack.getItemStack(); // Update the player's inventory with the withdrawn items diff --git a/src/main/java/com/glodblock/github/util/Util.java b/src/main/java/com/glodblock/github/util/Util.java index 35cc4c5a..e5ec633d 100644 --- a/src/main/java/com/glodblock/github/util/Util.java +++ b/src/main/java/com/glodblock/github/util/Util.java @@ -196,6 +196,32 @@ public final class Util { } } + public static ImmutablePair getWirelessTerminal(EntityPlayer player) { + // Check player inventory for the wireless terminal. + int inventorySize = player.inventory.getSizeInventory(); + for (int i = 0; i < inventorySize; ++i) { + ItemStack is = player.inventory.getStackInSlot(i); + if (is != null && is.getItem() instanceof ToolWirelessTerminal) { + return new ImmutablePair<>(GuiHelper.encodeType(i, GuiHelper.InvType.PLAYER_BAUBLES), is); + } + } + + if (isBaublesLoaded) { + IInventory handler = BaublesApi.getBaubles(player); + if (handler != null) { + int baubleSlotCount = handler.getSizeInventory(); + for (int i = 0; i < baubleSlotCount; ++i) { + ItemStack is = handler.getStackInSlot(i); + if (is != null && is.getItem() instanceof ToolWirelessTerminal) { + return new ImmutablePair<>(GuiHelper.encodeType(i, GuiHelper.InvType.PLAYER_BAUBLES), is); + } + } + } + } + + return null; + } + public static ImmutablePair getUltraWirelessTerm(EntityPlayer player) { int invSize = player.inventory.getSizeInventory(); From a106dd7c0245c590fd523d1b79fd0a9c46b9020b Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Thu, 4 Dec 2025 17:09:11 -0500 Subject: [PATCH 3/5] Add JavaDocs --- .../network/CPacketPickBlockWithdraw.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java index 1049e62e..3713f3d0 100644 --- a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java +++ b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java @@ -124,7 +124,11 @@ public class CPacketPickBlockWithdraw implements IMessage { 1, world.getBlockMetadata(message.blockX, message.blockY, message.blockZ)); - // 1. Initial Scan for full/partial stacks + // 1. Scan through the player's main inventory to categorize existing stacks of the target block: + // - If a full stack (stackSize >= maxStackSize) is found, record its slot and stop searching. + // This indicates the player already has the maximum possible stack, so no withdrawal is needed. + // - Otherwise, collect all partial stack slots (stacks that match the item but aren't full). + // Partial stacks will be consolidated in a later step. int fullStackSlot = -1; List partialStackSlotsList = new ArrayList<>(); for (int i = 0; i < player.inventory.mainInventory.length; i++) { @@ -152,7 +156,8 @@ public class CPacketPickBlockWithdraw implements IMessage { return null; } - // 4. Consolidate if multiple partial stacks exist. + // 4. Consolidate all partial stacks of target block into 1 ItemStack. + // If a full stack is obtained, set it as the active slot and return. ItemStack consolidatedStack = null; int consolidatedStackSlot = -1; for (Integer partialStackSlot : partialStackSlotsList) { @@ -203,12 +208,22 @@ public class CPacketPickBlockWithdraw implements IMessage { // If the target stack is already in the player's hotbar, set that as the active slot. // Otherwise, move the target stack to the active slot. + // The slot to swap will have either been a consolidated stack of partial ItemStacks, + // or it will have been a newly created ItemStack in the next empty slot. int slotToSwap = consolidatedStack == null ? nextEmptySlot : consolidatedStackSlot; setSlotAsActiveSlot(player, slotToSwap); return null; // No reply packet needed } + /** + * Finds the first empty slot in the player's inventory, searching in reverse order (from the last slot to the + * first). This prioritizes filling the inventory from the end, which helps keep commonly accessed hotbar slots + * free. + * + * @param inventory The player's inventory to search. + * @return The index of the first empty slot found (searching backwards), or -1 if the inventory is full. + */ private int getFirstEmptyStackReverse(InventoryPlayer inventory) { for (int i = inventory.mainInventory.length - 1; i >= 0; --i) { if (inventory.mainInventory[i] == null) { @@ -219,6 +234,14 @@ public class CPacketPickBlockWithdraw implements IMessage { return -1; } + /** + * Sets the specified inventory slot as the player's active (held) slot. If the target slot is in the hotbar + * (slots 0-8), it directly selects that hotbar slot. If the target slot is outside the hotbar, it swaps the + * contents of that slot with the current active slot. + * + * @param player The player whose active slot should be changed. + * @param slot The inventory slot index to make active (0-8 for hotbar, 9+ for main inventory). + */ private void setSlotAsActiveSlot(EntityPlayerMP player, int slot) { if (slot >= 0 && slot <= 8) { player.inventory.currentItem = slot; From f8e40f842bc65f371473b79fb8ef089c36095bd3 Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Thu, 4 Dec 2025 20:36:25 -0500 Subject: [PATCH 4/5] Create dedicated Player Inventory Util class and support WirelessCraftingTerminal --- .../network/CPacketPickBlockWithdraw.java | 151 +++++------------- .../github/util/PlayerInventoryUtil.java | 105 ++++++++++++ 2 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/glodblock/github/util/PlayerInventoryUtil.java diff --git a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java index 3713f3d0..1f757476 100644 --- a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java +++ b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java @@ -5,15 +5,17 @@ import java.util.List; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayerMP; -import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.network.play.server.S09PacketHeldItemChange; import net.minecraft.world.World; +import net.p455w0rd.wirelesscraftingterminal.common.container.ContainerWirelessCraftingTerminal; +import net.p455w0rd.wirelesscraftingterminal.common.utils.RandomUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import com.glodblock.github.util.PlayerInventoryUtil; import com.glodblock.github.util.Util; import appeng.api.AEApi; @@ -86,23 +88,7 @@ public class CPacketPickBlockWithdraw implements IMessage { World world = player.worldObj; // Ensure the player has a wireless terminal - ImmutablePair terminalAndInventorySlot = Util.getWirelessTerminal(player); - if (terminalAndInventorySlot == null) { - return null; - } - - ItemStack terminalItemStack = terminalAndInventorySlot.getRight(); - if (terminalItemStack == null) { - return null; - } - - // Obtain the inventory handler for the AE2 wireless terminal. - // - // We can safely suppress this warning, as we are the one providing the storage type in - // the function call. - @SuppressWarnings("unchecked") - IMEInventoryHandler terminalInventory = (IMEInventoryHandler) Util - .getWirelessInv(terminalItemStack, player, StorageChannel.ITEMS); + IMEInventoryHandler terminalInventory = getWirelessInventory(player); if (terminalInventory == null) { return null; } @@ -145,13 +131,13 @@ public class CPacketPickBlockWithdraw implements IMessage { // 2. If a full stack already exists, put in active slot and return. if (fullStackSlot >= 0) { - setSlotAsActiveSlot(player, fullStackSlot); + PlayerInventoryUtil.setSlotAsActiveSlot(player, fullStackSlot); return null; } // 3. If there are no partial stacks and the player's inventory is full, // then return since we cannot add a retrieved stack to a full inventory - int nextEmptySlot = getFirstEmptyStackReverse(player.inventory); + int nextEmptySlot = PlayerInventoryUtil.getFirstEmptyStackReverse(player.inventory); if (partialStackSlotsList.isEmpty() && nextEmptySlot == -1) { return null; } @@ -165,12 +151,13 @@ public class CPacketPickBlockWithdraw implements IMessage { consolidatedStack = player.inventory.getStackInSlot(partialStackSlot); consolidatedStackSlot = partialStackSlot; } else { - consolidateItemStacks(player.inventory, partialStackSlot, consolidatedStackSlot); + PlayerInventoryUtil + .consolidateItemStacks(player.inventory, partialStackSlot, consolidatedStackSlot); } // Check if we created a full stack of items if (consolidatedStack.stackSize == consolidatedStack.getMaxStackSize()) { - setSlotAsActiveSlot(player, consolidatedStackSlot); + PlayerInventoryUtil.setSlotAsActiveSlot(player, consolidatedStackSlot); return null; } } @@ -211,105 +198,49 @@ public class CPacketPickBlockWithdraw implements IMessage { // The slot to swap will have either been a consolidated stack of partial ItemStacks, // or it will have been a newly created ItemStack in the next empty slot. int slotToSwap = consolidatedStack == null ? nextEmptySlot : consolidatedStackSlot; - setSlotAsActiveSlot(player, slotToSwap); + PlayerInventoryUtil.setSlotAsActiveSlot(player, slotToSwap); return null; // No reply packet needed } - /** - * Finds the first empty slot in the player's inventory, searching in reverse order (from the last slot to the - * first). This prioritizes filling the inventory from the end, which helps keep commonly accessed hotbar slots - * free. - * - * @param inventory The player's inventory to search. - * @return The index of the first empty slot found (searching backwards), or -1 if the inventory is full. - */ - private int getFirstEmptyStackReverse(InventoryPlayer inventory) { - for (int i = inventory.mainInventory.length - 1; i >= 0; --i) { - if (inventory.mainInventory[i] == null) { - return i; + @SuppressWarnings("unchecked") + private IMEInventoryHandler getWirelessInventory(EntityPlayerMP player) { + ImmutablePair terminalAndInventorySlot = Util.getWirelessTerminal(player); + ItemStack wctTerminal = null; + + if (terminalAndInventorySlot == null) { + // Secondary check, does the player have a WirelessCraftingTerminal terminal + // Why is this mod still in GTNH ;-; + wctTerminal = RandomUtils.getWirelessTerm(player.inventory); + if (wctTerminal == null) { + return null; } } - return -1; - } - - /** - * Sets the specified inventory slot as the player's active (held) slot. If the target slot is in the hotbar - * (slots 0-8), it directly selects that hotbar slot. If the target slot is outside the hotbar, it swaps the - * contents of that slot with the current active slot. - * - * @param player The player whose active slot should be changed. - * @param slot The inventory slot index to make active (0-8 for hotbar, 9+ for main inventory). - */ - private void setSlotAsActiveSlot(EntityPlayerMP player, int slot) { - if (slot >= 0 && slot <= 8) { - player.inventory.currentItem = slot; - player.playerNetServerHandler.sendPacket(new S09PacketHeldItemChange(slot)); + IMEInventoryHandler inventoryHandler; + if (wctTerminal != null) { + inventoryHandler = ContainerWirelessCraftingTerminal.getGuiObject( + wctTerminal, + player, + player.worldObj, + (int) player.posX, + (int) player.posY, + (int) player.posZ).getItemInventory(); } else { - swapInventorySlots(player.inventory, player.inventory.currentItem, slot); - } - player.inventory.markDirty(); - } + ItemStack terminalItemStack = terminalAndInventorySlot.getRight(); + if (terminalItemStack == null) { + return null; + } - /** - * Moves an ItemStack from a source slot to a destination slot in the player's inventory. If the destination - * slot is occupied, the items are swapped. - * - * @param inventory The player's inventory. - * @param slot1 The index of the first ItemStack to move. - * @param slot2 The index of the second ItemStack to move. - */ - private void swapInventorySlots(InventoryPlayer inventory, int slot1, int slot2) { - if (slot1 == slot2) { - return; + // Obtain the inventory handler for the AE2 wireless terminal. + // + // We can safely suppress this warning, as we are the one providing the storage type in + // the function call. + inventoryHandler = (IMEInventoryHandler) Util + .getWirelessInv(terminalItemStack, player, StorageChannel.ITEMS); } - // Get the stacks from both slots - ItemStack sourceStack = inventory.getStackInSlot(slot1); - ItemStack destinationStack = inventory.getStackInSlot(slot2); - - // Set the destination slot with the source stack (even if it's null) - inventory.setInventorySlotContents(slot2, sourceStack); - - // Set the source slot with the original destination stack - inventory.setInventorySlotContents(slot1, destinationStack); - - // Mark the inventory as dirty to ensure changes are saved and synced - inventory.markDirty(); - } - - /** - * Consolidate ItemStacks from a source slot to a destination slot in the player's inventory. - * - * @param inventory The player's inventory. - * @param sourceSlot The index of the slot to move the item from. - * @param destinationSlot The index of the slot to move the item to. - */ - private void consolidateItemStacks(InventoryPlayer inventory, int sourceSlot, int destinationSlot) { - ItemStack sourceStack = inventory.getStackInSlot(sourceSlot); - ItemStack destinationStack = inventory.getStackInSlot(destinationSlot); - - if (!sourceStack.isItemEqual(destinationStack) - || !ItemStack.areItemStackTagsEqual(sourceStack, destinationStack)) { - return; - } - - int missingQuantity = destinationStack.getMaxStackSize() - destinationStack.stackSize; - if (missingQuantity >= sourceStack.stackSize) { - destinationStack.stackSize = destinationStack.stackSize + sourceStack.stackSize; - sourceStack = null; - } else { - sourceStack.stackSize -= missingQuantity; - destinationStack.stackSize += missingQuantity; - } - - // Update the inventory stacks - inventory.setInventorySlotContents(destinationSlot, destinationStack); - inventory.setInventorySlotContents(sourceSlot, sourceStack); - - // Mark the inventory as dirty to ensure changes are saved and synced - inventory.markDirty(); + return inventoryHandler; } } } diff --git a/src/main/java/com/glodblock/github/util/PlayerInventoryUtil.java b/src/main/java/com/glodblock/github/util/PlayerInventoryUtil.java new file mode 100644 index 00000000..e1910d7f --- /dev/null +++ b/src/main/java/com/glodblock/github/util/PlayerInventoryUtil.java @@ -0,0 +1,105 @@ +package com.glodblock.github.util; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.server.S09PacketHeldItemChange; + +public class PlayerInventoryUtil { + + /** + * Finds the first empty slot in the player's inventory, searching in reverse order (from the last slot to the + * first). This prioritizes filling the inventory from the end, which helps keep commonly accessed hotbar slots + * free. + * + * @param inventory The player's inventory to search. + * @return The index of the first empty slot found (searching backwards), or -1 if the inventory is full. + */ + public static int getFirstEmptyStackReverse(InventoryPlayer inventory) { + for (int i = inventory.mainInventory.length - 1; i >= 0; --i) { + if (inventory.mainInventory[i] == null) { + return i; + } + } + + return -1; + } + + /** + * Sets the specified inventory slot as the player's active (held) slot. If the target slot is in the hotbar (slots + * 0-8), it directly selects that hotbar slot. If the target slot is outside the hotbar, it swaps the contents of + * that slot with the current active slot. + * + * @param player The player whose active slot should be changed. + * @param slot The inventory slot index to make active (0-8 for hotbar, 9+ for main inventory). + */ + public static void setSlotAsActiveSlot(EntityPlayerMP player, int slot) { + if (slot >= 0 && slot <= 8) { + player.inventory.currentItem = slot; + player.playerNetServerHandler.sendPacket(new S09PacketHeldItemChange(slot)); + } else { + swapInventorySlots(player.inventory, player.inventory.currentItem, slot); + } + player.inventory.markDirty(); + } + + /** + * Moves an ItemStack from a source slot to a destination slot in the player's inventory. If the destination slot is + * occupied, the items are swapped. + * + * @param inventory The player's inventory. + * @param slot1 The index of the first ItemStack to move. + * @param slot2 The index of the second ItemStack to move. + */ + public static void swapInventorySlots(InventoryPlayer inventory, int slot1, int slot2) { + if (slot1 == slot2) { + return; + } + + // Get the stacks from both slots + ItemStack sourceStack = inventory.getStackInSlot(slot1); + ItemStack destinationStack = inventory.getStackInSlot(slot2); + + // Set the destination slot with the source stack (even if it's null) + inventory.setInventorySlotContents(slot2, sourceStack); + + // Set the source slot with the original destination stack + inventory.setInventorySlotContents(slot1, destinationStack); + + // Mark the inventory as dirty to ensure changes are saved and synced + inventory.markDirty(); + } + + /** + * Consolidate ItemStacks from a source slot to a destination slot in the player's inventory. + * + * @param inventory The player's inventory. + * @param sourceSlot The index of the slot to move the item from. + * @param destinationSlot The index of the slot to move the item to. + */ + public static void consolidateItemStacks(InventoryPlayer inventory, int sourceSlot, int destinationSlot) { + ItemStack sourceStack = inventory.getStackInSlot(sourceSlot); + ItemStack destinationStack = inventory.getStackInSlot(destinationSlot); + + if (!sourceStack.isItemEqual(destinationStack) + || !ItemStack.areItemStackTagsEqual(sourceStack, destinationStack)) { + return; + } + + int missingQuantity = destinationStack.getMaxStackSize() - destinationStack.stackSize; + if (missingQuantity >= sourceStack.stackSize) { + destinationStack.stackSize = destinationStack.stackSize + sourceStack.stackSize; + sourceStack = null; + } else { + sourceStack.stackSize -= missingQuantity; + destinationStack.stackSize += missingQuantity; + } + + // Update the inventory stacks + inventory.setInventorySlotContents(destinationSlot, destinationStack); + inventory.setInventorySlotContents(sourceSlot, sourceStack); + + // Mark the inventory as dirty to ensure changes are saved and synced + inventory.markDirty(); + } +} From 75c62058d4b421aed259b88e2c26c682066689ec Mon Sep 17 00:00:00 2001 From: Michael Doyle Date: Thu, 4 Dec 2025 20:37:08 -0500 Subject: [PATCH 5/5] Remove client-side terminal checks --- .../com/glodblock/github/loader/KeybindLoader.java | 14 -------------- .../github/network/CPacketPickBlockWithdraw.java | 3 ++- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/glodblock/github/loader/KeybindLoader.java b/src/main/java/com/glodblock/github/loader/KeybindLoader.java index 585f215b..66e09397 100644 --- a/src/main/java/com/glodblock/github/loader/KeybindLoader.java +++ b/src/main/java/com/glodblock/github/loader/KeybindLoader.java @@ -4,7 +4,6 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.entity.EntityClientPlayerMP; import net.minecraft.client.settings.KeyBinding; import net.minecraft.item.ItemStack; -import net.minecraft.util.ChatComponentText; import net.minecraft.util.MovingObjectPosition; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -100,19 +99,6 @@ public class KeybindLoader implements Runnable { EntityClientPlayerMP player = minecraft.thePlayer; if (player == null) return; - // Ensure the player has the wireless terminal - ImmutablePair terminalAndInventorySlot = Util.getWirelessTerminal(player); - if (terminalAndInventorySlot == null) { - player.addChatMessage(new ChatComponentText("Could not find wireless terminal.")); - return; - } - - ItemStack terminal = terminalAndInventorySlot.getRight(); - if (terminal == null) { - player.addChatMessage(new ChatComponentText("Could not find wireless terminal.")); - return; - } - // Get the block the player is currently looking at MovingObjectPosition movingObject = minecraft.objectMouseOver; if (movingObject == null || movingObject.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) { diff --git a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java index 1f757476..fbff6d12 100644 --- a/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java +++ b/src/main/java/com/glodblock/github/network/CPacketPickBlockWithdraw.java @@ -8,7 +8,7 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.network.play.server.S09PacketHeldItemChange; +import net.minecraft.util.ChatComponentText; import net.minecraft.world.World; import net.p455w0rd.wirelesscraftingterminal.common.container.ContainerWirelessCraftingTerminal; import net.p455w0rd.wirelesscraftingterminal.common.utils.RandomUtils; @@ -90,6 +90,7 @@ public class CPacketPickBlockWithdraw implements IMessage { // Ensure the player has a wireless terminal IMEInventoryHandler terminalInventory = getWirelessInventory(player); if (terminalInventory == null) { + player.addChatMessage(new ChatComponentText("Could not access AE2 network.")); return null; }