add extended crafting station stacks api (#344)

Co-authored-by: Martin Robertz <dream-master@gmx.net>
This commit is contained in:
ABKQPOandMartin Robertz authored and GitHub committed 2026-09-24 19:31:12 +00:00
1 parent 2e02b96996
commit 27d296fd4d
5 files changed
+728 -63

No files matched your search

@@ -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;
}
}
@@ -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.
*
* <p>
* 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.
* </p>
*/
public interface IExtendedStackLimitProvider {
int getExtendedStackLimit(int slotIndex, ItemStack stack);
}
@@ -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<EntityPlayerMP, Map<Integer, ItemStack>> extendedStackStates = new HashMap<>();
/** Slots selected by the client during an extended drag operation. */
private final Set<Slot> 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<Integer, ItemStack> previousStacks = extendedStackStates
.computeIfAbsent(playerMP, ignored -> new HashMap<>());
List<Integer> changedSlotIds = null;
List<ItemStack> 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<Integer> 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<Slot> 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<IRecipe> 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;
@@ -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;
}
}
@@ -137,6 +137,7 @@ public class PacketPipeline extends MessageToMessageCodec<FMLProxyPacket, Abstra
registerPacket(ArmourGuiSyncPacket.class);
registerPacket(CraftingStationDumpPacket.class);
registerPacket(CraftingStationStackSyncPacket.class);
}
// Method to call from FMLPostInitializationEvent