diff --git a/src/main/java/org/bukkit/event/inventory/ClickType.java b/src/main/java/org/bukkit/event/inventory/ClickType.java new file mode 100644 index 00000000..dc2928cc --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/ClickType.java @@ -0,0 +1,115 @@ +package org.bukkit.event.inventory; + +/** + * What the client did to trigger this action (not the result). + */ +public enum ClickType { + /** + * The left (or primary) mouse button. + */ + LEFT, + /** + * Holding shift while pressing the left mouse button. + */ + SHIFT_LEFT, + /** + * The right mouse button. + */ + RIGHT, + /** + * Holding shift while pressing the right mouse button. + */ + SHIFT_RIGHT, + /** + * Clicking the left mouse button on the grey area around the + * inventory. + */ + WINDOW_BORDER_LEFT, + /** + * Clicking the right mouse button on the grey area around the + * inventory. + */ + WINDOW_BORDER_RIGHT, + /** + * The middle mouse button, or a "scrollwheel click". + */ + MIDDLE, + /** + * One of the number keys 1-9, correspond to slots on the hotbar. + */ + NUMBER_KEY, + /** + * Pressing the left mouse button twice in quick succession. + */ + DOUBLE_CLICK, + /** + * The "Drop" key (defaults to Q). + */ + DROP, + /** + * Holding Ctrl while pressing the "Drop" key (defaults to Q). + */ + CONTROL_DROP, + /** + * Any action done with the Creative inventory open. + */ + CREATIVE, + /** + * A type of inventory manipulation not yet recognized by Bukkit. + * This is only for transitional purposes on a new Minecraft update, + * and should never be relied upon. + *

+ * Any ClickType.UNKNOWN is called on a best-effort basis. + */ + UNKNOWN, + ; + + /** + * Gets whether this ClickType represents the pressing of a key on a + * keyboard. + * + * @return true if this ClickType represents the pressing of a key + */ + public boolean isKeyboardClick() { + return (this == ClickType.NUMBER_KEY) || (this == ClickType.DROP) || (this == ClickType.CONTROL_DROP); + } + + /** + * Gets whether this ClickType represents an action that can only be + * performed by a Player in creative mode. + * + * @return true if this action requires Creative mode + */ + public boolean isCreativeAction() { + // Why use middle click? + return (this == ClickType.MIDDLE) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType represents a right click. + * + * @return true if this ClickType represents a right click + */ + public boolean isRightClick() { + return (this == ClickType.RIGHT) || (this == ClickType.SHIFT_RIGHT); + } + + /** + * Gets whether this ClickType represents a left click. + * + * @return true if this ClickType represents a left click + */ + public boolean isLeftClick() { + return (this == ClickType.LEFT) || (this == ClickType.SHIFT_LEFT) || (this == ClickType.DOUBLE_CLICK) || (this == ClickType.CREATIVE); + } + + /** + * Gets whether this ClickType indicates that the shift key was pressed + * down when the click was made. + * + * @return true if the action uses Shift. + */ + public boolean isShiftClick() { + return (this == ClickType.SHIFT_LEFT) || (this == ClickType.SHIFT_RIGHT) || (this == ClickType.CONTROL_DROP); + } +} diff --git a/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java b/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java index 264ab0a9..4ebc36bd 100644 --- a/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java +++ b/src/main/java/org/bukkit/event/inventory/CraftItemEvent.java @@ -8,8 +8,18 @@ import org.bukkit.inventory.Recipe; public class CraftItemEvent extends InventoryClickEvent { private Recipe recipe; + @Deprecated public CraftItemEvent(Recipe recipe, InventoryView what, SlotType type, int slot, boolean right, boolean shift) { - super(what, type, slot, right, shift); + this(recipe, what, type, slot, right ? (shift ? ClickType.SHIFT_RIGHT : ClickType.RIGHT) : (shift ? ClickType.SHIFT_LEFT : ClickType.LEFT), InventoryAction.PICKUP_ALL); + } + + public CraftItemEvent(Recipe recipe, InventoryView what, SlotType type, int slot, ClickType click, InventoryAction action) { + super(what, type, slot, click, action); + this.recipe = recipe; + } + + public CraftItemEvent(Recipe recipe, InventoryView what, SlotType type, int slot, ClickType click, InventoryAction action, int key) { + super(what, type, slot, click, action, key); this.recipe = recipe; } diff --git a/src/main/java/org/bukkit/event/inventory/DragType.java b/src/main/java/org/bukkit/event/inventory/DragType.java new file mode 100644 index 00000000..9000faf1 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/DragType.java @@ -0,0 +1,18 @@ +package org.bukkit.event.inventory; + +/** + * Represents the effect of a drag that will be applied to an Inventory in an + * InventoryDragEvent. + */ +public enum DragType { + /** + * One item from the cursor is placed in each selected slot. + */ + SINGLE, + /** + * The cursor is split evenly across all selected slots, not to + * exceed the Material's max stack size, with the remainder going to + * the cursor. + */ + EVEN, +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryAction.java b/src/main/java/org/bukkit/event/inventory/InventoryAction.java new file mode 100644 index 00000000..c9da0222 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryAction.java @@ -0,0 +1,90 @@ +package org.bukkit.event.inventory; + +/** + * An estimation of what the result will be. + */ +public enum InventoryAction { + /** + * Nothing will happen from the click. + * There may be cases where nothing will happen and this is value is + * not provided, but it is guaranteed that this value is accurate + * when given. + */ + NOTHING, + /** + * All of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ALL, + /** + * Some of the items on the clicked slot are moved to the cursor. + */ + PICKUP_SOME, + /** + * Half of the items on the clicked slot are moved to the cursor. + */ + PICKUP_HALF, + /** + * One of the items on the clicked slot are moved to the cursor. + */ + PICKUP_ONE, + /** + * All of the items on the cursor are moved to the clicked slot. + */ + PLACE_ALL, + /** + * Some of the items from the cursor are moved to the clicked slot + * (usually up to the max stack size). + */ + PLACE_SOME, + /** + * A single item from the cursor is moved to the clicked slot. + */ + PLACE_ONE, + /** + * The clicked item and the cursor are exchanged. + */ + SWAP_WITH_CURSOR, + /** + * The entire cursor item is dropped. + */ + DROP_ALL_CURSOR, + /** + * One item is dropped from the cursor. + */ + DROP_ONE_CURSOR, + /** + * The entire clicked slot is dropped. + */ + DROP_ALL_SLOT, + /** + * One item is dropped from the clicked slot. + */ + DROP_ONE_SLOT, + /** + * The item is moved to the opposite inventory if a space is found. + */ + MOVE_TO_OTHER_INVENTORY, + /** + * The clicked item is moved to the hotbar, and the item currently + * there is re-added to the player's inventory. + */ + HOTBAR_MOVE_AND_READD, + /** + * The clicked slot and the picked hotbar slot are swapped. + */ + HOTBAR_SWAP, + /** + * A max-size stack of the clicked item is put on the cursor. + */ + CLONE_STACK, + /** + * The inventory is searched for the same material, and they are put + * on the cursor up to {@link org.bukkit.Material#getMaxStackSize()}. + */ + COLLECT_TO_CURSOR, + /** + * An unrecognized ClickType. + */ + UNKNOWN, + ; +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java index 26e1d382..28198b8b 100644 --- a/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java +++ b/src/main/java/org/bukkit/event/inventory/InventoryClickEvent.java @@ -3,121 +3,170 @@ package org.bukkit.event.inventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.bukkit.entity.HumanEntity; -import org.bukkit.event.Cancellable; +import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.Location; import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.plugin.Plugin; -public class InventoryClickEvent extends InventoryEvent implements Cancellable { +/** + * This event is called when a player clicks a slot in an inventory. + *

+ * Because InventoryClickEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

+ * The following should never be invoked by an EventHandler for + * InventoryClickEvent using the HumanEntity or InventoryView associated with + * this event: + *

+ * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

+ * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

+ * Modifications to slots that are modified by the results of this + * InventoryClickEvent can be overwritten. To change these slots, this event + * should be cancelled and all desired changes to the inventory applied. + * Alternatively, scheduling a task using {@link BukkitScheduler#runTask( + * Plugin, Runnable)}, which would execute the task on the next tick, would + * work as well. + */ +public class InventoryClickEvent extends InventoryInteractEvent { private static final HandlerList handlers = new HandlerList(); + private final ClickType click; + private final InventoryAction action; private SlotType slot_type; - private boolean rightClick, shiftClick; - private Result result; private int whichSlot; private int rawSlot; private ItemStack current = null; + private int hotbarKey = -1; - public InventoryClickEvent(InventoryView what, SlotType type, int slot, boolean right, boolean shift) { - super(what); + @Deprecated + public InventoryClickEvent(InventoryView view, SlotType type, int slot, boolean right, boolean shift) { + this(view, type, slot, right ? (shift ? ClickType.SHIFT_RIGHT : ClickType.RIGHT) : (shift ? ClickType.SHIFT_LEFT : ClickType.LEFT), InventoryAction.SWAP_WITH_CURSOR); + } + + public InventoryClickEvent(InventoryView view, SlotType type, int slot, ClickType click, InventoryAction action) { + super(view); this.slot_type = type; - this.rightClick = right; - this.shiftClick = shift; - this.result = Result.DEFAULT; this.rawSlot = slot; - this.whichSlot = what.convertSlot(slot); + this.whichSlot = view.convertSlot(slot); + this.click = click; + this.action = action; + } + + public InventoryClickEvent(InventoryView view, SlotType type, int slot, ClickType click, InventoryAction action, int key) { + this(view, type, slot, click, action); + this.hotbarKey = key; } /** - * Get the type of slot that was clicked. - * @return The slot type. + * Gets the type of slot that was clicked. + * + * @return the slot type */ public SlotType getSlotType() { return slot_type; } /** - * Get the current item on the cursor. - * @return The cursor item + * Gets the current ItemStack on the cursor. + * + * @return the cursor ItemStack */ public ItemStack getCursor() { return getView().getCursor(); } /** - * Get the current item in the clicked slot. - * @return The slot item. + * Gets the ItemStack currently in the clicked slot. + * + * @return the item in the clicked */ public ItemStack getCurrentItem() { - if(slot_type == SlotType.OUTSIDE) return current; + if (slot_type == SlotType.OUTSIDE) { + return current; + } return getView().getItem(rawSlot); } /** - * @return True if the click is a right-click. + * Gets whether or not the ClickType for this event represents a right + * click. + * + * @return true if the ClickType uses the right mouse button. + * @see ClickType#isRightClick() */ public boolean isRightClick() { - return rightClick; + return click.isRightClick(); } /** - * @return True if the click is a left-click. + * Gets whether or not the ClickType for this event represents a left + * click. + * + * @return true if the ClickType uses the left mouse button. + * @see ClickType#isLeftClick() */ public boolean isLeftClick() { - return !rightClick; + return click.isLeftClick(); } /** - * Shift can be combined with right-click or left-click as a modifier. - * @return True if the click is a shift-click. + * Gets whether the ClickType for this event indicates that the key was + * pressed down when the click was made. + * + * @return true if the ClickType uses Shift or Ctrl. + * @see ClickType#isShiftClick() */ public boolean isShiftClick() { - return shiftClick; - } - - public void setResult(Result newResult) { - result = newResult; - } - - public Result getResult() { - return result; + return click.isShiftClick(); } /** - * Get the player who performed the click. - * @return The clicking player. + * Sets the item on the cursor. + * + * @param stack the new cursor item + * @deprecated This changes the ItemStack in their hand before any + * calculations are applied to the Inventory, which has a tendency to + * create inconsistencies between the Player and the server, and to + * make unexpected changes in the behavior of the clicked Inventory. */ - public HumanEntity getWhoClicked() { - return getView().getPlayer(); + @Deprecated + public void setCursor(ItemStack stack) { + getView().setCursor(stack); } /** - * Set the item on the cursor. - * @param what The new cursor item. + * Sets the ItemStack currently in the clicked slot. + * + * @param stack the item to be placed in the current slot */ - public void setCursor(ItemStack what) { - getView().setCursor(what); + public void setCurrentItem(ItemStack stack) { + if (slot_type == SlotType.OUTSIDE) { + current = stack; + } else { + getView().setItem(rawSlot, stack); + } } /** - * Set the current item in the slot. - * @param what The new slot item. - */ - public void setCurrentItem(ItemStack what) { - if(slot_type == SlotType.OUTSIDE) current = what; - else getView().setItem(rawSlot, what); - } - - public boolean isCancelled() { - return result == Result.DENY; - } - - public void setCancelled(boolean toCancel) { - result = toCancel ? Result.DENY : Result.ALLOW; - } - - /** - * The slot number that was clicked, ready for passing to {@link Inventory#getItem(int)}. Note - * that there may be two slots with the same slot number, since a view links two different inventories. + * The slot number that was clicked, ready for passing to + * {@link Inventory#getItem(int)}. Note that there may be two slots with + * the same slot number, since a view links two different inventories. + * * @return The slot number. */ public int getSlot() { @@ -125,13 +174,50 @@ public class InventoryClickEvent extends InventoryEvent implements Cancellable { } /** - * The raw slot number, which is unique for the view. - * @return The slot number. + * The raw slot number clicked, ready for passing to {@link InventoryView + * #getItem(int)} This slot number is unique for the view. + * + * @return the slot number */ public int getRawSlot() { return rawSlot; } + /** + * If the ClickType is NUMBER_KEY, this method will return the index of + * the pressed key (0-8). + * + * @return the number on the key minus 1 (range 0-8); or -1 if not + * a NUMBER_KEY action + */ + public int getHotbarButton() { + return hotbarKey; + } + + /** + * Gets the InventoryAction that triggered this event. + *

+ * This action cannot be changed, and represents what the normal outcome + * of the event will be. To change the behavior of this + * InventoryClickEvent, changes must be manually applied. + * + * @return the InventoryAction that triggered this event. + */ + public InventoryAction getAction() { + return action; + } + + /** + * Gets the ClickType for this event. + *

+ * This is insulated against changes to the inventory by other plugins. + * + * @return the type of inventory click + */ + public ClickType getClick() { + return click; + } + @Override public HandlerList getHandlers() { return handlers; diff --git a/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java new file mode 100644 index 00000000..da7dffc0 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryCreativeEvent.java @@ -0,0 +1,27 @@ +package org.bukkit.event.inventory; + +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * This event is called when a player in creative mode puts down or picks up + * an item in their inventory / hotbar and when they drop items from their + * Inventory while in creative mode. + */ +public class InventoryCreativeEvent extends InventoryClickEvent { + private ItemStack item; + + public InventoryCreativeEvent(InventoryView what, SlotType type, int slot, ItemStack newItem) { + super(what, type, slot, ClickType.CREATIVE, InventoryAction.PLACE_ALL); + this.item = newItem; + } + + public ItemStack getCursor() { + return item; + } + + public void setCursor(ItemStack item) { + this.item = item; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java new file mode 100644 index 00000000..e7e54a75 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryDragEvent.java @@ -0,0 +1,164 @@ +package org.bukkit.event.inventory; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; + +import com.google.common.collect.ImmutableSet; + +/** + * This event is called when the player drags an item in their cursor across + * the inventory. The ItemStack is distributed across the slots the + * HumanEntity dragged over. The method of distribution is described by the + * DragType returned by {@link #getType()}. + *

+ * Canceling this event will result in none of the changes described in + * {@link #getNewItems()} being applied to the Inventory. + *

+ * Because InventoryDragEvent occurs within a modification of the Inventory, + * not all Inventory related methods are safe to use. + *

+ * The following should never be invoked by an EventHandler for + * InventoryDragEvent using the HumanEntity or InventoryView associated with + * this event. + *

+ * To invoke one of these methods, schedule a task using + * {@link BukkitScheduler#runTask(Plugin, Runnable)}, which will run the task + * on the next tick. Also be aware that this is not an exhaustive list, and + * other methods could potentially create issues as well. + *

+ * Assuming the EntityHuman associated with this event is an instance of a + * Player, manipulating the MaxStackSize or contents of an Inventory will + * require an Invocation of {@link Player#updateInventory()}. + *

+ * Any modifications to slots that are modified by the results of this + * InventoryDragEvent will be overwritten. To change these slots, this event + * should be cancelled and the changes applied. Alternatively, scheduling a + * task using {@link BukkitScheduler#runTask(Plugin, Runnable)}, which would + * execute the task on the next tick, would work as well. + */ +public class InventoryDragEvent extends InventoryInteractEvent { + private static final HandlerList handlers = new HandlerList(); + private final DragType type; + private final Map addedItems; + private final Set containerSlots; + private final ItemStack oldCursor; + private ItemStack newCursor; + + public InventoryDragEvent(InventoryView what, ItemStack newCursor, ItemStack oldCursor, boolean right, Map slots) { + super(what); + + Validate.notNull(oldCursor); + Validate.notNull(slots); + + type = right ? DragType.SINGLE : DragType.EVEN; + this.newCursor = newCursor; + this.oldCursor = oldCursor; + this.addedItems = slots; + ImmutableSet.Builder b = ImmutableSet.builder(); + for (Integer slot : slots.keySet()) { + b.add(what.convertSlot(slot)); + } + this.containerSlots = b.build(); + } + + /** + * Gets all items to be added to the inventory in this drag. + * + * @return map from raw slot id to new ItemStack + */ + public Map getNewItems() { + return Collections.unmodifiableMap(addedItems); + } + + /** + * Gets the raw slot ids to be changed in this drag. + * + * @return list of raw slot ids, suitable for getView().getItem(int) + */ + public Set getRawSlots() { + return addedItems.keySet(); + } + + /** + * Gets the slots to be changed in this drag. + * + * @return list of converted slot ids, suitable for {@link + * org.bukkit.inventory.Inventory#getItem(int)}. + */ + public Set getInventorySlots() { + return containerSlots; + } + + /** + * Gets the result cursor after the drag is done. The returned value is + * mutable. + * + * @return the result cursor + */ + public ItemStack getCursor() { + return newCursor; + } + + /** + * Sets the result cursor after the drag is done. + *

+ * Changing this item stack changes the cursor item. Note that changing + * the affected "dragged" slots does not change this ItemStack, nor does + * changing this ItemStack affect the "dragged" slots. + * + * @param newCursor the new cursor ItemStack + */ + public void setCursor(ItemStack newCursor) { + this.newCursor = newCursor; + } + + /** + * Gets an ItemStack representing the cursor prior to any modifications + * as a result of this drag. + * + * @return the original cursor + */ + public ItemStack getOldCursor() { + return oldCursor.clone(); + } + + /** + * Gets the DragType that describes the behavior of ItemStacks placed + * after this InventoryDragEvent. + *

+ * The ItemStacks and the raw slots that they're being applied to can be + * found using {@link #getNewItems()}. + * + * @return the DragType of this InventoryDragEvent + */ + public DragType getType() { + return type; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java new file mode 100644 index 00000000..8624f8d7 --- /dev/null +++ b/src/main/java/org/bukkit/event/inventory/InventoryInteractEvent.java @@ -0,0 +1,78 @@ +package org.bukkit.event.inventory; + +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event.Result; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; + +/** + * An abstract base class for events that describe an interaction between a + * HumanEntity and the contents of an Inventory. + */ +public abstract class InventoryInteractEvent extends InventoryEvent implements Cancellable { + private Result result = Result.DEFAULT; + + public InventoryInteractEvent(InventoryView transaction) { + super(transaction); + } + + /** + * Gets the player who performed the click. + * + * @return The clicking player. + */ + public HumanEntity getWhoClicked() { + return getView().getPlayer(); + } + + /** + * Sets the result of this event. This will change whether or not this + * event is considered cancelled. + * + * @see #isCancelled() + * @param newResult the new {@link Result} for this event + */ + public void setResult(Result newResult) { + result = newResult; + } + + /** + * Gets the {@link Result} of this event. The Result describes the + * behavior that will be applied to the inventory in relation to this + * event. + * + * @return the Result of this event. + */ + public Result getResult() { + return result; + } + + /** + * Gets whether or not this event is cancelled. This is based off of the + * Result value returned by {@link #getResult()}. Result.ALLOW and + * Result.DEFAULT will result in a returned value of false, but + * Result.DENY will result in a returned value of true. + *

+ * {@inheritDoc} + * + * @return whether the event is cancelled + */ + public boolean isCancelled() { + return getResult() == Result.DENY; + } + + /** + * Proxy method to {@link #setResult(Event.Result)} for the Cancellable + * interface. {@link #setResult(Event.Result)} is preferred, as it allows + * you to specify the Result beyond Result.DENY and Result.ALLOW. + *

+ * {@inheritDoc} + * + * @param toCancel result becomes DENY if true, ALLOW if false + */ + public void setCancelled(boolean toCancel) { + setResult(toCancel ? Result.DENY : Result.ALLOW); + } + +}