diff --git a/src/main/java/net/minestom/server/event/GlobalHandles.java b/src/main/java/net/minestom/server/event/GlobalHandles.java index eae94d6c3..1d98ca896 100644 --- a/src/main/java/net/minestom/server/event/GlobalHandles.java +++ b/src/main/java/net/minestom/server/event/GlobalHandles.java @@ -4,6 +4,7 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.event.entity.EntityTickEvent; import net.minestom.server.event.instance.InstanceChunkLoadEvent; import net.minestom.server.event.instance.InstanceTickEvent; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.event.player.PlayerChunkLoadEvent; import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerPacketEvent; @@ -23,4 +24,5 @@ public final class GlobalHandles { public static final ListenerHandle INSTANCE_TICK = EventDispatcher.getHandle(InstanceTickEvent.class); public static final ListenerHandle PLAYER_CHUNK_LOAD = EventDispatcher.getHandle(PlayerChunkLoadEvent.class); public static final ListenerHandle INSTANCE_CHUNK_LOAD = EventDispatcher.getHandle(InstanceChunkLoadEvent.class); + public static final ListenerHandle INVENTORY_ITEM_CHANGE_EVENT = EventDispatcher.getHandle(InventoryItemChangeEvent.class); } diff --git a/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java b/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java new file mode 100644 index 000000000..2111ec1ab --- /dev/null +++ b/src/main/java/net/minestom/server/event/inventory/InventoryItemChangeEvent.java @@ -0,0 +1,74 @@ +package net.minestom.server.event.inventory; + +import net.minestom.server.event.trait.InventoryEvent; +import net.minestom.server.inventory.AbstractInventory; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.PlayerInventory; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Called when {@link AbstractInventory#safeItemInsert(int, ItemStack)} is being invoked. + * This event cannot be cancelled and items related to the change are already moved. + */ +@SuppressWarnings("JavadocReference") +public class InventoryItemChangeEvent implements InventoryEvent { + + private final Inventory inventory; + private final PlayerInventory playerInventory; + private final int slot; + private final ItemStack previousItem; + private final ItemStack newItem; + + public InventoryItemChangeEvent(@Nullable Inventory inventory, @Nullable PlayerInventory playerInventory, + int slot, @NotNull ItemStack previousItem, @NotNull ItemStack newItem) { + this.inventory = inventory; + this.playerInventory = playerInventory; + this.slot = slot; + this.previousItem = previousItem; + this.newItem = newItem; + } + + /** + * Gets the changed slot number. + * + * @return the changed slot number. + */ + public int getSlot() { + return slot; + } + + /** + * Gets a previous item that was on changed slot. + * + * @return a previous item that was on changed slot. + */ + public @NotNull ItemStack getPreviousItem() { + return previousItem; + } + + /** + * Gets a new item on a changed slot. + * + * @return a new item on a changed slot. + */ + public @NotNull ItemStack getNewItem() { + return newItem; + } + + /** + * Gets a player inventory in which an event has occurred. + * If event happened in a regular (i.e. not player) inventory, the result is null. + * + * @return null or a player inventory in which an event has occurred. + */ + public @Nullable PlayerInventory getPlayerInventory() { + return playerInventory; + } + + @Override + public @Nullable Inventory getInventory() { + return inventory; + } +} diff --git a/src/main/java/net/minestom/server/inventory/AbstractInventory.java b/src/main/java/net/minestom/server/inventory/AbstractInventory.java index 9843ad125..7647c6c2f 100644 --- a/src/main/java/net/minestom/server/inventory/AbstractInventory.java +++ b/src/main/java/net/minestom/server/inventory/AbstractInventory.java @@ -1,5 +1,7 @@ package net.minestom.server.inventory; +import net.minestom.server.event.GlobalHandles; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.inventory.click.InventoryClickProcessor; import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; @@ -52,7 +54,31 @@ public abstract class AbstractInventory implements InventoryClickHandler, TagHan safeItemInsert(slot, itemStack); } - protected abstract void safeItemInsert(int slot, @NotNull ItemStack itemStack); + /** + * Inserts safely an item into the inventory. + *

+ * This will update the slot for all viewers and warn the inventory that + * the window items packet is not up-to-date. + * + * @param slot the internal slot id + * @param itemStack the item to insert (use air instead of null) + * + * @throws IllegalArgumentException if the slot {@code slot} does not exist + */ + protected synchronized final void safeItemInsert(int slot, @NotNull ItemStack itemStack) { + Check.argCondition( + !MathUtils.isBetween(slot, 0, getSize()), + "The slot {0} does not exist in this inventory", + slot + ); + ItemStack previous = itemStacks[slot]; + UNSAFE_itemInsert(slot, itemStack); + GlobalHandles.INVENTORY_ITEM_CHANGE_EVENT.call(getItemChangeEvent(slot, previous, itemStack)); + } + + protected abstract void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack); + + protected abstract InventoryItemChangeEvent getItemChangeEvent(int slot, @NotNull ItemStack previous, @NotNull ItemStack current); public synchronized @NotNull T processItemStack(@NotNull ItemStack itemStack, @NotNull TransactionType type, diff --git a/src/main/java/net/minestom/server/inventory/Inventory.java b/src/main/java/net/minestom/server/inventory/Inventory.java index c8a253939..326eacdf3 100644 --- a/src/main/java/net/minestom/server/inventory/Inventory.java +++ b/src/main/java/net/minestom/server/inventory/Inventory.java @@ -3,6 +3,7 @@ package net.minestom.server.inventory; import net.kyori.adventure.text.Component; import net.minestom.server.Viewable; import net.minestom.server.entity.Player; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickResult; import net.minestom.server.item.ItemStack; @@ -209,21 +210,17 @@ public class Inventory extends AbstractInventory implements Viewable { } } - /** - * Inserts safely an item into the inventory. - *

- * This will update the slot for all viewers and warn the inventory that - * the window items packet is not up-to-date. - * - * @param slot the internal slot id - * @param itemStack the item to insert - */ @Override - protected synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { - this.itemStacks[slot] = itemStack; + protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) { + itemStacks[slot] = itemStack; sendPacketToViewers(new SetSlotPacket(getWindowId(), 0, (short) slot, itemStack)); } + @Override + protected InventoryItemChangeEvent getItemChangeEvent(int slot, @NotNull ItemStack previous, @NotNull ItemStack current) { + return new InventoryItemChangeEvent(this, null, slot, previous, current); + } + /** * Creates a complete new {@link WindowItemsPacket}. * diff --git a/src/main/java/net/minestom/server/inventory/PlayerInventory.java b/src/main/java/net/minestom/server/inventory/PlayerInventory.java index def1fdca1..f83fca6f6 100644 --- a/src/main/java/net/minestom/server/inventory/PlayerInventory.java +++ b/src/main/java/net/minestom/server/inventory/PlayerInventory.java @@ -3,6 +3,7 @@ package net.minestom.server.inventory; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.Player; import net.minestom.server.event.EventDispatcher; +import net.minestom.server.event.inventory.InventoryItemChangeEvent; import net.minestom.server.event.item.EntityEquipEvent; import net.minestom.server.inventory.click.ClickType; import net.minestom.server.inventory.click.InventoryClickResult; @@ -10,8 +11,6 @@ import net.minestom.server.inventory.condition.InventoryCondition; import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.server.play.SetSlotPacket; import net.minestom.server.network.packet.server.play.WindowItemsPacket; -import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import static net.minestom.server.utils.inventory.PlayerInventoryUtils.*; @@ -149,20 +148,8 @@ public class PlayerInventory extends AbstractInventory implements EquipmentHandl } } - /** - * Inserts an item safely (synchronized) in the appropriate slot. - * - * @param slot an internal slot - * @param itemStack the item to insert at the slot - * @throws IllegalArgumentException if the slot {@code slot} does not exist - * @throws NullPointerException if {@code itemStack} is null - */ @Override - protected synchronized void safeItemInsert(int slot, @NotNull ItemStack itemStack) { - Check.argCondition(!MathUtils.isBetween(slot, 0, getSize()), - "The slot {0} does not exist for player", slot); - Check.notNull(itemStack, "The ItemStack cannot be null, you can set air instead"); - + protected void UNSAFE_itemInsert(int slot, @NotNull ItemStack itemStack) { EquipmentSlot equipmentSlot = null; if (slot == player.getHeldSlot()) { equipmentSlot = EquipmentSlot.MAIN_HAND; @@ -191,6 +178,11 @@ public class PlayerInventory extends AbstractInventory implements EquipmentHandl sendSlotRefresh((short) convertToPacketSlot(slot), itemStack); } + @Override + protected InventoryItemChangeEvent getItemChangeEvent(int slot, @NotNull ItemStack previous, @NotNull ItemStack current) { + return new InventoryItemChangeEvent(null, this, slot, previous, current); + } + /** * Refreshes an inventory slot. *