package de.take_weiland.mods.commons.inv; import com.google.common.collect.ImmutableSet; import cpw.mods.fml.relauncher.Side; import de.take_weiland.mods.commons.internal.ContainerProxy; import de.take_weiland.mods.commons.internal.PacketContainerButton; import de.take_weiland.mods.commons.internal.SevenCommons; import de.take_weiland.mods.commons.util.ItemStacks; import de.take_weiland.mods.commons.util.SCReflector; 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; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.Slot; import net.minecraft.item.ItemStack; import java.util.List; import java.util.Set; import java.util.UUID; /** * <p>Utilities for working with {@link net.minecraft.inventory.Container Inventory Containers}.</p> */ public final class Containers { static final int PLAYER_INV_Y_DEFAULT = 84; static final int PLAYER_INV_X_DEFAULT = 8; private static final int SLOT_HEIGHT = 18; /** * <p>Add the full player inventory to the given Container.</p> * <p>This method uses the default coordinates (8, 84) as the start position and each Slot is assumed to be 18x18 pixels.</p> * @param container the Container * @param inventoryPlayer the player inventory */ public static void addPlayerInventory(Container container, InventoryPlayer inventoryPlayer) { addPlayerInventory(container, inventoryPlayer, PLAYER_INV_X_DEFAULT, PLAYER_INV_Y_DEFAULT); } /** * <p>Add the full player inventory to the given Container.</p> * <p>This method uses the given start coordinates, each Slot is assumed to be 18x18 pixels.</p> * @param container the Container * @param inventoryPlayer the player inventory * @param xStart the x coordinate * @param yStart the y coordinate */ public static void addPlayerInventory(Container container, InventoryPlayer inventoryPlayer, int xStart, int yStart) { Set<UUID> allItemInvs = allItemInventories(container); // add the upper 3 rows for (int y = 0; y < 3; y++) { for (int x = 0; x < 9; x++) { SCReflector.instance.addSlot(container, new PlayerSlot(inventoryPlayer, x + y * 9 + 9, xStart + x * SLOT_HEIGHT, yStart + y * SLOT_HEIGHT, allItemInvs)); } } // add the hotbar for (int k = 0; k < 9; k++) { SCReflector.instance.addSlot(container, new PlayerSlot(inventoryPlayer, k, xStart + k * 18, yStart + 58, allItemInvs)); } } /** * <p>Trigger the given button on the currently open Container. This method will call * {@link de.take_weiland.mods.commons.inv.ButtonContainer#onButtonClick(cpw.mods.fml.relauncher.Side, net.minecraft.entity.player.EntityPlayer, int)} * on both client and server.</p> * <p>This method must only be called from the client thread and will throw a {@code ClassCastException} if the * currently open container does not implement {@code ButtonContainer}.</p> * @param button the button */ public static void triggerButton(int button) { EntityPlayer player = SevenCommons.proxy.getClientPlayer(); ((ButtonContainer) player.openContainer).onButtonClick(Side.CLIENT, player, button); new PacketContainerButton(player.openContainer.windowId, button).sendToServer(); } /** * <p>Get all inventories present in the given Container. The inventories are returned in order of first appearance * by the Iterator of the returned Set.</p> * @param container the Container * @return all inventories */ public static ImmutableSet<IInventory> getInventories(Container container) { return ((ContainerProxy) container)._sc$getInventories(); } /** * <p>Get the player interacting with the given Container. On the client this is always {@code Minecraft#thePlayer}.</p> * @param container the Container * @return the player */ public static EntityPlayer getViewer(Container container) { List<ICrafting> listeners = SCReflector.instance.getCrafters(container); for (int i = 0, len = listeners.size(); i < len; i++) { ICrafting listener = listeners.get(i); if (listener instanceof EntityPlayerMP) { return (EntityPlayer) listener; } } return SevenCommons.proxy.getClientPlayer(); } /** * <p>Implementation for shift-clicking in Containers. This is a drop-in replacement you can call from the * {@link Container#transferStackInSlot(net.minecraft.entity.player.EntityPlayer, int)} method in your Container.</p> * <ul> * <li>When the slot to be moved is not in the inventory of the player it will be moved there as known * from vanilla inventories.</li> * <li>When the slot to be moved is in the inventory of the player it will be moved to the first available Slot * that accepts it.</li> * </ul> * <p>This behavior can be overridden by implementing {@link de.take_weiland.mods.commons.inv.SpecialShiftClick} on * your Container.</p> * @param container the Container * @param player the player performing the shift-click * @param slotIndex the slot being shift-clicked * @return null if no transfer is possible */ public static ItemStack handleShiftClick(Container container, EntityPlayer player, int slotIndex) { @SuppressWarnings("unchecked") List<Slot> slots = container.inventorySlots; Slot sourceSlot = slots.get(slotIndex); ItemStack inputStack = sourceSlot.getStack(); if (inputStack == null) return null; boolean sourceIsPlayer = sourceSlot.inventory == player.inventory; ItemStack copy = inputStack.copy(); if (sourceIsPlayer) { if (container instanceof SpecialShiftClick) { ShiftClickTarget target = ((SpecialShiftClick) container).getShiftClickTarget(inputStack, player); if (!target.isStandard()) { if (target.isNone() || !mergeToTarget(player.inventory, sourceSlot, slots, target)) { return null; } else { return copy; } } } // transfer to any inventory if (!mergeStack(player.inventory, false, sourceSlot, slots, false)) { return null; } else { return copy; } } else { // transfer to player inventory // this is heuristic, but should do fine. if it doesn't the only "issue" is that vanilla behavior is not matched 100% boolean isMachineOutput = !sourceSlot.isItemValid(inputStack); if (!mergeStack(player.inventory, true, sourceSlot, slots, !isMachineOutput)) { return null; } else { return copy; } } } // same as mergeStack, but uses a ShiftClickTarget // ugly copy paste is ugly private static boolean mergeToTarget(InventoryPlayer playerInv, Slot sourceSlot, List<Slot> slots, ShiftClickTarget target) { ItemStack sourceStack = sourceSlot.getStack(); int originalSize = sourceStack.stackSize; // first pass, try merge with existing stacks target.reset(); while (sourceStack.stackSize > 0 && target.hasNext()) { Slot targetSlot = slots.get(target.next()); if (targetSlot.inventory != playerInv) { ItemStack targetStack = targetSlot.getStack(); if (ItemStacks.equal(targetStack, sourceStack)) { int targetMax = Math.min(targetSlot.getSlotStackLimit(), targetStack.getMaxStackSize()); int toTransfer = Math.min(sourceStack.stackSize, targetMax - targetStack.stackSize); if (toTransfer > 0) { targetStack.stackSize += toTransfer; sourceStack.stackSize -= toTransfer; targetSlot.onSlotChanged(); } } } } if (sourceStack.stackSize == 0) { sourceSlot.putStack(null); return true; } //2nd pass: merge anything leftover into empty slots target.reset(); while (target.hasNext()) { Slot targetSlot = slots.get(target.next()); if (targetSlot.inventory != playerInv && !targetSlot.getHasStack() && targetSlot.isItemValid(sourceStack)) { targetSlot.putStack(sourceStack); sourceSlot.putStack(null); return true; } } if (originalSize != sourceStack.stackSize) { sourceSlot.onSlotChanged(); return true; } else { return false; } } // returns true if it has found a target private static boolean mergeStack(InventoryPlayer playerInv, boolean mergeIntoPlayer, Slot sourceSlot, List<Slot> slots, boolean reverse) { ItemStack sourceStack = sourceSlot.getStack(); int originalSize = sourceStack.stackSize; int len = slots.size(); int idx; // first pass, try to merge with existing stacks // can skip this if stack is not stackable at all if (sourceStack.isStackable()) { idx = reverse ? len - 1 : 0; while (sourceStack.stackSize > 0 && (reverse ? idx >= 0 : idx < len)) { Slot targetSlot = slots.get(idx); if ((targetSlot.inventory == playerInv) == mergeIntoPlayer) { ItemStack target = targetSlot.getStack(); if (ItemStacks.equal(sourceStack, target)) { // also checks target != null, because stack is never null int targetMax = Math.min(targetSlot.getSlotStackLimit(), target.getMaxStackSize()); int toTransfer = Math.min(sourceStack.stackSize, targetMax - target.stackSize); if (toTransfer > 0) { target.stackSize += toTransfer; sourceStack.stackSize -= toTransfer; targetSlot.onSlotChanged(); } } } if (reverse) { idx--; } else { idx++; } } if (sourceStack.stackSize == 0) { sourceSlot.putStack(null); return true; } } // 2nd pass: try to put anything remaining into a free slot idx = reverse ? len - 1 : 0; while (reverse ? idx >= 0 : idx < len) { Slot targetSlot = slots.get(idx); if ((targetSlot.inventory == playerInv) == mergeIntoPlayer && !targetSlot.getHasStack() && targetSlot.isItemValid(sourceStack)) { targetSlot.putStack(sourceStack); sourceSlot.putStack(null); return true; } if (reverse) { idx--; } else { idx++; } } // we had success in merging only a partial stack if (sourceStack.stackSize != originalSize) { sourceSlot.onSlotChanged(); return true; } return false; } private static Set<UUID> allItemInventories(Container container) { ImmutableSet.Builder<UUID> builder = ImmutableSet.builder(); ItemInventory last = null; @SuppressWarnings("unchecked") List<Slot> slots = container.inventorySlots; for (Slot slot : slots) { // keep reference to last found inventory, to avoid adding // many duplicates to the builder, because the filtering of those // does not happen before .build() if (last != slot.inventory && slot.inventory instanceof ItemInventory) { last = (ItemInventory) slot.inventory; builder.add(last.uuid); } } return builder.build(); } private Containers() { } }