From 27d296fd4d52702f09877e3026a721596b41ff90 Mon Sep 17 00:00:00 2001 From: ABKQPO <93412322+ABKQPO@users.noreply.github.com> Date: Fri, 25 Sep 2026 03:31:12 +0800 Subject: [PATCH] add extended crafting station stacks api (#344) Co-authored-by: Martin Robertz --- .../api/ExtendedStackLimitHelper.java | 41 ++ .../api/IExtendedStackLimitProvider.java | 17 + .../inventory/CraftingStationContainer.java | 613 ++++++++++++++++-- .../CraftingStationStackSyncPacket.java | 119 ++++ .../util/network/PacketPipeline.java | 1 + 5 files changed, 728 insertions(+), 63 deletions(-) create mode 100644 src/main/java/tconstruct/api/ExtendedStackLimitHelper.java create mode 100644 src/main/java/tconstruct/api/IExtendedStackLimitProvider.java create mode 100644 src/main/java/tconstruct/util/network/CraftingStationStackSyncPacket.java diff --git a/src/main/java/tconstruct/api/ExtendedStackLimitHelper.java b/src/main/java/tconstruct/api/ExtendedStackLimitHelper.java new file mode 100644 index 00000000..4ac8b57e --- /dev/null +++ b/src/main/java/tconstruct/api/ExtendedStackLimitHelper.java @@ -0,0 +1,41 @@ +package tconstruct.api; + +import net.minecraft.inventory.Container; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +/** + * Resolves the effective capacity used by container operations. + */ +public class ExtendedStackLimitHelper { + + public static boolean hasExtendedStackLimit(Slot slot) { + return getProvider(slot) != null; + } + + public static boolean hasExtendedStackLimit(Container container) { + if (container == null) return false; + + for (Object object : container.inventorySlots) { + if (object instanceof Slot slot && hasExtendedStackLimit(slot)) return true; + } + return false; + } + + public static int getStackLimit(Slot slot, ItemStack stack) { + if (slot == null || stack == null) return 0; + + IExtendedStackLimitProvider provider = getProvider(slot); + if (provider != null) { + return Math.max(0, provider.getExtendedStackLimit(slot.getSlotIndex(), stack)); + } + + return Math.max(0, Math.min(slot.getSlotStackLimit(), stack.getMaxStackSize())); + } + + private static IExtendedStackLimitProvider getProvider(Slot slot) { + if (slot instanceof IExtendedStackLimitProvider provider) return provider; + if (slot.inventory instanceof IExtendedStackLimitProvider provider) return provider; + return null; + } +} diff --git a/src/main/java/tconstruct/api/IExtendedStackLimitProvider.java b/src/main/java/tconstruct/api/IExtendedStackLimitProvider.java new file mode 100644 index 00000000..0a60e55b --- /dev/null +++ b/src/main/java/tconstruct/api/IExtendedStackLimitProvider.java @@ -0,0 +1,17 @@ +package tconstruct.api; + +import net.minecraft.item.ItemStack; + +/** + * Provides an item-aware stack capacity for a slot or its backing inventory. + * + *

+ * The returned value is the complete capacity for the supplied item in the supplied slot. Implementations may return a + * value larger than the item's normal stack size when the inventory supports extended stacks. The backing inventory + * must preserve values up to the returned capacity when a stack is written back. + *

+ */ +public interface IExtendedStackLimitProvider { + + int getExtendedStackLimit(int slotIndex, ItemStack stack); +} diff --git a/src/main/java/tconstruct/tools/inventory/CraftingStationContainer.java b/src/main/java/tconstruct/tools/inventory/CraftingStationContainer.java index 380fdf2b..1fd3759e 100644 --- a/src/main/java/tconstruct/tools/inventory/CraftingStationContainer.java +++ b/src/main/java/tconstruct/tools/inventory/CraftingStationContainer.java @@ -1,12 +1,18 @@ package tconstruct.tools.inventory; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.entity.player.InventoryPlayer; import net.minecraft.inventory.Container; import net.minecraft.inventory.ICrafting; @@ -19,6 +25,8 @@ import net.minecraft.item.crafting.CraftingManager; import net.minecraft.item.crafting.IRecipe; import net.minecraft.world.World; +import tconstruct.TConstruct; +import tconstruct.api.ExtendedStackLimitHelper; import tconstruct.items.tools.Arrow; import tconstruct.items.tools.BowBase; import tconstruct.library.crafting.ModifyBuilder; @@ -30,6 +38,7 @@ import tconstruct.library.weaponry.ProjectileWeapon; import tconstruct.tools.TinkerTools; import tconstruct.tools.gui.ChestSlot; import tconstruct.tools.logic.CraftingStationLogic; +import tconstruct.util.network.CraftingStationStackSyncPacket; public class CraftingStationContainer extends Container { @@ -43,6 +52,8 @@ public class CraftingStationContainer extends Container { private static final int CLICK_MODE_PICKUP = 0; private static final int CLICK_MODE_QUICK_MOVE = 1; private static final int CLICK_MODE_HOTBAR_SWAP = 2; + private static final int CLICK_MODE_CREATIVE_PICK = 3; + private static final int CLICK_MODE_DROP = 4; private static final int CLICK_MODE_DRAG = 5; private static final int CLICK_MODE_COLLECT = 6; @@ -53,7 +64,7 @@ public class CraftingStationContainer extends Container { private final boolean shiftClickToGrid; @SuppressWarnings("rawtypes") - private final WeakReference[] inventories; + public final WeakReference[] inventories; /** * The crafting matrix inventory (3x3). @@ -65,16 +76,34 @@ public class CraftingStationContainer extends Container { EntityPlayer player; /** Last matched recipe, tried first to avoid rescanning the whole recipe list on every matrix change. */ - private IRecipe lastRecipe; + public IRecipe lastRecipe; /** While true, matrix changes don't recompute the result. Batches ingredient consumption into one lookup. */ - private boolean suppressCraftingUpdates; + public boolean suppressCraftingUpdates; /** Side-inventory preference of the stack currently carried by this container's cursor. */ - private boolean carriedStackPrefersSideInventory; + public boolean carriedStackPrefersSideInventory; /** Last preference mask sent to clients. */ - private int lastSideInventoryPreferences = -1; + public int lastSideInventoryPreferences = -1; + + /** Provider-backed side slots whose counts cannot use the vanilla byte-sized update. */ + private final boolean[] extendedStackSlots; + + /** Provider-backed side slot indices, cached because the slot layout cannot change after construction. */ + private final int[] extendedStackSlotIds; + + /** Last oversized stack state sent to each server-side listener. */ + private final Map> extendedStackStates = new HashMap<>(); + + /** Slots selected by the client during an extended drag operation. */ + private final Set extendedDragSlots = new HashSet<>(); + + /** Drag distribution mode used by the current extended drag operation. */ + private int extendedDragMode = -1; + + /** Whether the current extended drag operation has passed its start phase. */ + private boolean extendedDragActive; public CraftingStationContainer(InventoryPlayer inventoryplayer, CraftingStationLogic logic, int x, int y, int z) { this.worldObj = logic.getWorldObj(); @@ -154,6 +183,8 @@ public class CraftingStationContainer extends Container { } } + this.extendedStackSlots = createExtendedStackSlots(); + this.extendedStackSlotIds = createExtendedStackSlotIds(); this.onCraftMatrixChanged(this.craftMatrix); } @@ -174,7 +205,7 @@ public class CraftingStationContainer extends Container { } public ItemStack transferStackInSlot(EntityPlayer entityPlayer, int index) { - Slot slot = (Slot) this.inventorySlots.get(index); + Slot slot = this.inventorySlots.get(index); if (slot == null || !slot.getHasStack()) { return null; @@ -247,7 +278,16 @@ public class CraftingStationContainer extends Container { ? copySlotRange(SIDE_INVENTORY_FIRST_SLOT, inventorySlots.size()) : null; - ItemStack result = super.slotClick(slotId, clickedButton, mode, player); + ItemStack result; + if (isExtendedStackSlot(slotId) && isExtendedSlotClickMode(mode)) { + result = handleExtendedSlotClick(slotId, clickedButton, mode, player); + } else if (hasExtendedStackSlots() && mode == CLICK_MODE_DRAG) { + result = handleExtendedDrag(slotId, clickedButton, player); + } else if (hasExtendedStackSlots() && mode == CLICK_MODE_COLLECT) { + result = handleExtendedCollect(slotId, clickedButton, player); + } else { + result = super.slotClick(slotId, clickedButton, mode, player); + } ItemStack carriedAfter = player.inventory.getItemStack(); ItemStack[] gridAfter = copySlotRange(CRAFTING_GRID_FIRST_SLOT, CRAFTING_GRID_END_SLOT); @@ -294,6 +334,7 @@ public class CraftingStationContainer extends Container { @Override public void detectAndSendChanges() { + suppressOversizedVanillaUpdates(); super.detectAndSendChanges(); int sideInventoryPreferences = logic.getSideInventoryPreferences(); @@ -303,6 +344,95 @@ public class CraftingStationContainer extends Container { } lastSideInventoryPreferences = sideInventoryPreferences; } + + sendExtendedStackUpdates(); + } + + private void sendExtendedStackUpdates() { + if (worldObj.isRemote) return; + + for (ICrafting crafter : crafters) { + if (!(crafter instanceof EntityPlayerMP playerMP)) continue; + + Map previousStacks = extendedStackStates + .computeIfAbsent(playerMP, ignored -> new HashMap<>()); + List changedSlotIds = null; + List changedStacks = null; + + for (int slotId : extendedStackSlotIds) { + ItemStack stack = inventorySlots.get(slotId).getStack(); + ItemStack previous = previousStacks.get(slotId); + if (!isOversized(stack)) { + previousStacks.remove(slotId); + continue; + } + + if (!ItemStack.areItemStacksEqual(previous, stack)) { + if (changedSlotIds == null) { + changedSlotIds = new ArrayList<>(); + changedStacks = new ArrayList<>(); + } + changedSlotIds.add(slotId); + changedStacks.add(stack); + previousStacks.put(slotId, stack.copy()); + } + } + + ItemStack cursorStack = playerMP.inventory.getItemStack(); + ItemStack previousCursorStack = previousStacks.get(-1); + boolean cursorWasOversized = isOversized(previousCursorStack); + if (isOversized(cursorStack)) { + if (!ItemStack.areItemStacksEqual(previousCursorStack, cursorStack)) { + if (changedSlotIds == null) { + changedSlotIds = new ArrayList<>(); + changedStacks = new ArrayList<>(); + } + changedSlotIds.add(-1); + changedStacks.add(cursorStack); + previousStacks.put(-1, cursorStack.copy()); + } + } else { + if (cursorWasOversized && !ItemStack.areItemStacksEqual(previousCursorStack, cursorStack)) { + if (changedSlotIds == null) { + changedSlotIds = new ArrayList<>(); + changedStacks = new ArrayList<>(); + } + changedSlotIds.add(-1); + changedStacks.add(cursorStack); + } + previousStacks.remove(-1); + } + + if (changedSlotIds != null) { + TConstruct.packetPipeline.sendTo( + new CraftingStationStackSyncPacket( + windowId, + toIntArray(changedSlotIds), + changedStacks.toArray(new ItemStack[0])), + playerMP); + } + } + } + + private void suppressOversizedVanillaUpdates() { + for (int slotId : extendedStackSlotIds) { + ItemStack stack = inventorySlots.get(slotId).getStack(); + if (isOversized(stack) && !ItemStack.areItemStacksEqual(inventoryItemStacks.get(slotId), stack)) { + inventoryItemStacks.set(slotId, stack.copy()); + } + } + } + + private static boolean isOversized(ItemStack stack) { + return stack != null && stack.stackSize > Byte.MAX_VALUE; + } + + private static int[] toIntArray(List values) { + int[] result = new int[values.size()]; + for (int i = 0; i < values.size(); i++) { + result[i] = values.get(i); + } + return result; } @Override @@ -312,7 +442,7 @@ public class CraftingStationContainer extends Container { } } - private void updateGridPreferences(ItemStack[] before, ItemStack[] after, boolean[] preferencesBefore, + public void updateGridPreferences(ItemStack[] before, ItemStack[] after, boolean[] preferencesBefore, ItemStack carriedBefore, ItemStack clickedBefore, int clickedSlot, int mode, boolean sideInventoryShiftClick, boolean incomingStackPrefersSideInventory) { for (int i = 0; i < after.length; i++) { @@ -356,7 +486,7 @@ public class CraftingStationContainer extends Container { } } - private static boolean getCarriedStackPreference(ItemStack carriedBefore, ItemStack carriedAfter, + public static boolean getCarriedStackPreference(ItemStack carriedBefore, ItemStack carriedAfter, boolean carriedPreferenceBefore, ItemStack clickedBefore, boolean clickedStackPrefersSideInventory, boolean collectedPreferredGridStack, boolean collectedSideInventoryStack) { if (carriedAfter == null || carriedAfter.stackSize <= 0) return false; @@ -372,7 +502,7 @@ public class CraftingStationContainer extends Container { return collectedPreferredGridStack || collectedSideInventoryStack; } - private static boolean collectedPreferredGridStack(ItemStack carriedStack, ItemStack[] before, ItemStack[] after, + public static boolean collectedPreferredGridStack(ItemStack carriedStack, ItemStack[] before, ItemStack[] after, boolean[] preferencesBefore) { for (int i = 0; i < before.length; i++) { if (preferencesBefore[i] && stackWasCollected(carriedStack, before[i], after[i])) return true; @@ -380,7 +510,7 @@ public class CraftingStationContainer extends Container { return false; } - private static boolean collectedSideInventoryStack(ItemStack carriedStack, ItemStack[] before, ItemStack[] after) { + public static boolean collectedSideInventoryStack(ItemStack carriedStack, ItemStack[] before, ItemStack[] after) { if (before == null || after == null) return false; for (int i = 0; i < before.length; i++) { @@ -389,12 +519,12 @@ public class CraftingStationContainer extends Container { return false; } - private static boolean stackWasCollected(ItemStack carriedStack, ItemStack before, ItemStack after) { + public static boolean stackWasCollected(ItemStack carriedStack, ItemStack before, ItemStack after) { if (before == null || !stacksCanMerge(carriedStack, before)) return false; return after == null || !stacksCanMerge(before, after) || after.stackSize < before.stackSize; } - private boolean[] copyGridPreferences() { + public boolean[] copyGridPreferences() { boolean[] preferences = new boolean[CRAFTING_GRID_END_SLOT - CRAFTING_GRID_FIRST_SLOT]; for (int i = 0; i < preferences.length; i++) { preferences[i] = logic.prefersSideInventory(CRAFTING_GRID_FIRST_SLOT + i); @@ -402,38 +532,388 @@ public class CraftingStationContainer extends Container { return preferences; } - private ItemStack[] copySlotRange(int start, int end) { + public ItemStack[] copySlotRange(int start, int end) { ItemStack[] stacks = new ItemStack[Math.max(0, end - start)]; for (int i = 0; i < stacks.length; i++) { - stacks[i] = copyStack(((Slot) inventorySlots.get(start + i)).getStack()); + stacks[i] = copyStack(inventorySlots.get(start + i).getStack()); } return stacks; } - private ItemStack getSlotStackCopy(int slot) { + public ItemStack getSlotStackCopy(int slot) { if (slot < 0 || slot >= inventorySlots.size()) return null; - return copyStack(((Slot) inventorySlots.get(slot)).getStack()); + return copyStack(inventorySlots.get(slot).getStack()); } - private static ItemStack copyStack(ItemStack stack) { + public static boolean isExtendedSlotClickMode(int mode) { + return mode == CLICK_MODE_PICKUP || mode == CLICK_MODE_HOTBAR_SWAP + || mode == CLICK_MODE_CREATIVE_PICK + || mode == CLICK_MODE_DROP; + } + + public ItemStack handleExtendedSlotClick(int slotId, int clickedButton, int mode, EntityPlayer player) { + if (slotId < 0 || slotId >= inventorySlots.size()) return null; + + Slot slot = inventorySlots.get(slotId); + return switch (mode) { + case CLICK_MODE_PICKUP -> handleExtendedPickup(slot, clickedButton, player); + case CLICK_MODE_HOTBAR_SWAP -> handleExtendedHotbarSwap(slot, clickedButton, player); + case CLICK_MODE_CREATIVE_PICK -> handleExtendedCreativePick(slot, player); + case CLICK_MODE_DROP -> handleExtendedDrop(slot, clickedButton, player); + default -> null; + }; + } + + public ItemStack handleExtendedPickup(Slot slot, int clickedButton, EntityPlayer player) { + InventoryPlayer inventory = player.inventory; + ItemStack stored = getNonEmptyStack(slot); + ItemStack carried = inventory.getItemStack(); + ItemStack result = copyStack(stored); + + if (stored == null) { + if (carried == null || !slot.isItemValid(carried)) return result; + + int amount = clickedButton == 0 ? carried.stackSize : 1; + amount = Math.min(amount, ExtendedStackLimitHelper.getStackLimit(slot, carried)); + if (amount <= 0) return result; + + ItemStack placed = carried.splitStack(amount); + slot.putStack(placed); + slot.onSlotChanged(); + if (carried.stackSize <= 0) inventory.setItemStack(null); + return result; + } + + if (!slot.canTakeStack(player)) return result; + + if (carried == null) { + int transferLimit = getNormalStackLimit(stored); + int amount = clickedButton == 0 ? transferLimit : transferLimit / 2 + transferLimit % 2; + ItemStack extracted = slot.decrStackSize(Math.min(stored.stackSize, amount)); + if (extracted != null && extracted.stackSize > 0) { + inventory.setItemStack(extracted); + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + } + return result; + } + + if (!slot.isItemValid(carried) || !stacksCanMerge(stored, carried)) return result; + + int limit = ExtendedStackLimitHelper.getStackLimit(slot, stored); + int available = Math.max(0, limit - stored.stackSize); + int amount = clickedButton == 0 ? Math.min(carried.stackSize, available) : Math.min(1, available); + if (amount > 0) { + stored.stackSize += amount; + carried.stackSize -= amount; + slot.putStack(stored); + slot.onSlotChanged(); + if (carried.stackSize <= 0) inventory.setItemStack(null); + return result; + } + + if (clickedButton == 0) { + int cursorSpace = Math.max(0, getNormalStackLimit(carried) - carried.stackSize); + int amountToTake = Math.min(stored.stackSize, cursorSpace); + if (amountToTake > 0) { + ItemStack extracted = slot.decrStackSize(amountToTake); + if (extracted != null && extracted.stackSize > 0) { + carried.stackSize += extracted.stackSize; + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + } + } + } + return result; + } + + private ItemStack handleExtendedDrag(int slotId, int clickedButton, EntityPlayer player) { + int dragEvent = Container.func_94532_c(clickedButton); + if (dragEvent == 0) { + extendedDragSlots.clear(); + extendedDragMode = Container.func_94529_b(clickedButton); + extendedDragActive = player.inventory.getItemStack() != null && Container.func_94528_d(extendedDragMode); + return null; + } + + if (dragEvent == 1) { + ItemStack carried = player.inventory.getItemStack(); + if (!extendedDragActive || carried == null || carried.stackSize <= extendedDragSlots.size()) { + resetExtendedDrag(); + return null; + } + + Slot slot = getSlotIfPresent(slotId); + if (canAddExtendedDragSlot(slot, carried)) extendedDragSlots.add(slot); + return null; + } + + if (dragEvent == 2) { + if (!extendedDragActive) return null; + + ItemStack carried = player.inventory.getItemStack(); + if (carried != null) { + ItemStack template = carried.copy(); + int remaining = carried.stackSize; + Set selectedSlots = new HashSet<>(extendedDragSlots); + int selectedSlotCount = selectedSlots.size(); + for (Slot slot : selectedSlots) { + if (remaining < selectedSlotCount || !canAddExtendedDragSlot(slot, carried)) continue; + + ItemStack existing = getNonEmptyStack(slot); + int existingSize = existing == null ? 0 : existing.stackSize; + int targetSize = getDraggedStackSize( + template.stackSize, + extendedDragMode, + selectedSlotCount, + existingSize, + getStorageLimit(slot, template)); + int moved = targetSize - existingSize; + if (moved <= 0) continue; + + ItemStack placed = template.copy(); + placed.stackSize = targetSize; + slot.putStack(placed); + slot.onSlotChanged(); + remaining -= moved; + } + + template.stackSize = remaining; + player.inventory.setItemStack(remaining > 0 ? template : null); + } + + resetExtendedDrag(); + return null; + } + + resetExtendedDrag(); + return null; + } + + private ItemStack handleExtendedCollect(int slotId, int clickedButton, EntityPlayer player) { + if (slotId < 0) return null; + + Slot clickedSlot = getSlotIfPresent(slotId); + if (clickedSlot != null && getNonEmptyStack(clickedSlot) != null && clickedSlot.canTakeStack(player)) + return null; + + ItemStack carried = player.inventory.getItemStack(); + if (carried == null || carried.stackSize >= carried.getMaxStackSize()) return null; + + boolean changed = false; + int firstIndex = clickedButton == 0 ? 0 : inventorySlots.size() - 1; + int step = clickedButton == 0 ? 1 : -1; + for (int pass = 0; pass < 2 && carried.stackSize < carried.getMaxStackSize(); pass++) { + for (int index = firstIndex; index >= 0 && index < inventorySlots.size() + && carried.stackSize < carried.getMaxStackSize(); index += step) { + Slot slot = inventorySlots.get(index); + ItemStack stored = getNonEmptyStack(slot); + if (stored == null || !slot.canTakeStack(player) + || !func_94530_a(carried, slot) + || !stacksCanMerge(carried, stored)) + continue; + + int limit = getStorageLimit(slot, carried); + if (pass == 0 && stored.stackSize >= limit) continue; + + int amount = Math.min(carried.getMaxStackSize() - carried.stackSize, stored.stackSize); + ItemStack extracted = slot.decrStackSize(amount); + if (extracted == null || extracted.stackSize <= 0) continue; + + carried.stackSize += extracted.stackSize; + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + changed = true; + } + } + + if (changed) detectAndSendChanges(); + return null; + } + + private boolean canAddExtendedDragSlot(Slot slot, ItemStack carried) { + if (slot == null || !slot.isItemValid(carried) || !func_94530_a(carried, slot) || !canDragIntoSlot(slot)) + return false; + + ItemStack existing = getNonEmptyStack(slot); + return existing == null + || stacksCanMerge(carried, existing) && existing.stackSize < getStorageLimit(slot, carried); + } + + private static int getDraggedStackSize(int carriedSize, int dragMode, int slotCount, int existingSize, int limit) { + long requested = dragMode == 0 ? carriedSize / (long) slotCount : 1L; + long target = Math.min((long) existingSize + requested, limit); + return target > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) target; + } + + private Slot getSlotIfPresent(int slotId) { + return slotId < 0 || slotId >= inventorySlots.size() ? null : inventorySlots.get(slotId); + } + + private void resetExtendedDrag() { + extendedDragSlots.clear(); + extendedDragMode = -1; + extendedDragActive = false; + } + + public ItemStack handleExtendedHotbarSwap(Slot slot, int hotbarIndex, EntityPlayer player) { + if (hotbarIndex < 0 || hotbarIndex >= 9) return null; + + InventoryPlayer inventory = player.inventory; + ItemStack stored = getNonEmptyStack(slot); + ItemStack hotbar = inventory.getStackInSlot(hotbarIndex); + ItemStack result = copyStack(stored); + + if (stored == null) { + if (hotbar == null || !slot.isItemValid(hotbar)) return result; + + int amount = Math.min(hotbar.stackSize, ExtendedStackLimitHelper.getStackLimit(slot, hotbar)); + if (amount <= 0) return result; + + ItemStack placed = hotbar.splitStack(amount); + slot.putStack(placed); + inventory.setInventorySlotContents(hotbarIndex, hotbar.stackSize > 0 ? hotbar : null); + slot.onSlotChanged(); + return result; + } + + if (!slot.canTakeStack(player)) return result; + + if (hotbar == null) { + ItemStack extracted = slot.decrStackSize(getNormalStackLimit(stored)); + if (extracted != null && extracted.stackSize > 0) { + inventory.setInventorySlotContents(hotbarIndex, extracted); + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + } + return result; + } + + if (stacksCanMerge(stored, hotbar) && slot.isItemValid(hotbar)) { + int available = Math.max(0, getNormalStackLimit(hotbar) - hotbar.stackSize); + int amount = Math.min(stored.stackSize, available); + if (amount > 0) { + ItemStack extracted = slot.decrStackSize(amount); + if (extracted != null && extracted.stackSize > 0) { + hotbar.stackSize += extracted.stackSize; + inventory.setInventorySlotContents(hotbarIndex, hotbar); + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + } + } + return result; + } + + int emptySlot = inventory.getFirstEmptyStack(); + if (emptySlot < 0) return result; + + ItemStack extracted = slot.decrStackSize(getNormalStackLimit(stored)); + if (extracted != null && extracted.stackSize > 0) { + inventory.setInventorySlotContents(emptySlot, hotbar); + inventory.setInventorySlotContents(hotbarIndex, extracted); + clearEmptySlot(slot); + slot.onPickupFromSlot(player, extracted); + slot.onSlotChanged(); + } + return result; + } + + public ItemStack handleExtendedCreativePick(Slot slot, EntityPlayer player) { + InventoryPlayer inventory = player.inventory; + ItemStack stored = getNonEmptyStack(slot); + if (stored == null || !player.capabilities.isCreativeMode || inventory.getItemStack() != null) { + return copyStack(stored); + } + + ItemStack picked = stored.copy(); + picked.stackSize = getNormalStackLimit(picked); + inventory.setItemStack(picked); + return stored.copy(); + } + + public ItemStack handleExtendedDrop(Slot slot, int clickedButton, EntityPlayer player) { + InventoryPlayer inventory = player.inventory; + ItemStack stored = getNonEmptyStack(slot); + ItemStack result = copyStack(stored); + if (stored == null || inventory.getItemStack() != null || !slot.canTakeStack(player)) return result; + + int amount = clickedButton == 0 ? 1 : getNormalStackLimit(stored); + ItemStack dropped = slot.decrStackSize(amount); + if (dropped != null && dropped.stackSize > 0) { + clearEmptySlot(slot); + slot.onPickupFromSlot(player, dropped); + player.dropPlayerItemWithRandomChoice(dropped, true); + slot.onSlotChanged(); + } + return result; + } + + public static void clearEmptySlot(Slot slot) { + ItemStack stack = slot.getStack(); + if (stack != null && stack.stackSize <= 0) { + slot.putStack(null); + } + } + + public static int getNormalStackLimit(ItemStack stack) { + return Math.max(0, stack.getMaxStackSize()); + } + + public static ItemStack copyStack(ItemStack stack) { return stack == null ? null : stack.copy(); } - private static boolean stacksCanMerge(ItemStack first, ItemStack second) { + public static boolean stacksCanMerge(ItemStack first, ItemStack second) { return first != null && second != null && first.getItem() == second.getItem() && (!first.getHasSubtypes() || first.getItemDamage() == second.getItemDamage()) && ItemStack.areItemStackTagsEqual(first, second); } - private static boolean isCraftingGridSlot(int slot) { + public static boolean isCraftingGridSlot(int slot) { return slot >= CRAFTING_GRID_FIRST_SLOT && slot < CRAFTING_GRID_END_SLOT; } - private boolean isSideInventorySlot(int slot) { + public boolean isSideInventorySlot(int slot) { return slot >= SIDE_INVENTORY_FIRST_SLOT && slot < inventorySlots.size(); } + private boolean[] createExtendedStackSlots() { + boolean[] extendedSlots = new boolean[inventorySlots.size()]; + for (int slotId = SIDE_INVENTORY_FIRST_SLOT; slotId < inventorySlots.size(); slotId++) { + Slot slot = inventorySlots.get(slotId); + extendedSlots[slotId] = ExtendedStackLimitHelper.hasExtendedStackLimit(slot); + } + return extendedSlots; + } + + private int[] createExtendedStackSlotIds() { + int count = 0; + for (boolean extended : extendedStackSlots) { + if (extended) count++; + } + + int[] slotIds = new int[count]; + int index = 0; + for (int slotId = SIDE_INVENTORY_FIRST_SLOT; slotId < extendedStackSlots.length; slotId++) { + if (extendedStackSlots[slotId]) slotIds[index++] = slotId; + } + return slotIds; + } + + public boolean isExtendedStackSlot(int slot) { + return slot >= 0 && slot < extendedStackSlots.length && extendedStackSlots[slot]; + } + + private boolean hasExtendedStackSlots() { + return extendedStackSlotIds.length > 0; + } + protected boolean refillChest(ItemStack itemstack) { if (itemstack == null || itemstack.stackSize <= 0 || inventorySlots.size() == SIDE_INVENTORY_FIRST_SLOT) return false; @@ -502,7 +982,7 @@ public class CraftingStationContainer extends Container { } /** Like {@link CraftingManager#findMatchingRecipe} but tries the last matched recipe first. */ - private ItemStack findMatchingRecipeCached() { + public ItemStack findMatchingRecipeCached() { // Vanilla's two-item tool repair takes precedence over the recipe list if (isVanillaToolRepair()) { return CraftingManager.getInstance().findMatchingRecipe(this.craftMatrix, this.worldObj); @@ -514,7 +994,6 @@ public class CraftingStationContainer extends Container { } this.lastRecipe = null; - @SuppressWarnings("unchecked") List recipes = CraftingManager.getInstance().getRecipeList(); for (int i = 0; i < recipes.size(); i++) { IRecipe recipe = recipes.get(i); @@ -529,7 +1008,7 @@ public class CraftingStationContainer extends Container { /** * Mirrors the repair check in {@link CraftingManager#findMatchingRecipe}: two size-1 stacks of one repairable item. */ - private boolean isVanillaToolRepair() { + public boolean isVanillaToolRepair() { ItemStack first = null; ItemStack second = null; int found = 0; @@ -554,12 +1033,12 @@ public class CraftingStationContainer extends Container { * Suppresses result updates until {@link #endBatchCraftingUpdate}; each consumed ingredient fires a lookup * otherwise. */ - void beginBatchCraftingUpdate() { + public void beginBatchCraftingUpdate() { suppressCraftingUpdates = true; } /** Re-enables result updates and recomputes once. */ - void endBatchCraftingUpdate() { + public void endBatchCraftingUpdate() { suppressCraftingUpdates = false; this.onCraftMatrixChanged(this.craftMatrix); } @@ -590,19 +1069,23 @@ public class CraftingStationContainer extends Container { if (stack.stackSize > 0) { while (!playerInventory && slotIndex < slotsTotal || playerInventory && slotIndex >= slotsStart) { - otherInventorySlot = (Slot) this.inventorySlots.get(slotIndex); - copyStack = otherInventorySlot.getStack(); + otherInventorySlot = this.inventorySlots.get(slotIndex); + copyStack = getNonEmptyStack(otherInventorySlot); if (copyStack == null && otherInventorySlot.isItemValid(stack)) { - ItemStack placed = stack.copy(); - if (placed.hasTagCompound() && placed.getItem() instanceof IModifyable modifyable) { - placed.getTagCompound().getCompoundTag(modifyable.getBaseTagName()).removeTag("ToRemove"); + int limit = ExtendedStackLimitHelper.getStackLimit(otherInventorySlot, stack); + int amount = Math.min(stack.stackSize, limit); + if (amount > 0) { + ItemStack placed = stack.copy(); + placed.stackSize = amount; + if (placed.hasTagCompound() && placed.getItem() instanceof IModifyable modifyable) { + placed.getTagCompound().getCompoundTag(modifyable.getBaseTagName()).removeTag("ToRemove"); + } + otherInventorySlot.putStack(placed); + otherInventorySlot.onSlotChanged(); + stack.stackSize -= amount; + failedToMerge = true; } - otherInventorySlot.putStack(placed); - otherInventorySlot.onSlotChanged(); - stack.stackSize = 0; - failedToMerge = true; - break; } if (playerInventory) { @@ -625,6 +1108,17 @@ public class CraftingStationContainer extends Container { return ret; } + private static ItemStack getNonEmptyStack(Slot slot) { + if (slot == null) return null; + + ItemStack stack = slot.getStack(); + return stack == null || stack.stackSize <= 0 ? null : stack; + } + + private static int getStorageLimit(Slot slot, ItemStack stack) { + return ExtendedStackLimitHelper.getStackLimit(slot, stack); + } + // only refills items that are already present protected boolean mergeItemStackRefill(@Nonnull ItemStack stack, int startIndex, int endIndex, boolean useEndIndex) { @@ -640,24 +1134,20 @@ public class CraftingStationContainer extends Container { if (stack.isStackable()) { while (stack.stackSize > 0 && (!useEndIndex && k < endIndex || useEndIndex && k >= startIndex)) { - slot = (Slot) this.inventorySlots.get(k); - itemstack1 = slot.getStack(); + slot = this.inventorySlots.get(k); + itemstack1 = getNonEmptyStack(slot); if (itemstack1 != null && itemstack1.getItem() == stack.getItem() && (!stack.getHasSubtypes() || stack.getItemDamage() == itemstack1.getItemDamage()) && ItemStack.areItemStackTagsEqual(stack, itemstack1) && this.func_94530_a /* canMergeSlot */(stack, slot)) { - int l = itemstack1.stackSize + stack.stackSize; - int limit = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + int limit = ExtendedStackLimitHelper.getStackLimit(slot, stack); + int space = limit - itemstack1.stackSize; + int amount = Math.min(stack.stackSize, Math.max(0, space)); - if (l <= limit) { - stack.stackSize = 0; - itemstack1.stackSize = l; - slot.onSlotChanged(); - didSomething = true; - } else if (itemstack1.stackSize < limit) { - stack.stackSize -= (limit - itemstack1.stackSize); - itemstack1.stackSize = limit; + if (amount > 0) { + stack.stackSize -= amount; + itemstack1.stackSize += amount; slot.onSlotChanged(); didSomething = true; } @@ -681,23 +1171,20 @@ public class CraftingStationContainer extends Container { int k = useEndIndex ? endIndex - 1 : startIndex; while (!useEndIndex && k < endIndex || useEndIndex && k >= startIndex) { - final Slot slot = (Slot) this.inventorySlots.get(k); - ItemStack itemstack1 = slot.getStack(); + final Slot slot = this.inventorySlots.get(k); + ItemStack itemstack1 = getNonEmptyStack(slot); - if ((itemstack1 == null || itemstack1.stackSize == 0) && slot.isItemValid(stack) - && this.func_94530_a /* canMergeSlot */(stack, slot)) { - // Forge: Make sure to respect isItemValid in the slot. - int limit = slot.getSlotStackLimit(); - ItemStack stack2 = stack.copy(); - if (stack2.stackSize > limit) { - stack2.stackSize = limit; - stack.stackSize -= limit; - } else { - stack.stackSize = 0; + if (itemstack1 == null && slot.isItemValid(stack) && this.func_94530_a /* canMergeSlot */(stack, slot)) { + int limit = ExtendedStackLimitHelper.getStackLimit(slot, stack); + int amount = Math.min(stack.stackSize, limit); + if (amount > 0) { + ItemStack stack2 = stack.copy(); + stack2.stackSize = amount; + stack.stackSize -= amount; + slot.putStack(stack2); + slot.onSlotChanged(); + didSomething = true; } - slot.putStack(stack2); - slot.onSlotChanged(); - didSomething = true; if (stack.stackSize <= 0) { break; diff --git a/src/main/java/tconstruct/util/network/CraftingStationStackSyncPacket.java b/src/main/java/tconstruct/util/network/CraftingStationStackSyncPacket.java new file mode 100644 index 00000000..8868cedc --- /dev/null +++ b/src/main/java/tconstruct/util/network/CraftingStationStackSyncPacket.java @@ -0,0 +1,119 @@ +package tconstruct.util.network; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import cpw.mods.fml.common.network.ByteBufUtils; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import mantle.common.network.AbstractPacket; +import tconstruct.tools.inventory.CraftingStationContainer; + +public class CraftingStationStackSyncPacket extends AbstractPacket { + + private static final int FLAG_HAS_STACK = 1; + private static final int FLAG_HAS_TAG = 2; + private static final int MAX_UPDATE_COUNT = 255; + + private int windowId; + private int[] slotIds; + private ItemStack[] stacks; + + public CraftingStationStackSyncPacket() {} + + public CraftingStationStackSyncPacket(int windowId, int[] slotIds, ItemStack[] stacks) { + if (slotIds == null || stacks == null || slotIds.length != stacks.length || slotIds.length > MAX_UPDATE_COUNT) { + throw new IllegalArgumentException("Crafting station update arrays must have the same size up to 255"); + } + + this.windowId = windowId; + this.slotIds = slotIds.clone(); + this.stacks = copyStacks(stacks); + } + + @Override + public void encodeInto(ChannelHandlerContext ctx, ByteBuf buffer) { + buffer.writeByte(windowId); + buffer.writeByte(slotIds.length); + + for (int i = 0; i < slotIds.length; i++) { + buffer.writeShort(slotIds[i]); + + ItemStack stack = stacks[i]; + if (stack == null || stack.stackSize <= 0 || stack.getItem() == null) { + buffer.writeByte(0); + continue; + } + + int flags = FLAG_HAS_STACK; + if (stack.hasTagCompound()) flags |= FLAG_HAS_TAG; + buffer.writeByte(flags); + ByteBufUtils.writeVarInt(buffer, Item.getIdFromItem(stack.getItem()), 3); + ByteBufUtils.writeVarInt(buffer, stack.stackSize, 5); + buffer.writeShort(stack.getItemDamage()); + if ((flags & FLAG_HAS_TAG) != 0) ByteBufUtils.writeTag(buffer, stack.getTagCompound()); + } + } + + @Override + public void decodeInto(ChannelHandlerContext ctx, ByteBuf buffer) { + windowId = buffer.readByte(); + int updateCount = buffer.readUnsignedByte(); + slotIds = new int[updateCount]; + stacks = new ItemStack[updateCount]; + + for (int i = 0; i < updateCount; i++) { + slotIds[i] = buffer.readShort(); + int flags = buffer.readUnsignedByte(); + if ((flags & FLAG_HAS_STACK) == 0) continue; + + int itemId = ByteBufUtils.readVarInt(buffer, 3); + int stackSize = ByteBufUtils.readVarInt(buffer, 5); + int itemDamage = buffer.readShort(); + Item item = Item.getItemById(itemId); + if (item == null || stackSize <= 0) { + if ((flags & FLAG_HAS_TAG) != 0) ByteBufUtils.readTag(buffer); + continue; + } + + ItemStack stack = new ItemStack(item, stackSize, itemDamage); + if ((flags & FLAG_HAS_TAG) != 0) stack.setTagCompound(ByteBufUtils.readTag(buffer)); + stacks[i] = stack; + } + } + + @Override + public void handleClientSide(EntityPlayer player) { + if (player == null || !(player.openContainer instanceof CraftingStationContainer container) + || container.windowId != windowId) { + return; + } + + for (int i = 0; i < slotIds.length; i++) { + if (slotIds[i] == -1) { + player.inventory.setItemStack(stacks[i]); + continue; + } + + if (slotIds[i] < 0 || slotIds[i] >= container.inventorySlots.size()) continue; + Slot slot = container.getSlot(slotIds[i]); + if (slot != null) { + slot.putStack(stacks[i]); + slot.onSlotChanged(); + } + } + } + + @Override + public void handleServerSide(EntityPlayer player) {} + + private static ItemStack[] copyStacks(ItemStack[] source) { + ItemStack[] copies = new ItemStack[source.length]; + for (int i = 0; i < source.length; i++) { + copies[i] = source[i] == null ? null : source[i].copy(); + } + return copies; + } +} diff --git a/src/main/java/tconstruct/util/network/PacketPipeline.java b/src/main/java/tconstruct/util/network/PacketPipeline.java index 454f19da..0c353fab 100644 --- a/src/main/java/tconstruct/util/network/PacketPipeline.java +++ b/src/main/java/tconstruct/util/network/PacketPipeline.java @@ -137,6 +137,7 @@ public class PacketPipeline extends MessageToMessageCodec