diff --git a/src/main/java/net/minecraft/server/Container.java b/src/main/java/net/minecraft/server/Container.java index e9ba2f4236..d48282cf27 100644 --- a/src/main/java/net/minecraft/server/Container.java +++ b/src/main/java/net/minecraft/server/Container.java @@ -7,7 +7,12 @@ import java.util.List; import java.util.Set; // CraftBukkit start +import java.util.HashMap; +import java.util.Map; import org.bukkit.craftbukkit.inventory.CraftInventory; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.event.Event.Result; +import org.bukkit.event.inventory.InventoryDragEvent; import org.bukkit.inventory.InventoryView; // CraftBukkit end @@ -18,7 +23,7 @@ public abstract class Container { public int windowId = 0; private short a = 0; private int f = -1; - private int g = 0; + public int g = 0; // CraftBukkit - private -> public private final Set h = new HashSet(); protected List listeners = new ArrayList(); private Set i = new HashSet(); @@ -140,6 +145,7 @@ public abstract class Container { l = playerinventory.getCarried().count; Iterator iterator = this.h.iterator(); + Map draggedSlots = new HashMap(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack) while (iterator.hasNext()) { Slot slot1 = (Slot) iterator.next(); @@ -157,16 +163,46 @@ public abstract class Container { } l -= itemstack2.count - j1; - slot1.set(itemstack2); + draggedSlots.put(slot1.g, itemstack2); // CraftBukkit - Put in map instead of setting, Should be Slot.rawSlotIndex } } - itemstack1.count = l; - if (itemstack1.count <= 0) { - itemstack1 = null; + // CraftBukkit start - InventoryDragEvent + InventoryView view = getBukkitView(); + org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack1); + newcursor.setAmount(l); + Map eventmap = new HashMap(); + for (Map.Entry ditem : draggedSlots.entrySet()) { + eventmap.put(ditem.getKey(), CraftItemStack.asBukkitCopy(ditem.getValue())); } - playerinventory.setCarried(itemstack1); + // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory. + ItemStack oldCursor = playerinventory.getCarried(); + playerinventory.setCarried(CraftItemStack.asNMSCopy(newcursor)); + + InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.f == 1, eventmap); // Should be dragButton + entityhuman.world.getServer().getPluginManager().callEvent(event); + + // Whether or not a change was made to the inventory that requires an update. + boolean needsUpdate = event.getResult() != Result.DEFAULT; + + if (event.getResult() != Result.DENY) { + for (Map.Entry dslot : draggedSlots.entrySet()) { + view.setItem(dslot.getKey(), CraftItemStack.asBukkitCopy(dslot.getValue())); + } + // The only time the carried item will be set to null is if the inventory is closed by the server. + // If the inventory is closed by the server, then the cursor items are dropped. This is why we change the cursor early. + if (playerinventory.getCarried() != null) { + playerinventory.setCarried(CraftItemStack.asNMSCopy(event.getCursor())); + needsUpdate = true; + + } + } + + if (needsUpdate && entityhuman instanceof EntityPlayer) { + ((EntityPlayer) entityhuman).updateInventory(this); + } + // CraftBukkit end } this.d(); diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java index 5d103824c8..42dbc56f0c 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -21,12 +21,18 @@ import org.bukkit.craftbukkit.util.Waitable; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Event; import org.bukkit.event.block.Action; import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCreativeEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryType.SlotType; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerAnimationEvent; @@ -1153,53 +1159,259 @@ public class PlayerConnection extends Connection { if (this.player.activeContainer.windowId == packet102windowclick.a && this.player.activeContainer.c(this.player)) { // CraftBukkit start - Call InventoryClickEvent - if (packet102windowclick.slot == -1) { - // Vanilla doesn't do anything with this, neither should we + if (packet102windowclick.slot < -1 && packet102windowclick.slot != -999) { return; } InventoryView inventory = this.player.activeContainer.getBukkitView(); SlotType type = CraftInventoryView.getSlotType(inventory, packet102windowclick.slot); - InventoryClickEvent event = new InventoryClickEvent(inventory, type, packet102windowclick.slot, packet102windowclick.button != 0, packet102windowclick.shift == 1); - org.bukkit.inventory.Inventory top = inventory.getTopInventory(); - if (packet102windowclick.slot == 0 && top instanceof CraftingInventory) { - org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe(); - if (recipe != null) { - event = new org.bukkit.event.inventory.CraftItemEvent(recipe, inventory, type, packet102windowclick.slot, packet102windowclick.button != 0, packet102windowclick.shift == 1); - } - } - server.getPluginManager().callEvent(event); + InventoryClickEvent event = null; + ClickType click = ClickType.UNKNOWN; + InventoryAction action = InventoryAction.UNKNOWN; ItemStack itemstack = null; - boolean defaultBehaviour = false; - switch(event.getResult()) { - case DEFAULT: - itemstack = this.player.activeContainer.clickItem(packet102windowclick.slot, packet102windowclick.button, packet102windowclick.shift, this.player); - defaultBehaviour = true; - break; - case DENY: // Deny any change, including changes from the event - break; - case ALLOW: // Allow changes unconditionally - org.bukkit.inventory.ItemStack cursor = event.getCursor(); - if (cursor == null) { - this.player.inventory.setCarried((ItemStack) null); - } else { - this.player.inventory.setCarried(CraftItemStack.asNMSCopy(cursor)); + if (packet102windowclick.slot == -1) { + type = SlotType.OUTSIDE; // override + click = packet102windowclick.button == 0 ? ClickType.WINDOW_BORDER_LEFT : ClickType.WINDOW_BORDER_RIGHT; + action = InventoryAction.NOTHING; + } else if (packet102windowclick.shift == 0) { + if (packet102windowclick.button == 0) { + click = ClickType.LEFT; + } else if (packet102windowclick.button == 1) { + click = ClickType.RIGHT; } - org.bukkit.inventory.ItemStack item = event.getCurrentItem(); - if (item != null) { - itemstack = CraftItemStack.asNMSCopy(item); + if (packet102windowclick.button == 0 || packet102windowclick.button == 1) { + action = InventoryAction.NOTHING; // Don't want to repeat ourselves if (packet102windowclick.slot == -999) { - this.player.drop(itemstack); + if (player.inventory.getCarried() != null) { + action = packet102windowclick.button == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; + } } else { - this.player.activeContainer.getSlot(packet102windowclick.slot).set(itemstack); + Slot slot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (slot != null) { + ItemStack clickedItem = slot.getItem(); + ItemStack cursor = player.inventory.getCarried(); + if (clickedItem == null) { + if (cursor != null) { + action = packet102windowclick.button == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE; + } + } else if (slot.a(player)) { // Should be Slot.isPlayerAllowed + if (cursor == null) { + action = packet102windowclick.button == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF; + } else if (slot.isAllowed(cursor)) { // Should be Slot.isItemAllowed + if (clickedItem.doMaterialsMatch(cursor) && ItemStack.equals(clickedItem, cursor)) { + int toPlace = packet102windowclick.button == 0 ? cursor.count : 1; + toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.count); + toPlace = Math.min(toPlace, slot.inventory.getMaxStackSize() - clickedItem.count); + if (toPlace == 1) { + action = InventoryAction.PLACE_ONE; + } else if (toPlace == cursor.count) { + action = InventoryAction.PLACE_ALL; + } else if (toPlace < 0) { + action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks + } else if (toPlace != 0) { + action = InventoryAction.PLACE_SOME; + } + } else if (cursor.count <= slot.a()) { // Should be Slot.getMaxStackSize() + action = InventoryAction.SWAP_WITH_CURSOR; + } + } else if (cursor.id == clickedItem.id && (!cursor.usesData() || cursor.getData() == clickedItem.getData()) && ItemStack.equals(cursor, clickedItem)) { + if (clickedItem.count >= 0) { + if (clickedItem.count + cursor.count <= cursor.getMaxStackSize()) { + // As of 1.5, this is result slots only + action = InventoryAction.PICKUP_ALL; + } + } + } + } + } } - } else if (packet102windowclick.slot != -999) { - this.player.activeContainer.getSlot(packet102windowclick.slot).set((ItemStack) null); } - break; + } else if (packet102windowclick.shift == 1) { + if (packet102windowclick.button == 0) { + click = ClickType.SHIFT_LEFT; + } else if (packet102windowclick.button == 1) { + click = ClickType.SHIFT_RIGHT; + } + if (packet102windowclick.button == 0 || packet102windowclick.button == 1) { + if (packet102windowclick.slot < 0) { + action = InventoryAction.NOTHING; + } else { + Slot slot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (slot != null && slot.a(this.player) && slot.d()) { // Should be Slot.hasItem() + action = InventoryAction.MOVE_TO_OTHER_INVENTORY; + } else { + action = InventoryAction.NOTHING; + } + } + } + } else if (packet102windowclick.shift == 2) { + if (packet102windowclick.button >= 0 && packet102windowclick.button < 9) { + click = ClickType.NUMBER_KEY; + Slot clickedSlot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (clickedSlot.a(player)) { + ItemStack hotbar = this.player.inventory.getItem(packet102windowclick.button); + boolean canCleanSwap = hotbar == null || (clickedSlot.inventory == player.inventory && clickedSlot.isAllowed(hotbar)); // the slot will accept the hotbar item + if (clickedSlot.d()) { + if (canCleanSwap) { + action = InventoryAction.HOTBAR_SWAP; + } else { + int firstEmptySlot = player.inventory.j(); // Should be Inventory.firstEmpty() + if (firstEmptySlot > -1) { + action = InventoryAction.HOTBAR_MOVE_AND_READD; + } else { + action = InventoryAction.NOTHING; // This is not sane! Mojang: You should test for other slots of same type + } + } + } else if (!clickedSlot.d() && hotbar != null && clickedSlot.isAllowed(hotbar)) { + action = InventoryAction.HOTBAR_SWAP; + } else { + action = InventoryAction.NOTHING; + } + } else { + action = InventoryAction.NOTHING; + } + // Special constructor for number key + event = new InventoryClickEvent(inventory, type, packet102windowclick.slot, click, action, packet102windowclick.button); + } + } else if (packet102windowclick.shift == 3) { + if (packet102windowclick.button == 2) { + click = ClickType.MIDDLE; + if (packet102windowclick.slot == -999) { + action = InventoryAction.NOTHING; + } else { + Slot slot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (slot != null && slot.d() && player.abilities.canInstantlyBuild && player.inventory.getCarried() == null) { + action = InventoryAction.CLONE_STACK; + } else { + action = InventoryAction.NOTHING; + } + } + } else { + click = ClickType.UNKNOWN; + action = InventoryAction.UNKNOWN; + } + } else if (packet102windowclick.shift == 4) { + if (packet102windowclick.slot >= 0) { + if (packet102windowclick.button == 0) { + click = ClickType.DROP; + Slot slot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (slot != null && slot.d() && slot.a(player) && slot.getItem() != null && slot.getItem().id != 0) { + action = InventoryAction.DROP_ONE_SLOT; + } else { + action = InventoryAction.NOTHING; + } + } else if (packet102windowclick.button == 1) { + click = ClickType.CONTROL_DROP; + Slot slot = this.player.activeContainer.getSlot(packet102windowclick.slot); + if (slot != null && slot.d() && slot.a(player) && slot.getItem() != null && slot.getItem().id != 0) { + action = InventoryAction.DROP_ALL_SLOT; + } else { + action = InventoryAction.NOTHING; + } + } + } else { + // Sane default (because this happens when they are holding nothing. Don't ask why.) + click = ClickType.LEFT; + if (packet102windowclick.button == 1) { + click = ClickType.RIGHT; + } + action = InventoryAction.NOTHING; + } + } else if (packet102windowclick.shift == 5) { + itemstack = this.player.activeContainer.clickItem(packet102windowclick.slot, packet102windowclick.button, 5, this.player); + } else if (packet102windowclick.shift == 6) { + click = ClickType.DOUBLE_CLICK; + action = InventoryAction.NOTHING; + if (packet102windowclick.slot >= 0 && this.player.inventory.getCarried() != null) { + ItemStack cursor = this.player.inventory.getCarried(); + action = InventoryAction.NOTHING; + // Quick check for if we have any of the item + if (inventory.getTopInventory().contains(cursor.id) || inventory.getBottomInventory().contains(cursor.id)) { + action = InventoryAction.COLLECT_TO_CURSOR; + } + } + } + // TODO check on updates + + if (packet102windowclick.shift != 5) { + if (click == ClickType.NUMBER_KEY) { + event = new InventoryClickEvent(inventory, type, packet102windowclick.slot, click, action, packet102windowclick.button); + } else { + event = new InventoryClickEvent(inventory, type, packet102windowclick.slot, click, action); + } + + org.bukkit.inventory.Inventory top = inventory.getTopInventory(); + if (packet102windowclick.slot == 0 && top instanceof CraftingInventory) { + org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe(); + if (recipe != null) { + if (click == ClickType.NUMBER_KEY) { + event = new CraftItemEvent(recipe, inventory, type, packet102windowclick.slot, click, action, packet102windowclick.button); + } else { + event = new CraftItemEvent(recipe, inventory, type, packet102windowclick.slot, click, action); + } + } + } + + server.getPluginManager().callEvent(event); + + switch (event.getResult()) { + case ALLOW: + case DEFAULT: + itemstack = this.player.activeContainer.clickItem(packet102windowclick.slot, packet102windowclick.button, packet102windowclick.shift, this.player); + break; + case DENY: + /* Needs enum constructor in InventoryAction + if (action.modifiesOtherSlots()) { + + } else { + if (action.modifiesCursor()) { + this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried())); + } + if (action.modifiesClicked()) { + this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem())); + } + }*/ + switch (action) { + // Modified other slots + case MOVE_TO_OTHER_INVENTORY: + case HOTBAR_MOVE_AND_READD: + case HOTBAR_SWAP: + case COLLECT_TO_CURSOR: + case UNKNOWN: + this.player.updateInventory(this.player.activeContainer); + break; + // Modified cursor and clicked + case PICKUP_ALL: + case PICKUP_SOME: + case PICKUP_HALF: + case PICKUP_ONE: + case PLACE_ALL: + case PLACE_SOME: + case PLACE_ONE: + case SWAP_WITH_CURSOR: + this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried())); + this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem())); + break; + // Modified clicked only + case DROP_ALL_SLOT: + case DROP_ONE_SLOT: + this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem())); + break; + // Modified cursor only + case DROP_ALL_CURSOR: + case DROP_ONE_CURSOR: + case CLONE_STACK: + this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried())); + break; + // Nothing + case NOTHING: + break; + } + return; + } } // CraftBukkit end @@ -1247,40 +1459,46 @@ public class PlayerConnection extends Connection { boolean flag3 = itemstack == null || itemstack.getData() >= 0 && itemstack.getData() >= 0 && itemstack.count <= 64 && itemstack.count > 0; // CraftBukkit start - Call click event - org.bukkit.entity.HumanEntity player = this.player.getBukkitEntity(); - InventoryView inventory = new CraftInventoryView(player, player.getInventory(), this.player.defaultContainer); - SlotType slot = SlotType.QUICKBAR; - if (packet107setcreativeslot.slot == -1) { - slot = SlotType.OUTSIDE; - } + if (flag1 || flag) { // Insist on valid slot + ItemStack existingItem = this.player.defaultContainer.getSlot(packet107setcreativeslot.slot).getItem(); + // Client assumes that the server forgets the contents of the inventory. It doesn't. + if (!ItemStack.matches(existingItem, packet107setcreativeslot.b)) { - InventoryClickEvent event = new InventoryClickEvent(inventory, slot, slot == SlotType.OUTSIDE ? -999 : packet107setcreativeslot.slot, false, false); - server.getPluginManager().callEvent(event); - org.bukkit.inventory.ItemStack item = event.getCurrentItem(); + org.bukkit.entity.HumanEntity player = this.player.getBukkitEntity(); + InventoryView inventory = new CraftInventoryView(player, player.getInventory(), this.player.defaultContainer); + org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet107setcreativeslot.b); // Should be packet107setcreativeslot.newitem - switch (event.getResult()) { - case ALLOW: - if (slot == SlotType.QUICKBAR) { - if (item == null) { - this.player.defaultContainer.setItem(packet107setcreativeslot.slot, (ItemStack) null); - } else { - this.player.defaultContainer.setItem(packet107setcreativeslot.slot, CraftItemStack.asNMSCopy(item)); + SlotType type = SlotType.QUICKBAR; + if (flag) { + type = SlotType.OUTSIDE; + } else if (packet107setcreativeslot.slot < 36) { + if (packet107setcreativeslot.slot >= 5 && packet107setcreativeslot.slot < 9) { + type = SlotType.ARMOR; + } else { + type = SlotType.CONTAINER; + } + } + InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet107setcreativeslot.slot, item); + server.getPluginManager().callEvent(event); + + itemstack = CraftItemStack.asNMSCopy(event.getCursor()); + + switch (event.getResult()) { + case ALLOW: + // Plugin cleared the id / stacksize checks + flag2 = flag3 = true; + break; + case DEFAULT: + break; + case DENY: + // Reset the slot + if (packet107setcreativeslot.slot >= 0) { + this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.defaultContainer.windowId, packet107setcreativeslot.slot, this.player.defaultContainer.getSlot(packet107setcreativeslot.slot).getItem())); + this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, null)); + } + return; } - } else if (item != null) { - this.player.drop(CraftItemStack.asNMSCopy(item)); } - return; - case DENY: - // TODO: Will this actually work? - if (packet107setcreativeslot.slot > -1) { - this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.defaultContainer.windowId, packet107setcreativeslot.slot, CraftItemStack.asNMSCopy(item))); - } - return; - case DEFAULT: - // We do the stuff below - break; - default: - return; } // CraftBukkit end