From 623bb94c5dc7785728861d16976926bcebce3cb3 Mon Sep 17 00:00:00 2001 From: NichtStudioCode <51272202+NichtStudioCode@users.noreply.github.com> Date: Wed, 14 Jul 2021 18:14:57 +0200 Subject: [PATCH] VirtualInventory Background --- .../main/java/de/studiocode/invui/InvUI.java | 2 +- .../java/de/studiocode/invui/gui/GUI.java | 12 + .../de/studiocode/invui/gui/SlotElement.java | 23 +- .../de/studiocode/invui/gui/impl/BaseGUI.java | 296 +++++++++++------- .../studiocode/invui/util/InventoryUtils.java | 11 + .../invui/window/impl/BaseWindow.java | 2 +- 6 files changed, 231 insertions(+), 115 deletions(-) diff --git a/InvUI/src/main/java/de/studiocode/invui/InvUI.java b/InvUI/src/main/java/de/studiocode/invui/InvUI.java index c01496a..153bb6e 100644 --- a/InvUI/src/main/java/de/studiocode/invui/InvUI.java +++ b/InvUI/src/main/java/de/studiocode/invui/InvUI.java @@ -19,7 +19,7 @@ public class InvUI implements Listener { private final List disableHandlers = new ArrayList<>(); private final Plugin plugin; - public InvUI() { + private InvUI() { plugin = ReflectionUtils.getFieldValue(PLUGIN_CLASS_LOADER_PLUGIN_FIELD, getClass().getClassLoader()); Bukkit.getPluginManager().registerEvents(this, plugin); } diff --git a/InvUI/src/main/java/de/studiocode/invui/gui/GUI.java b/InvUI/src/main/java/de/studiocode/invui/gui/GUI.java index 4cadc57..ebb6bc1 100644 --- a/InvUI/src/main/java/de/studiocode/invui/gui/GUI.java +++ b/InvUI/src/main/java/de/studiocode/invui/gui/GUI.java @@ -369,4 +369,16 @@ public interface GUI extends GUIParent { */ void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting); + /** + * Fills a rectangle of a {@link VirtualInventory} in this {@link GUI}. + * + * @param x The x coordinate where the rectangle should start + * @param y The y coordinate where the rectangle should start + * @param width The line length of the rectangle. (VirtualInventory does not define a width) + * @param virtualInventory The {@link VirtualInventory} to be put into this {@link GUI}. + * @param background The {@link ItemBuilder} for empty slots of the {@link VirtualInventory} + * @param replaceExisting If existing {@link SlotElement}s should be replaced. + */ + void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, @Nullable ItemBuilder background, boolean replaceExisting); + } diff --git a/InvUI/src/main/java/de/studiocode/invui/gui/SlotElement.java b/InvUI/src/main/java/de/studiocode/invui/gui/SlotElement.java index a1cbbb1..369aa18 100644 --- a/InvUI/src/main/java/de/studiocode/invui/gui/SlotElement.java +++ b/InvUI/src/main/java/de/studiocode/invui/gui/SlotElement.java @@ -1,6 +1,7 @@ package de.studiocode.invui.gui; import de.studiocode.invui.item.Item; +import de.studiocode.invui.item.ItemBuilder; import de.studiocode.invui.virtualinventory.VirtualInventory; import org.bukkit.inventory.ItemStack; @@ -13,7 +14,7 @@ public interface SlotElement { SlotElement getHoldingElement(); /** - * Contains an Item + * Contains an {@link Item} */ class ItemSlotElement implements SlotElement { @@ -40,16 +41,24 @@ public interface SlotElement { } /** - * Links to a slot in a virtual inventory + * Links to a slot in a {@link VirtualInventory} */ class VISlotElement implements SlotElement { private final VirtualInventory virtualInventory; private final int slot; + private final ItemBuilder background; public VISlotElement(VirtualInventory virtualInventory, int slot) { this.virtualInventory = virtualInventory; this.slot = slot; + this.background = null; + } + + public VISlotElement(VirtualInventory virtualInventory, int slot, ItemBuilder background) { + this.virtualInventory = virtualInventory; + this.slot = slot; + this.background = background; } public VirtualInventory getVirtualInventory() { @@ -60,13 +69,15 @@ public interface SlotElement { return slot; } - public ItemStack getItemStack() { - return virtualInventory.getUnsafeItemStack(slot); + public ItemBuilder getBackground() { + return background; } @Override public ItemStack getItemStack(UUID viewerUUID) { - return getItemStack(); + ItemStack itemStack = virtualInventory.getUnsafeItemStack(slot); + if (itemStack == null && background != null) itemStack = background.buildFor(viewerUUID); + return itemStack; } @Override @@ -77,7 +88,7 @@ public interface SlotElement { } /** - * Links to a slot in another GUI + * Links to a slot in another {@link GUI} */ class LinkedSlotElement implements SlotElement { 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 ecc5dfe..33f5609 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 @@ -11,6 +11,7 @@ import de.studiocode.invui.gui.structure.Structure; import de.studiocode.invui.item.Item; import de.studiocode.invui.item.ItemBuilder; import de.studiocode.invui.util.ArrayUtils; +import de.studiocode.invui.util.InventoryUtils; import de.studiocode.invui.util.SlotUtils; import de.studiocode.invui.virtualinventory.VirtualInventory; import de.studiocode.invui.virtualinventory.event.ItemUpdateEvent; @@ -23,8 +24,11 @@ import de.studiocode.invui.window.impl.merged.split.SplitWindow; import de.studiocode.invui.window.impl.single.SingleWindow; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -73,112 +77,143 @@ public abstract class BaseGUI implements GUI { } else event.setCancelled(true); // Only VISlotElements have allowed interactions } - @SuppressWarnings("deprecation") private void handleVISlotElementClick(VISlotElement element, InventoryClickEvent event) { - VirtualInventory virtualInventory = element.getVirtualInventory(); - int slot = element.getSlot(); - - Player player = (Player) event.getWhoClicked(); - ItemStack cursor = event.getCursor(); - ItemStack clicked = event.getCurrentItem(); + // these actions are ignored as they don't modify the inventory + InventoryAction action = event.getAction(); + if (action != InventoryAction.CLONE_STACK + && action != InventoryAction.DROP_ALL_CURSOR + && action != InventoryAction.DROP_ONE_CURSOR + ) { + event.setCancelled(true); + + VirtualInventory inventory = element.getVirtualInventory(); + int slot = element.getSlot(); + + Player player = (Player) event.getWhoClicked(); + + ItemStack cursor = event.getCursor(); + if (cursor != null && cursor.getType().isAir()) cursor = null; + + ItemStack clicked = event.getCurrentItem(); + if (clicked != null && clicked.getType().isAir()) clicked = null; + + ItemStack technicallyClicked = inventory.getItemStack(slot); + if (inventory.isSynced(slot, clicked) || didClickBackgroundItem(player, element, inventory, slot, clicked)) { + + switch (event.getClick()) { + case LEFT: + handleVILeftClick(event, inventory, slot, player, technicallyClicked, cursor); + break; + case RIGHT: + handleVIRightClick(event, inventory, slot, player, technicallyClicked, cursor); + break; + case SHIFT_RIGHT: + case SHIFT_LEFT: + handleVIItemShift(event, inventory, slot, player, technicallyClicked); + break; + case NUMBER_KEY: + handleVINumberKey(event, inventory, slot, player, technicallyClicked); + break; + case SWAP_OFFHAND: + handleVIOffHandKey(event, inventory, slot, player, technicallyClicked); + break; + case DROP: + handleVIDrop(false, event, inventory, slot, player, technicallyClicked); + break; + case CONTROL_DROP: + handleVIDrop(true, event, inventory, slot, player, technicallyClicked); + break; + case DOUBLE_CLICK: + handleVIDoubleClick(event, inventory, player, cursor); + break; + } + } + } + } + + private boolean didClickBackgroundItem(Player player, VISlotElement element, VirtualInventory inventory, int slot, ItemStack clicked) { + UUID uuid = player.getUniqueId(); + return inventory.getUnsafeItemStack(slot) == null + && (isBuilderSimilar(background, uuid, clicked) || isBuilderSimilar(element.getBackground(), uuid, clicked)); + } + + private boolean isBuilderSimilar(ItemBuilder builder, UUID uuid, ItemStack expected) { + return builder != null && builder.buildFor(uuid).isSimilar(expected); + } + + @SuppressWarnings("deprecation") + private void handleVILeftClick(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked, ItemStack cursor) { + // nothing happens if both cursor and clicked stack are empty + if (clicked == null && cursor == null) return; UpdateReason updateReason = new PlayerUpdateReason(player, event); - if (virtualInventory.isSynced(slot, clicked)) { - boolean cancel = false; - - switch (event.getAction()) { - - case CLONE_STACK: - case DROP_ALL_CURSOR: - case DROP_ONE_CURSOR: - // empty, this does not affect anything - break; - - case DROP_ONE_SLOT: - case PICKUP_ONE: - cancel = virtualInventory.addItemAmount(updateReason, slot, -1) != -1; - break; - - case DROP_ALL_SLOT: - case PICKUP_ALL: - cancel = !virtualInventory.setItemStack(updateReason, slot, null); - // set null - break; - - case PICKUP_HALF: - int amount = virtualInventory.getAmount(slot); - int halfAmount = amount / 2; - int newAmount = virtualInventory.setItemAmount(updateReason, slot, halfAmount); - - // amount did not change as predicted - if (newAmount != halfAmount) { - cancel = true; - - // action wasn't completely cancelled - if (newAmount != amount) { - int cursorAmount = amount - newAmount; - cancel = true; - ItemStack newCursorStack = clicked.clone(); - newCursorStack.setAmount(cursorAmount); - event.setCursor(newCursorStack); - } - } - - break; - - case PLACE_SOME: - case PLACE_ALL: - int amountLeft = virtualInventory.putItemStack(updateReason, slot, cursor); - if (amountLeft > 0) { - cancel = true; - if (amountLeft != cursor.getAmount()) - cursor.setAmount(amountLeft); - } - break; - - case PLACE_ONE: - ItemStack itemStack = cursor.clone(); - itemStack.setAmount(1); - cancel = virtualInventory.putItemStack(updateReason, slot, itemStack) != 0; - break; - - case SWAP_WITH_CURSOR: - cancel = !virtualInventory.setItemStack(updateReason, slot, event.getCursor()); - break; - - case COLLECT_TO_CURSOR: - cancel = true; - ItemStack newCursorStack = cursor.clone(); - newCursorStack.setAmount(virtualInventory.collectToCursor(updateReason, newCursorStack)); - event.setCursor(newCursorStack); - break; - - case MOVE_TO_OTHER_INVENTORY: - cancel = true; - handleMoveToOtherInventory(player, event, virtualInventory, slot, updateReason); - break; - - case HOTBAR_MOVE_AND_READD: - case HOTBAR_SWAP: - cancel = handleHotbarSwap(player, event, virtualInventory, slot, updateReason); - break; - - default: - // action not supported - cancel = true; - break; + if (cursor == null) { + // if the cursor is empty, pick the stack up + if (inventory.setItemStack(updateReason, slot, null)) + event.setCursor(clicked); + } else if (clicked == null) { + // if the clicked slot is empty, place the item on the cursor there + if (inventory.setItemStack(updateReason, slot, cursor)) + event.setCursor(null); + } else if (cursor.isSimilar(clicked)) { + // if the items on the cursor are similar to the clicked ones, add them to the stack + int cursorAmount = cursor.getAmount(); + int added = inventory.addItemAmount(updateReason, slot, cursorAmount); + if (added != 0) { + if (added == cursorAmount) { + event.setCursor(null); + } else { + cursor.setAmount(cursorAmount - added); + event.setCursor(cursor); + } } - - event.setCancelled(cancel); - } else event.setCancelled(true); + } else if (!cursor.isSimilar(clicked)) { + // if the stacks are not similar, swap them + if (inventory.setItemStack(updateReason, slot, cursor)) + event.setCursor(clicked); + } } - private void handleMoveToOtherInventory(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) { - Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); + @SuppressWarnings("deprecation") + private void handleVIRightClick(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked, ItemStack cursor) { + // nothing happens if both cursor and clicked stack are empty + if (clicked == null && cursor == null) return; - ItemStack invStack = inventory.getItemStack(slot); - ItemUpdateEvent updateEvent = inventory.callUpdateEvent(reason, slot, invStack, null); + UpdateReason updateReason = new PlayerUpdateReason(player, event); + + if (cursor == null) { + // if the cursor is empty, split the stack to the cursor + // if the stack is not divisible by 2, give the cursor the bigger part + int clickedAmount = clicked.getAmount(); + int newClickedAmount = clickedAmount / 2; + int newCursorAmount = clickedAmount - newClickedAmount; + + cursor = clicked.clone(); + + clicked.setAmount(newClickedAmount); + cursor.setAmount(newCursorAmount); + + if (inventory.setItemStack(updateReason, slot, clicked)) + event.setCursor(cursor); + } else { + // put one item from the cursor in the inventory + ItemStack toAdd = cursor.clone(); + toAdd.setAmount(1); + int remains = inventory.putItemStack(updateReason, slot, toAdd); + if (remains == 0) { + cursor.setAmount(cursor.getAmount() - 1); + event.setCursor(cursor); + } + } + } + + private void handleVIItemShift(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) { + if (clicked == null) return; + + UpdateReason updateReason = new PlayerUpdateReason(player, event); + Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); + ItemUpdateEvent updateEvent = inventory.callUpdateEvent(updateReason, slot, clicked, null); if (!updateEvent.isCancelled()) { int leftOverAmount; @@ -192,29 +227,71 @@ public abstract class BaseGUI implements GUI { otherGui = this; } - leftOverAmount = ((BaseGUI) otherGui).putIntoVirtualInventories(reason, invStack, inventory); + leftOverAmount = ((BaseGUI) otherGui).putIntoVirtualInventories(updateReason, clicked, inventory); } else { leftOverAmount = 0; HashMap leftover = event.getWhoClicked().getInventory().addItem(inventory.getItemStack(slot)); if (!leftover.isEmpty()) leftOverAmount = leftover.get(0).getAmount(); } - invStack.setAmount(leftOverAmount); - inventory.setItemStackSilently(slot, invStack); + clicked.setAmount(leftOverAmount); + inventory.setItemStackSilently(slot, clicked); } } - private boolean handleHotbarSwap(Player player, InventoryClickEvent event, VirtualInventory inventory, int slot, UpdateReason reason) { + // TODO: add support for merged windows + private void handleVINumberKey(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) { Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); if (window instanceof SingleWindow) { + Inventory playerInventory = player.getInventory(); int hotbarButton = event.getHotbarButton(); - ItemStack hotbarItem = player.getInventory().getItem(hotbarButton); + ItemStack hotbarItem = playerInventory.getItem(hotbarButton); if (hotbarItem != null) hotbarItem = hotbarItem.clone(); - return !inventory.setItemStack(reason, slot, hotbarItem); - } // TODO: add support for merged windows + UpdateReason updateReason = new PlayerUpdateReason(player, event); + + if (inventory.setItemStack(updateReason, slot, hotbarItem)) + playerInventory.setItem(hotbarButton, clicked); + } + } + + // TODO: add support for merged windows + private void handleVIOffHandKey(InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) { + Window window = WindowManager.getInstance().findOpenWindow(player).orElse(null); + if (window instanceof SingleWindow) { + PlayerInventory playerInventory = player.getInventory(); + ItemStack offhandItem = playerInventory.getItemInOffHand(); + + UpdateReason updateReason = new PlayerUpdateReason(player, event); + + if (inventory.setItemStack(updateReason, slot, offhandItem)) + playerInventory.setItemInOffHand(clicked); + } + } + + private void handleVIDrop(boolean ctrl, InventoryClickEvent event, VirtualInventory inventory, int slot, Player player, ItemStack clicked) { + if (clicked == null) return; - return true; + UpdateReason updateReason = new PlayerUpdateReason(player, event); + + if (ctrl) { + if (inventory.setItemStack(updateReason, slot, null)) { + InventoryUtils.dropItemLikePlayer(player, clicked); + } + } else if (inventory.addItemAmount(updateReason, slot, -1) == -1) { + clicked.setAmount(1); + InventoryUtils.dropItemLikePlayer(player, clicked); + } + + } + + @SuppressWarnings("deprecation") + private void handleVIDoubleClick(InventoryClickEvent event, VirtualInventory inventory, Player player, ItemStack cursor) { + if (cursor == null) return; + + UpdateReason updateReason = new PlayerUpdateReason(player, event); + cursor.setAmount(inventory.collectToCursor(updateReason, cursor)); + event.setCursor(cursor); } @Override @@ -586,13 +663,18 @@ public abstract class BaseGUI implements GUI { @Override public void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, boolean replaceExisting) { + fillRectangle(x, y, width, virtualInventory, null, replaceExisting); + } + + @Override + public void fillRectangle(int x, int y, int width, @NotNull VirtualInventory virtualInventory, @Nullable ItemBuilder background, boolean replaceExisting) { int height = (int) Math.ceil((double) virtualInventory.getSize() / (double) width); int slotIndex = 0; for (int slot : SlotUtils.getSlotsRect(x, y, width, height, this.width)) { if (slotIndex >= virtualInventory.getSize()) return; if (hasSlotElement(slot) && !replaceExisting) continue; - setSlotElement(slot, new VISlotElement(virtualInventory, slotIndex)); + setSlotElement(slot, new VISlotElement(virtualInventory, slotIndex, background)); slotIndex++; } } diff --git a/InvUI/src/main/java/de/studiocode/invui/util/InventoryUtils.java b/InvUI/src/main/java/de/studiocode/invui/util/InventoryUtils.java index c2f62b3..3db7764 100644 --- a/InvUI/src/main/java/de/studiocode/invui/util/InventoryUtils.java +++ b/InvUI/src/main/java/de/studiocode/invui/util/InventoryUtils.java @@ -2,6 +2,9 @@ package de.studiocode.invui.util; import de.studiocode.invui.gui.GUI; import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -32,4 +35,12 @@ public class InventoryUtils { return false; } + public static void dropItemLikePlayer(Player player, ItemStack itemStack) { + Location location = player.getLocation(); + location.add(0, 1.5, 0); // not the eye location + Item item = location.getWorld().dropItem(location, itemStack); + item.setPickupDelay(40); + item.setVelocity(location.getDirection().multiply(0.35)); + } + } diff --git a/InvUI/src/main/java/de/studiocode/invui/window/impl/BaseWindow.java b/InvUI/src/main/java/de/studiocode/invui/window/impl/BaseWindow.java index c5751d8..0374cac 100644 --- a/InvUI/src/main/java/de/studiocode/invui/window/impl/BaseWindow.java +++ b/InvUI/src/main/java/de/studiocode/invui/window/impl/BaseWindow.java @@ -56,7 +56,7 @@ public abstract class BaseWindow implements Window { protected void redrawItem(int index, SlotElement element, boolean setItem) { // put ItemStack in inventory ItemStack itemStack; - if (element == null) { + if (element == null || (element instanceof VISlotElement && element.getItemStack(viewerUUID) == null)) { ItemBuilder background = getGuiAt(index).getFirst().getBackground(); itemStack = background == null ? null : background.buildFor(viewerUUID); } else itemStack = element.getItemStack(viewerUUID);