From 05bd26eb87d873cad66abf762299c05633cc56b4 Mon Sep 17 00:00:00 2001 From: Chicken-Bomes Date: Fri, 29 May 2015 17:32:27 +1000 Subject: [PATCH] Moved ExtendedContainer code from CCC --- .../lib/inventory/ContainerExtended.java | 297 ++++++++++++++++++ .../lib/inventory/ContainerSynchronised.java | 67 ++++ .../lib/inventory/GuiContainerWidget.java | 131 ++++++++ .../lib/inventory/IContainerSyncVar.java | 14 + .../lib/inventory/IntegerSync.java | 34 ++ src/codechicken/lib/inventory/SlotDummy.java | 72 +++++ .../lib/inventory/SlotDummyOutput.java | 19 ++ .../lib/inventory/SlotHandleClicks.java | 16 + src/codechicken/lib/packet/PacketCustom.java | 12 +- 9 files changed, 656 insertions(+), 6 deletions(-) create mode 100644 src/codechicken/lib/inventory/ContainerExtended.java create mode 100644 src/codechicken/lib/inventory/ContainerSynchronised.java create mode 100644 src/codechicken/lib/inventory/GuiContainerWidget.java create mode 100644 src/codechicken/lib/inventory/IContainerSyncVar.java create mode 100644 src/codechicken/lib/inventory/IntegerSync.java create mode 100644 src/codechicken/lib/inventory/SlotDummy.java create mode 100644 src/codechicken/lib/inventory/SlotDummyOutput.java create mode 100644 src/codechicken/lib/inventory/SlotHandleClicks.java diff --git a/src/codechicken/lib/inventory/ContainerExtended.java b/src/codechicken/lib/inventory/ContainerExtended.java new file mode 100644 index 0000000..7c7cb15 --- /dev/null +++ b/src/codechicken/lib/inventory/ContainerExtended.java @@ -0,0 +1,297 @@ +package codechicken.lib.inventory; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import codechicken.lib.packet.PacketCustom; +import codechicken.lib.packet.PacketCustom.IClientPacketHandler; +import codechicken.lib.packet.PacketCustom.IServerPacketHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; +import net.minecraft.client.Minecraft; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.ICrafting; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.inventory.Slot; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.INetHandlerPlayServer; +import org.apache.logging.log4j.LogManager; + +/** + * Clean container implementation with a few extra features. + * Easy shift-click handling. + * Hooks for slot clicks, large stack sizes and some networking. + */ +public abstract class ContainerExtended extends Container implements ICrafting +{ + private static final String netChannel = "CCL:Container"; + private static int nextNetworkID = 0; + static { + PacketCustom.assignHandler(netChannel, new IClientPacketHandler() + { + @Override + public void handlePacket(PacketCustom packet, Minecraft mc, INetHandlerPlayClient handler) { + Container cont = mc.thePlayer.openContainer; + if(!(cont instanceof ContainerExtended)) + return; + + ContainerExtended c = (ContainerExtended)cont; + if(packet.getType() == 1) + c.netID = packet.readInt(); + else if(c.netID == packet.readInt()) + c.handleClientPacket(packet); + } + }); + PacketCustom.assignHandler(netChannel, new IServerPacketHandler() + { + @Override + public void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer handler) { + Container cont = sender.openContainer; + if(!(cont instanceof ContainerExtended)) + return; + + ContainerExtended c = (ContainerExtended)cont; + if(c.netID == packet.readInt()) + c.handleServerPacket(packet); + } + }); + } + + public LinkedList playerCrafters = new LinkedList(); + private int netID; + + public ContainerExtended() { + crafters.add(this); + if(FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) + netID = ++nextNetworkID; + } + + @Override + public void addCraftingToCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) { + playerCrafters.add((EntityPlayerMP) icrafting); + sendNetID((EntityPlayerMP) icrafting); + sendContainerAndContentsToPlayer(this, getInventory(), Arrays.asList((EntityPlayerMP) icrafting)); + detectAndSendChanges(); + } else + super.addCraftingToCrafters(icrafting); + } + + private void sendNetID(EntityPlayerMP player) { + if(netID == 0) + LogManager.getLogger("CodeChickenLib").error("Player added to container with 0 network ID"); + else + new PacketCustom(netChannel, 1).writeInt(netID).sendToPlayer(player); + } + + @Override + public void removeCraftingFromCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) + playerCrafters.remove(icrafting); + else + super.removeCraftingFromCrafters(icrafting); + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list) { + sendContainerAndContentsToPlayer(container, list, playerCrafters); + } + + public void sendContainerAndContentsToPlayer(Container container, List list, List playerCrafters) { + LinkedList largeStacks = new LinkedList(); + for (int i = 0; i < list.size(); i++) { + ItemStack stack = list.get(i); + if (stack != null && stack.stackSize > Byte.MAX_VALUE) { + list.set(i, null); + largeStacks.add(stack); + } else + largeStacks.add(null); + } + + for (EntityPlayerMP player : playerCrafters) + player.sendContainerAndContentsToPlayer(container, list); + + for (int i = 0; i < largeStacks.size(); i++) { + ItemStack stack = largeStacks.get(i); + if (stack != null) + sendLargeStack(stack, i, playerCrafters); + } + } + + public void sendLargeStack(ItemStack stack, int slot, List players) { + } + + @Override + public void sendProgressBarUpdate(Container container, int i, int j) { + for (EntityPlayerMP player : playerCrafters) + player.sendProgressBarUpdate(container, i, j); + } + + @Override + public void sendSlotContents(Container container, int slot, ItemStack stack) { + if (stack != null && stack.stackSize > Byte.MAX_VALUE) + sendLargeStack(stack, slot, playerCrafters); + else + for (EntityPlayerMP player : playerCrafters) + player.sendSlotContents(container, slot, stack); + } + + @Override + public ItemStack slotClick(int par1, int par2, int par3, EntityPlayer player) { + if (par1 >= 0 && par1 < inventorySlots.size()) { + Slot slot = getSlot(par1); + if (slot instanceof SlotHandleClicks) + return ((SlotHandleClicks) slot).slotClick(this, player, par2, par3); + } + return super.slotClick(par1, par2, par3, player); + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int slotIndex) { + ItemStack transferredStack = null; + Slot slot = (Slot) inventorySlots.get(slotIndex); + + if (slot != null && slot.getHasStack()) { + ItemStack stack = slot.getStack(); + transferredStack = stack.copy(); + + if (!doMergeStackAreas(slotIndex, stack)) + return null; + + if (stack.stackSize == 0) + slot.putStack(null); + else + slot.onSlotChanged(); + } + + return transferredStack; + } + + @Override + public boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean reverse) { + boolean merged = false; + int slotIndex = reverse ? endIndex - 1 : startIndex; + + if (stack == null) + return false; + + if (stack.isStackable())//search for stacks to increase + { + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) inventorySlots.get(slotIndex); + ItemStack slotStack = slot.getStack(); + + if (slotStack != null && slotStack.getItem() == stack.getItem() && + (!stack.getHasSubtypes() || stack.getItemDamage() == slotStack.getItemDamage()) && + ItemStack.areItemStackTagsEqual(stack, slotStack)) { + int totalStackSize = slotStack.stackSize + stack.stackSize; + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (totalStackSize <= maxStackSize) { + stack.stackSize = 0; + slotStack.stackSize = totalStackSize; + slot.onSlotChanged(); + merged = true; + } else if (slotStack.stackSize < maxStackSize) { + stack.stackSize -= maxStackSize - slotStack.stackSize; + slotStack.stackSize = maxStackSize; + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + if (stack.stackSize > 0)//normal transfer :) + { + slotIndex = reverse ? endIndex - 1 : startIndex; + + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) this.inventorySlots.get(slotIndex); + + if (!slot.getHasStack() && slot.isItemValid(stack)) { + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (stack.stackSize <= maxStackSize) { + slot.putStack(stack.copy()); + slot.onSlotChanged(); + stack.stackSize = 0; + merged = true; + } else { + slot.putStack(stack.splitStack(maxStackSize)); + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + return merged; + } + + /** + * Called when slotIndex is shift clicked on. Recommended implementation is to call mergeItemStack based on slotIndex + * + * @param stack The stack in the clicked slot + * @return True if one or more items were moved from this slots into other slots + */ + public boolean doMergeStackAreas(int slotIndex, ItemStack stack) { + return false; + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) { + bindPlayerInventory(inventoryPlayer, 8, 84); + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer, int x, int y) { + for (int row = 0; row < 3; row++) + for (int col = 0; col < 9; col++) + addSlotToContainer(new Slot(inventoryPlayer, col + row * 9 + 9, x + col * 18, y + row * 18)); + for (int slot = 0; slot < 9; slot++) + addSlotToContainer(new Slot(inventoryPlayer, slot, x + slot * 18, y + 58)); + } + + @Override + public boolean canInteractWith(EntityPlayer var1) { + return true; + } + + public void sendContainerPacket(PacketCustom packet) { + for (EntityPlayerMP player : playerCrafters) + packet.sendToPlayer(player); + } + + /** + * @param type An identifying number for the packet type between 2 and 0x79 inclusive. 1 is reserved for synchronising networkIDs + * @return A packet on the CCL inventory channel that will be recieved by this container on the other network side + */ + public PacketCustom getPacket(int type) { + if(netID == 0) + LogManager.getLogger("CodeChickenLib").error("Tried to get packet for container with 0 network ID"); + if(type == 1) + throw new IllegalArgumentException("Packet type 1 is reserved for network synchronisation in ContainerExtended"); + + return new PacketCustom(netChannel, type).writeInt(netID); + } + + /** + * Handle a packet from the server obtained by getPacket. + */ + public void handleClientPacket(PacketCustom packet) {} + + /** + * Handle a packet from the client obtained by getPacket. + */ + public void handleServerPacket(PacketCustom packet) {} + + public void sendProgressBarUpdate(int barID, int value) { + for (ICrafting crafting : (List) crafters) + crafting.sendProgressBarUpdate(this, barID, value); + } +} diff --git a/src/codechicken/lib/inventory/ContainerSynchronised.java b/src/codechicken/lib/inventory/ContainerSynchronised.java new file mode 100644 index 0000000..f2fee59 --- /dev/null +++ b/src/codechicken/lib/inventory/ContainerSynchronised.java @@ -0,0 +1,67 @@ +package codechicken.lib.inventory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.item.ItemStack; + +import codechicken.lib.packet.PacketCustom; + +public abstract class ContainerSynchronised extends ContainerExtended +{ + private ArrayList syncVars = new ArrayList(); + + /** + * Create a packet to be used to send a synced variable update. + * Calls getPacket. Can be overriden to add extra identifying data or change the type (default 2) + */ + public PacketCustom createSyncPacket() { + return getPacket(2); + } + + @Override + public final void detectAndSendChanges() { + super.detectAndSendChanges(); + + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + if (var.changed()) { + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + sendContainerPacket(packet); + var.reset(); + } + } + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list, List playerCrafters) { + super.sendContainerAndContentsToPlayer(container, list, playerCrafters); + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + var.reset(); + for (EntityPlayerMP player : playerCrafters) + packet.sendToPlayer(player); + } + } + + public void addSyncVar(IContainerSyncVar var) { + syncVars.add(var); + } + + @Override + public final void handleClientPacket(PacketCustom packet) { + syncVars.get(packet.readUByte()).readChange(packet); + } + + public List getSyncedVars() { + return Collections.unmodifiableList(syncVars); + } +} diff --git a/src/codechicken/lib/inventory/GuiContainerWidget.java b/src/codechicken/lib/inventory/GuiContainerWidget.java new file mode 100644 index 0000000..eaf4226 --- /dev/null +++ b/src/codechicken/lib/inventory/GuiContainerWidget.java @@ -0,0 +1,131 @@ +package codechicken.lib.inventory; + +import java.awt.Point; +import java.util.ArrayList; + +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import codechicken.lib.gui.GuiDraw; +import codechicken.core.gui.GuiWidget; +import codechicken.core.gui.IGuiActionListener; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Container; + +public class GuiContainerWidget extends GuiContainer implements IGuiActionListener +{ + public ArrayList widgets = new ArrayList(); + + public GuiContainerWidget(Container inventorySlots) + { + this(inventorySlots, 176, 166); + } + + public GuiContainerWidget(Container inventorySlots, int xSize, int ySize) + { + super(inventorySlots); + this.xSize = xSize; + this.ySize = ySize; + } + + @Override + public void setWorldAndResolution(Minecraft mc, int i, int j) + { + super.setWorldAndResolution(mc, i, j); + if(widgets.isEmpty()) + addWidgets(); + } + + public void add(GuiWidget widget) + { + widgets.add(widget); + widget.onAdded(this); + } + + @Override + protected void drawGuiContainerBackgroundLayer(float f, int mousex, int mousey) + { + GL11.glTranslated(guiLeft, guiTop, 0); + drawBackground(); + for(GuiWidget widget : widgets) + widget.draw(mousex-guiLeft, mousey-guiTop, f); + + GL11.glTranslated(-guiLeft, -guiTop, 0); + } + + public void drawBackground() + { + } + + @Override + protected void mouseClicked(int x, int y, int button) + { + super.mouseClicked(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseClicked(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseMovedOrUp(int x, int y, int button) + { + super.mouseMovedOrUp(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseMovedOrUp(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseClickMove(int x, int y, int button, long time) + { + super.mouseClickMove(x, y, button, time); + for(GuiWidget widget : widgets) + widget.mouseDragged(x-guiLeft, y-guiTop, button, time); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + if(mc.currentScreen == this) + for(GuiWidget widget : widgets) + widget.update(); + } + + @Override + public void keyTyped(char c, int keycode) + { + super.keyTyped(c, keycode); + for(GuiWidget widget : widgets) + widget.keyTyped(c, keycode); + } + + @Override + public void handleMouseInput() + { + super.handleMouseInput(); + int i = Mouse.getEventDWheel(); + if(i != 0) + { + Point p = GuiDraw.getMousePosition(); + int scroll = i > 0 ? 1 : -1; + for(GuiWidget widget : widgets) + widget.mouseScrolled(p.x, p.y, scroll); + } + } + + @Override + public void actionPerformed(String ident, Object... params) + { + } + + public void addWidgets() + { + } + + @Override + public void onGuiClosed() { + super.onGuiClosed(); + widgets.clear(); + } +} diff --git a/src/codechicken/lib/inventory/IContainerSyncVar.java b/src/codechicken/lib/inventory/IContainerSyncVar.java new file mode 100644 index 0000000..dc3b01f --- /dev/null +++ b/src/codechicken/lib/inventory/IContainerSyncVar.java @@ -0,0 +1,14 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public interface IContainerSyncVar +{ + public boolean changed(); + + public void reset(); + + public void writeChange(PacketCustom packet); + + public void readChange(PacketCustom packet); +} diff --git a/src/codechicken/lib/inventory/IntegerSync.java b/src/codechicken/lib/inventory/IntegerSync.java new file mode 100644 index 0000000..841e564 --- /dev/null +++ b/src/codechicken/lib/inventory/IntegerSync.java @@ -0,0 +1,34 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public abstract class IntegerSync implements IContainerSyncVar +{ + public int c_value; + + @Override + public boolean changed() + { + return getValue() != c_value; + } + + @Override + public void reset() + { + c_value = getValue(); + } + + @Override + public void writeChange(PacketCustom packet) + { + packet.writeInt(getValue()); + } + + @Override + public void readChange(PacketCustom packet) + { + c_value = packet.readInt(); + } + + public abstract int getValue(); +} diff --git a/src/codechicken/lib/inventory/SlotDummy.java b/src/codechicken/lib/inventory/SlotDummy.java new file mode 100644 index 0000000..d72320c --- /dev/null +++ b/src/codechicken/lib/inventory/SlotDummy.java @@ -0,0 +1,72 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummy extends SlotHandleClicks +{ + public final int stackLimit; + public SlotDummy(IInventory inv, int slot, int x, int y) + { + this(inv, slot, x, y, 64); + } + + public SlotDummy(IInventory inv, int slot, int x, int y, int limit) + { + super(inv, slot, x, y); + stackLimit = limit; + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) + { + ItemStack held = player.inventory.getItemStack(); + boolean shift = modifier == 1; + slotClick(held, button, shift); + return null; + } + + public void slotClick(ItemStack held, int button, boolean shift) + { + ItemStack tstack = getStack(); + if(held != null && (tstack == null || !InventoryUtils.canStack(held, tstack))) + { + int quantity = Math.min(held.stackSize, stackLimit); + if(shift) + quantity = Math.min(stackLimit, held.getMaxStackSize()*16); + if(button == 1) + quantity = 1; + putStack(InventoryUtils.copyStack(held, quantity)); + } + else if(tstack != null) + { + int inc; + if(held != null) + { + inc = button == 1 ? -held.stackSize : held.stackSize; + if(shift) + inc *= 16; + } + else + { + inc = button == 1 ? -1 : 1; + if(shift) + inc *= 16; + } + int quantity = tstack.stackSize+inc; + if(quantity <= 0) + putStack(null); + else + putStack(InventoryUtils.copyStack(tstack, quantity)); + } + } + + @Override + public void putStack(ItemStack stack) + { + if(stack != null && stack.stackSize > stackLimit) + stack = InventoryUtils.copyStack(stack, stackLimit); + super.putStack(stack); + } +} diff --git a/src/codechicken/lib/inventory/SlotDummyOutput.java b/src/codechicken/lib/inventory/SlotDummyOutput.java new file mode 100644 index 0000000..b8fab64 --- /dev/null +++ b/src/codechicken/lib/inventory/SlotDummyOutput.java @@ -0,0 +1,19 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummyOutput extends SlotHandleClicks +{ + public SlotDummyOutput(IInventory inv, int slot, int x, int y) + { + super(inv, slot, x, y); + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) + { + return null; + } +} diff --git a/src/codechicken/lib/inventory/SlotHandleClicks.java b/src/codechicken/lib/inventory/SlotHandleClicks.java new file mode 100644 index 0000000..7e56ddd --- /dev/null +++ b/src/codechicken/lib/inventory/SlotHandleClicks.java @@ -0,0 +1,16 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +public abstract class SlotHandleClicks extends Slot +{ + public SlotHandleClicks(IInventory inv, int slot, int x, int y) + { + super(inv, slot, x, y); + } + + public abstract ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier); +} diff --git a/src/codechicken/lib/packet/PacketCustom.java b/src/codechicken/lib/packet/PacketCustom.java index fe5d32e..0d6bf2f 100644 --- a/src/codechicken/lib/packet/PacketCustom.java +++ b/src/codechicken/lib/packet/PacketCustom.java @@ -147,17 +147,17 @@ public final class PacketCustom implements MCDataInput, MCDataOutput public static String channelName(Object channelKey) { if (channelKey instanceof String) return (String) channelKey; - if (channelKey instanceof ModContainer) { - String s = ((ModContainer) channelKey).getModId(); + + ModContainer mc = channelKey instanceof ModContainer ? + (ModContainer) channelKey : + FMLCommonHandler.instance().findContainerFor(channelKey); + if (mc != null) { + String s = mc.getModId(); if(s.length() > 20) throw new IllegalArgumentException("Mod ID ("+s+") too long for use as channel (20 chars). Use a string identifier"); return s; } - ModContainer mc = FMLCommonHandler.instance().findContainerFor(channelKey); - if (mc != null) - return mc.getModId(); - throw new IllegalArgumentException("Invalid channel: " + channelKey); }