diff --git a/InvUI/src/main/java/de/studiocode/invui/gui/impl/BaseGUI.java b/InvUI/src/main/java/de/studiocode/invui/gui/impl/BaseGUI.java index 847f77c..a2bfe81 100644 --- a/InvUI/src/main/java/de/studiocode/invui/gui/impl/BaseGUI.java +++ b/InvUI/src/main/java/de/studiocode/invui/gui/impl/BaseGUI.java @@ -207,9 +207,11 @@ public abstract class BaseGUI implements GUI, Controllable { protected void handleVIItemShift(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) { if (clicked == null) return; + ItemStack previousStack = clicked.clone(); + UpdateReason updateReason = new PlayerUpdateReason(player, event); Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); - ItemUpdateEvent updateEvent = inventory.callPreUpdateEvent(updateReason, slot, clicked, null); + ItemUpdateEvent updateEvent = inventory.callPreUpdateEvent(updateReason, slot, previousStack, null); if (!updateEvent.isCancelled()) { int leftOverAmount; @@ -232,6 +234,8 @@ public abstract class BaseGUI implements GUI, Controllable { clicked.setAmount(leftOverAmount); inventory.setItemStackSilently(slot, clicked); + + inventory.callAfterUpdateEvent(updateReason, slot, previousStack, clicked); } } diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java index 9db02ee..0e730ee 100644 --- a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/VirtualInventory.java @@ -3,6 +3,7 @@ package de.studiocode.invui.virtualinventory; import de.studiocode.invui.InvUI; import de.studiocode.invui.gui.GUI; import de.studiocode.invui.util.ArrayUtils; +import de.studiocode.invui.virtualinventory.event.InventoryUpdatedEvent; import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent; import de.studiocode.invui.virtualinventory.event.UpdateReason; import de.studiocode.invui.window.Window; @@ -31,6 +32,7 @@ public class VirtualInventory implements ConfigurationSerializable { private ItemStack[] items; private int[] stackSizes; private Consumer itemUpdateHandler; + private Consumer inventoryUpdatedHandler; private int guiShiftPriority = 0; /** @@ -166,6 +168,15 @@ public class VirtualInventory implements ConfigurationSerializable { this.itemUpdateHandler = itemUpdateHandler; } + /** + * Sets a handler which is called every time after something has been updated in the {@link VirtualInventory}. + * + * @param inventoryUpdatedHandler The new handler + */ + public void setInventoryUpdatedHandler(Consumer inventoryUpdatedHandler) { + this.inventoryUpdatedHandler = inventoryUpdatedHandler; + } + /** * Gets the priority for shift-clicking {@link ItemStack ItemStacks} into a {@link GUI} * @@ -225,6 +236,16 @@ public class VirtualInventory implements ConfigurationSerializable { return Arrays.stream(items).map(item -> item != null ? item.clone() : null).toArray(ItemStack[]::new); } + /** + * Gets the same {@link ItemStack ItemStack[]} that backs this {@link VirtualInventory}. + *
+ * As it is not clone, it should be handled carefully as changes done on that array will not call any + * Window updates (and create inconsistency between server and client). + */ + public ItemStack[] getUnsafeItems() { + return items; + } + /** * Gets a clone of the {@link ItemStack} on that slot. * @@ -302,7 +323,7 @@ public class VirtualInventory implements ConfigurationSerializable { /** * Gets the maximum stack size for a specific slot while ignoring the {@link ItemStack} on it - * and it's own maximum stack size. + * and its own maximum stack size. * * @param slot The slot * @return The maximum stack size on that slo @@ -339,12 +360,27 @@ public class VirtualInventory implements ConfigurationSerializable { * @param newItemStack The {@link ItemStack} that will be on that slot * @return The {@link ItemUpdateEvent} after it has been handled by the {@link #itemUpdateHandler} */ - public ItemUpdateEvent callUpdateEvent(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { + public ItemUpdateEvent callPreUpdateEvent(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { ItemUpdateEvent event = new ItemUpdateEvent(this, slot, updateReason, previousItemStack, newItemStack); if (itemUpdateHandler != null) itemUpdateHandler.accept(event); return event; } + /** + * Creates an {@link InventoryUpdatedEvent} and calls the {@link #inventoryUpdatedHandler} to handle it. + * + * @param updateReason The {@link UpdateReason} + * @param slot The slot of the affected {@link ItemStack} + * @param previousItemStack The {@link ItemStack} that was on that slot previously. + * @param newItemStack The {@link ItemStack} that is on that slot now. + * @return The {@link InventoryUpdatedEvent} after it has been handled by the {@link #inventoryUpdatedHandler} + */ + public InventoryUpdatedEvent callAfterUpdateEvent(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { + InventoryUpdatedEvent event = new InventoryUpdatedEvent(this, slot, updateReason, previousItemStack, newItemStack); + if (inventoryUpdatedHandler != null) inventoryUpdatedHandler.accept(event); + return event; + } + /** * Checks if the {@link ItemStack} on that slot is the same * as the assumed {@link ItemStack} provided as parameter. @@ -389,9 +425,12 @@ public class VirtualInventory implements ConfigurationSerializable { * @return If the action was successful */ public boolean forceSetItemStack(@Nullable UpdateReason updateReason, int slot, @Nullable ItemStack itemStack) { - ItemUpdateEvent event = callUpdateEvent(updateReason, slot, items[slot], itemStack); + ItemStack previousStack = items[slot]; + ItemUpdateEvent event = callPreUpdateEvent(updateReason, slot, previousStack, itemStack); if (!event.isCancelled()) { - setItemStackSilently(slot, event.getNewItemStack()); + ItemStack newStack = event.getNewItemStack(); + setItemStackSilently(slot, newStack); + callAfterUpdateEvent(updateReason, slot, previousStack, newStack); return true; } return false; @@ -432,12 +471,14 @@ public class VirtualInventory implements ConfigurationSerializable { ItemStack newItemStack = itemStack.clone(); newItemStack.setAmount(min(currentAmount + itemStack.getAmount(), maxStackSize)); - ItemUpdateEvent event = callUpdateEvent(updateReason, slot, currentStack, newItemStack); + ItemUpdateEvent event = callPreUpdateEvent(updateReason, slot, currentStack, newItemStack); if (!event.isCancelled()) { newItemStack = event.getNewItemStack(); items[slot] = newItemStack; notifyWindows(); + callAfterUpdateEvent(updateReason, slot, currentStack, newItemStack); + int newAmount = newItemStack != null ? newItemStack.getAmount() : 0; return itemStack.getAmount() - (newAmount - currentAmount); } @@ -470,12 +511,14 @@ public class VirtualInventory implements ConfigurationSerializable { newItemStack = null; } - ItemUpdateEvent event = callUpdateEvent(updateReason, slot, currentStack, newItemStack); + ItemUpdateEvent event = callPreUpdateEvent(updateReason, slot, currentStack, newItemStack); if (!event.isCancelled()) { newItemStack = event.getNewItemStack(); items[slot] = newItemStack; notifyWindows(); + callAfterUpdateEvent(updateReason, slot, currentStack, newItemStack); + return newItemStack != null ? newItemStack.getAmount() : 0; } @@ -718,10 +761,13 @@ public class VirtualInventory implements ConfigurationSerializable { newStack.setAmount(amount - take); } else newStack = null; - ItemUpdateEvent event = callUpdateEvent(updateReason, index, itemStack, newStack); + ItemUpdateEvent event = callPreUpdateEvent(updateReason, index, itemStack, newStack); if (!event.isCancelled()) { + newStack = event.getNewItemStack(); items[index] = newStack; notifyWindows(); + + callAfterUpdateEvent(updateReason, index, itemStack, newStack); return take; } diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/InventoryUpdatedEvent.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/InventoryUpdatedEvent.java new file mode 100644 index 0000000..d0bd8eb --- /dev/null +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/InventoryUpdatedEvent.java @@ -0,0 +1,34 @@ +package de.studiocode.invui.virtualinventory.event; + +import de.studiocode.invui.virtualinventory.VirtualInventory; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// TODO: better solution? + +/** + * An event that is called after the {@link VirtualInventory} has been updated. + */ +public class InventoryUpdatedEvent extends ItemUpdateEvent { + + /** + * Creates a new {@link ItemUpdateEvent}. + * + * @param virtualInventory The {@link VirtualInventory} where this action takes place. + * @param updateReason The {@link UpdateReason} for the calling of this event. + * This will probably be a {@link PlayerUpdateReason} in most cases but can be a custom one + * if you called the methods in the {@link VirtualInventory} yourself. + * if it wasn't a {@link Player} + * @param slot The slot that is affected + * @param previousItemStack The {@link ItemStack} that was on that slot previously + * @param newItemStack The {@link ItemStack} that is on that slot now + */ + public InventoryUpdatedEvent(@NotNull VirtualInventory virtualInventory, int slot, @Nullable UpdateReason updateReason, + @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { + + super(virtualInventory, slot, updateReason, previousItemStack, newItemStack); + } + +} diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/ItemUpdateEvent.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/ItemUpdateEvent.java index 16f3c21..46c5cd7 100644 --- a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/ItemUpdateEvent.java +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/ItemUpdateEvent.java @@ -9,14 +9,7 @@ import org.jetbrains.annotations.Nullable; /** * An event that is called whenever a slot inside a {@link VirtualInventory} gets updated. */ -public class ItemUpdateEvent { - - private final VirtualInventory virtualInventory; - private final ItemStack previousItemStack; - private final UpdateReason updateReason; - private final int slot; - private ItemStack newItemStack; - private boolean cancelled; +public class ItemUpdateEvent extends UpdateEvent { /** * Creates a new {@link ItemUpdateEvent}. @@ -32,68 +25,11 @@ public class ItemUpdateEvent { */ public ItemUpdateEvent(@NotNull VirtualInventory virtualInventory, int slot, @Nullable UpdateReason updateReason, @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { - this.virtualInventory = virtualInventory; - this.slot = slot; - this.updateReason = updateReason; - this.previousItemStack = previousItemStack; - this.newItemStack = newItemStack; + + super(virtualInventory, slot, updateReason, previousItemStack, newItemStack); } - /** - * Gets the {@link VirtualInventory} where this action takes place. - * - * @return The {@link VirtualInventory} - */ - public VirtualInventory getVirtualInventory() { - return virtualInventory; - } - - /** - * Gets the {@link UpdateReason} for the calling of this event. - * - * @return The reason why this event was called. Probably a {@link PlayerUpdateReason} in most cases. - */ - public UpdateReason getUpdateReason() { - return updateReason; - } - - /** - * Gets the {@link ItemStack} that was there previously. - * - * @return The {@link ItemStack} - */ - public ItemStack getPreviousItemStack() { - return previousItemStack; - } - - /** - * The new {@link ItemStack} that will be there if the event isn't cancelled. - * - * @return The new {@link ItemStack} - */ - public ItemStack getNewItemStack() { - return newItemStack; - } - - /** - * Change the {@link ItemStack} that will appear in the {@link VirtualInventory} - * to a different one. - * - * @param newItemStack The {@link ItemStack} to appear in the {@link VirtualInventory} - * if the {@link ItemUpdateEvent} is not cancelled. - */ - public void setNewItemStack(@Nullable ItemStack newItemStack) { - this.newItemStack = newItemStack; - } - - /** - * Gets the slot that is affected. - * - * @return The slot - */ - public int getSlot() { - return slot; - } + private boolean cancelled; /** * Gets the cancellation state of this event. @@ -113,64 +49,4 @@ public class ItemUpdateEvent { this.cancelled = cancel; } - /** - * Gets if the action resulted in items being added to - * the {@link VirtualInventory}. - * - * @return If items were added to the {@link VirtualInventory} - */ - public boolean isAdd() { - if (newItemStack != null && previousItemStack != null && newItemStack.isSimilar(previousItemStack)) { - return newItemStack.getAmount() > previousItemStack.getAmount(); - } else return previousItemStack == null && newItemStack != null; - } - - /** - * Gets if the action resulted in items being removed - * from the {@link VirtualInventory}. - * - * @return If items were removed from the {@link VirtualInventory} - */ - public boolean isRemove() { - if (newItemStack != null && previousItemStack != null && newItemStack.isSimilar(previousItemStack)) { - return newItemStack.getAmount() < previousItemStack.getAmount(); - } else return newItemStack == null && previousItemStack != null; - } - - /** - * Gets if the type of the {@link ItemStack} has changed. - * This does not account for an {@link ItemStack} turning null - * or being null previously, {@link #isRemove()} and {@link #isAdd()} - * should be used for that. - * - * @return If the type of the {@link ItemStack} has changed - */ - public boolean isSwap() { - return newItemStack != null && previousItemStack != null && !newItemStack.isSimilar(previousItemStack); - } - - /** - * Gets the amount of items that have been removed. - * - * @return The amount of items that have been removed - * @throws IllegalStateException when {@link #isRemove()} is false - */ - public int getRemovedAmount() { - if (!isRemove()) throw new IllegalStateException("No items have been removed"); - if (newItemStack == null) return previousItemStack.getAmount(); - else return previousItemStack.getAmount() - newItemStack.getAmount(); - } - - /** - * Gets the amount of items that have been added. - * - * @return The amount of items that have been added - * @throws IllegalStateException when {@link #isAdd()} is false - */ - public int getAddedAmount() { - if (!isAdd()) throw new IllegalStateException("No items have been added"); - if (previousItemStack == null) return newItemStack.getAmount(); - else return newItemStack.getAmount() - previousItemStack.getAmount(); - } - } diff --git a/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/UpdateEvent.java b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/UpdateEvent.java new file mode 100644 index 0000000..ef1d062 --- /dev/null +++ b/InvUI/src/main/java/de/studiocode/invui/virtualinventory/event/UpdateEvent.java @@ -0,0 +1,155 @@ +package de.studiocode.invui.virtualinventory.event; + +import de.studiocode.invui.virtualinventory.VirtualInventory; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +abstract class UpdateEvent { + + private final VirtualInventory virtualInventory; + private final ItemStack previousItemStack; + private final UpdateReason updateReason; + private final int slot; + private ItemStack newItemStack; + + /** + * Creates a new {@link ItemUpdateEvent}. + * + * @param virtualInventory The {@link VirtualInventory} where this action takes place. + * @param updateReason The {@link UpdateReason} for the calling of this event. + * This will probably be a {@link PlayerUpdateReason} in most cases but can be a custom one + * if you called the methods in the {@link VirtualInventory} yourself. + * if it wasn't a {@link Player} + * @param slot The slot that is affected + * @param previousItemStack The {@link ItemStack} that was there previously + * @param newItemStack The {@link ItemStack} that will be there if the event isn't cancelled + */ + public UpdateEvent(@NotNull VirtualInventory virtualInventory, int slot, @Nullable UpdateReason updateReason, + @Nullable ItemStack previousItemStack, @Nullable ItemStack newItemStack) { + + this.virtualInventory = virtualInventory; + this.slot = slot; + this.updateReason = updateReason; + this.previousItemStack = previousItemStack; + this.newItemStack = newItemStack; + } + + /** + * Gets the {@link VirtualInventory} where this action takes place. + * + * @return The {@link VirtualInventory} + */ + public VirtualInventory getVirtualInventory() { + return virtualInventory; + } + + /** + * Gets the {@link UpdateReason} for the calling of this event. + * + * @return The reason why this event was called. Probably a {@link PlayerUpdateReason} in most cases. + */ + public UpdateReason getUpdateReason() { + return updateReason; + } + + /** + * Gets the {@link ItemStack} that was there previously. + * + * @return The {@link ItemStack} + */ + public ItemStack getPreviousItemStack() { + return previousItemStack; + } + + /** + * The new {@link ItemStack} that will be there if the event isn't cancelled. + * + * @return The new {@link ItemStack} + */ + public ItemStack getNewItemStack() { + return newItemStack; + } + + /** + * Change the {@link ItemStack} that will appear in the {@link VirtualInventory} + * to a different one. + * + * @param newItemStack The {@link ItemStack} to appear in the {@link VirtualInventory} + * if the {@link ItemUpdateEvent} is not cancelled. + */ + public void setNewItemStack(@Nullable ItemStack newItemStack) { + this.newItemStack = newItemStack; + } + + /** + * Gets the slot that is affected. + * + * @return The slot + */ + public int getSlot() { + return slot; + } + + /** + * Gets if the action resulted in items being added to + * the {@link VirtualInventory}. + * + * @return If items were added to the {@link VirtualInventory} + */ + public boolean isAdd() { + if (newItemStack != null && previousItemStack != null && newItemStack.isSimilar(previousItemStack)) { + return newItemStack.getAmount() > previousItemStack.getAmount(); + } else return previousItemStack == null && newItemStack != null; + } + + /** + * Gets if the action resulted in items being removed + * from the {@link VirtualInventory}. + * + * @return If items were removed from the {@link VirtualInventory} + */ + public boolean isRemove() { + if (newItemStack != null && previousItemStack != null && newItemStack.isSimilar(previousItemStack)) { + return newItemStack.getAmount() < previousItemStack.getAmount(); + } else return newItemStack == null && previousItemStack != null; + } + + /** + * Gets if the type of the {@link ItemStack} has changed. + * This does not account for an {@link ItemStack} turning null + * or being null previously, {@link #isRemove()} and {@link #isAdd()} + * should be used for that. + * + * @return If the type of the {@link ItemStack} has changed + */ + public boolean isSwap() { + return newItemStack != null && previousItemStack != null && !newItemStack.isSimilar(previousItemStack); + } + + /** + * Gets the amount of items that have been removed. + * + * @return The amount of items that have been removed + * @throws IllegalStateException when {@link #isRemove()} is false + */ + public int getRemovedAmount() { + if (!isRemove()) throw new IllegalStateException("No items have been removed"); + if (newItemStack == null) return previousItemStack.getAmount(); + else return previousItemStack.getAmount() - newItemStack.getAmount(); + } + + /** + * Gets the amount of items that have been added. + * + * @return The amount of items that have been added + * @throws IllegalStateException when {@link #isAdd()} is false + */ + public int getAddedAmount() { + if (!isAdd()) throw new IllegalStateException("No items have been added"); + if (previousItemStack == null) return newItemStack.getAmount(); + else return newItemStack.getAmount() - previousItemStack.getAmount(); + } + +}