Skip to content

Commit 1d1a6ed

Browse files
Fire PlayerDropItemEvent for dropping from inventories
- Fire and handle PlayerDropItemEvent when: - Using Q / CTRL-Q to drop an item from an inventory in a Gui - Using left / right-click to drop a cursor item - Closing a Window with an item on the cursor (Only if there's no space in the player inventory. If the event is fired an cancelled, the item is deleted. This matches existing behavior for e.g. Chests.) Improved handling of ItemPreUpdateEvent for Q / CTRL-Q: Updated new item stack amount will be reflected in the amount of dropped items
1 parent d827c62 commit 1d1a6ed

File tree

4 files changed

+60
-43
lines changed

4 files changed

+60
-43
lines changed

invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,9 @@ private void handleInvNumberKey(Click click, Inventory inventory, int slot) {
286286
if (link == null)
287287
return;
288288

289-
if (link.getHoldingElement() instanceof SlotElement.InventoryLink(var otherInventory, var otherSlot, var unused)) {
289+
if (link.getHoldingElement() instanceof SlotElement.InventoryLink(
290+
var otherInventory, var otherSlot, var unused
291+
)) {
290292
if (inventory == otherInventory && slot == otherSlot)
291293
return;
292294

@@ -319,22 +321,27 @@ private void handleInvOffHandKey(Click click, Inventory inventory, int slot) {
319321
}
320322

321323
private void handleInvDrop(boolean ctrl, Click click, Inventory inventory, int slot) {
322-
Player player = click.player();
323324
ItemStack clicked = inventory.getItem(slot);
324-
325-
if (clicked == null)
325+
if (ItemUtils.isEmpty(clicked))
326326
return;
327327

328+
Player player = click.player();
328329
UpdateReason updateReason = new PlayerUpdateReason.Click(player, click);
329330

330-
if (ctrl) {
331-
if (inventory.setItem(updateReason, slot, null)) {
332-
InventoryUtils.dropItemLikePlayer(player, clicked);
333-
}
334-
} else if (inventory.addItemAmount(updateReason, slot, -1) == -1) {
335-
clicked.setAmount(1);
336-
InventoryUtils.dropItemLikePlayer(player, clicked);
337-
}
331+
int initialDropCount = ctrl ? clicked.getAmount() : 1;
332+
var initialNewItem = ItemUtils.cloneWithCount(clicked, clicked.getAmount() - initialDropCount);
333+
var event = inventory.callPreUpdateEvent(updateReason, slot, clicked, initialNewItem);
334+
if (event.isCancelled())
335+
return;
336+
337+
var newItem = event.getNewItem();
338+
int dropCount = clicked.getAmount() - (newItem != null ? newItem.getAmount() : 0);
339+
var toDrop = ItemUtils.cloneWithCount(clicked, dropCount);
340+
if (!InventoryUtils.dropItemLikePlayer(player, toDrop))
341+
return;
342+
343+
inventory.forceSetItem(UpdateReason.SUPPRESSED, slot, newItem);
344+
inventory.callPostUpdateEvent(updateReason, slot, clicked, newItem);
338345
}
339346

340347
private void handleInvDoubleClick(Click click) {
@@ -385,28 +392,7 @@ private void handleInvBundleSelect(Player player, Inventory inventory, int slot,
385392
inventory.setItem(new PlayerUpdateReason.BundleSelect(player, bundleSlot), slot, bundle);
386393
}
387394
}
388-
389-
/**
390-
* Puts the given {@link ItemStack} into the first inventory that accepts it, starting with the
391-
* {@link Inventory#getGuiPriority(OperationCategory)} for {@link OperationCategory#ADD} highest priority inventory.
392-
* If one inventory accepts any amount of items, further inventories will not be queried, meaning that an item stack will not be split
393-
* across multiple inventories.
394-
*
395-
* @param updateReason the update reason to use
396-
* @param itemStack the item stack to put
397-
* @param ignored the inventories to ignore
398-
* @return the amount of items that are left over
399-
*/
400-
protected int putIntoFirstInventory(UpdateReason updateReason, ItemStack itemStack, Inventory... ignored) {
401-
return putIntoFirstInventory(
402-
updateReason,
403-
itemStack,
404-
getInventories(ignored).stream()
405-
.sorted(Comparator.<Inventory>comparingInt(inv -> inv.getGuiPriority(OperationCategory.ADD)).reversed())
406-
.toList()
407-
);
408-
}
409-
395+
410396
/**
411397
* Puts the given {@link ItemStack} into the first inventory that accepts it of the given collection of inventories.
412398
* If one inventory accepts any amount of items, further inventories will not be queried, meaning that an item stack
@@ -1116,7 +1102,7 @@ public S addIngredient(char key, SlotElementSupplier elementSupplier) {
11161102
if (structure == null)
11171103
throw new IllegalStateException("Structure is not set");
11181104
structure.addIngredient(key, elementSupplier);
1119-
return (S) this;
1105+
return (S) this;
11201106
}
11211107

11221108
@Override

invui/src/main/java/xyz/xenondevs/invui/internal/util/InventoryUtils.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import org.bukkit.Location;
55
import org.bukkit.entity.Item;
66
import org.bukkit.entity.Player;
7+
import org.bukkit.event.player.PlayerDropItemEvent;
78
import org.bukkit.inventory.ItemStack;
9+
import org.jspecify.annotations.Nullable;
810
import xyz.xenondevs.invui.gui.Gui;
911
import xyz.xenondevs.invui.inventory.Inventory;
1012
import xyz.xenondevs.invui.inventory.OperationCategory;
@@ -134,24 +136,37 @@ public static MenuType<?> getMatchingGenericMenuType(int width, int height) {
134136
}
135137

136138
/**
137-
* Spawns an item entity as if the player dropped it.
139+
* Spawns an item entity as if the player dropped it, also firing {@link PlayerDropItemEvent}.
138140
*
139141
* @param player The player
140142
* @param itemStack The item stack
143+
* @return Whether the item was dropped. False if the event was cancelled.
141144
*/
142-
public static void dropItemLikePlayer(Player player, ItemStack itemStack) {
145+
@SuppressWarnings("UnstableApiUsage")
146+
public static boolean dropItemLikePlayer(Player player, @Nullable ItemStack itemStack) {
143147
if (ItemUtils.isEmpty(itemStack))
144-
return;
148+
return true;
145149

146150
Location location = player.getLocation();
147151
location.add(0, 1.5, 0); // not the eye location
148-
Item item = location.getWorld().dropItem(location, itemStack);
152+
153+
Item item = location.getWorld().createEntity(location, Item.class);
154+
item.setItemStack(itemStack.clone());
149155
item.setPickupDelay(40);
150156
item.setVelocity(location.getDirection().multiply(0.35));
157+
158+
if (new PlayerDropItemEvent(player, item).callEvent()) {
159+
location.getWorld().addEntity(item);
160+
return true;
161+
}
162+
163+
return false;
151164
}
152165

153166
/**
154167
* Adds an item stack to the player's inventory or drops it if it doesn't fit.
168+
* Also fires {@link PlayerDropItemEvent}, effectively deleting the item if the event is cancelled.
169+
*
155170
* @param player The player
156171
* @param itemStack The item stack
157172
*/

invui/src/main/java/xyz/xenondevs/invui/util/ItemUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ public static boolean isEmpty(@Nullable ItemStack itemStack) {
7979
return itemStack.clone();
8080
}
8181

82+
/**
83+
* Clones the given {@link ItemStack} and sets its amount to the given count.
84+
*
85+
* @param itemStack the item stack to clone
86+
* @param count the amount for the cloned item stack
87+
* @return the cloned item stack with the given amount, or null if the item stack is empty
88+
*/
89+
public static @Nullable ItemStack cloneWithCount(@Nullable ItemStack itemStack, int count) {
90+
if (isEmpty(itemStack))
91+
return null;
92+
93+
ItemStack clone = itemStack.clone();
94+
clone.setAmount(count);
95+
return clone;
96+
}
97+
8298
/**
8399
* Returns a copy of the non-empty placeholder item, which is an invisible non-air item stack.
84100
*

invui/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ public void handleClick(int slot, Click click) {
223223
if (!event.isCancelled() && !ItemUtils.isEmpty(cursor)) {
224224
switch (click.clickType()) {
225225
case LEFT -> {
226-
InventoryUtils.dropItemLikePlayer(viewer, cursor);
227-
viewer.setItemOnCursor(null);
226+
if (InventoryUtils.dropItemLikePlayer(viewer, cursor))
227+
viewer.setItemOnCursor(null);
228228
}
229229

230230
case RIGHT -> {
231231
var drop = cursor.clone();
232232
drop.setAmount(1);
233-
InventoryUtils.dropItemLikePlayer(viewer, drop);
234-
cursor.setAmount(cursor.getAmount() - 1);
233+
if (InventoryUtils.dropItemLikePlayer(viewer, drop))
234+
cursor.setAmount(cursor.getAmount() - 1);
235235
}
236236

237237
default -> {}

0 commit comments

Comments
 (0)