From bc1345da59c59888d5ab5269e26a420b4f277fe5 Mon Sep 17 00:00:00 2001 From: NichtStudioCode <51272202+NichtStudioCode@users.noreply.github.com> Date: Sat, 30 Sep 2023 17:13:01 +0200 Subject: [PATCH] Ignore obscured embedded inventory slots by default --- invui-core/pom.xml | 6 ++ .../xyz/xenondevs/invui/gui/AbstractGui.java | 77 ++++++++++--- .../java/xyz/xenondevs/invui/gui/Gui.java | 30 ++++++ .../invui/inventory/ObscuredInventory.java | 101 ++++++++++++++++++ .../invui/window/AbstractDoubleWindow.java | 5 - .../invui/window/AbstractMergedWindow.java | 11 ++ .../invui/window/AbstractSingleWindow.java | 34 ++---- .../invui/window/AbstractSplitWindow.java | 10 ++ .../invui/window/AbstractWindow.java | 30 +++++- 9 files changed, 254 insertions(+), 50 deletions(-) create mode 100644 invui-core/src/main/java/xyz/xenondevs/invui/inventory/ObscuredInventory.java diff --git a/invui-core/pom.xml b/invui-core/pom.xml index c2cbd19..6ca9e15 100644 --- a/invui-core/pom.xml +++ b/invui-core/pom.xml @@ -25,6 +25,12 @@ 1.19.4-R0.1-SNAPSHOT provided + + it.unimi.dsi + fastutil + 8.5.12 + provided + com.mojang authlib diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java b/invui-core/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java index 3c561f0..d6c4d8f 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java @@ -1,5 +1,7 @@ package xyz.xenondevs.invui.gui; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryAction; @@ -12,6 +14,7 @@ import xyz.xenondevs.invui.animation.Animation; import xyz.xenondevs.invui.gui.structure.Marker; import xyz.xenondevs.invui.gui.structure.Structure; import xyz.xenondevs.invui.inventory.Inventory; +import xyz.xenondevs.invui.inventory.ObscuredInventory; import xyz.xenondevs.invui.inventory.ReferencingInventory; import xyz.xenondevs.invui.inventory.event.ItemPreUpdateEvent; import xyz.xenondevs.invui.inventory.event.PlayerUpdateReason; @@ -41,6 +44,7 @@ public abstract class AbstractGui implements Gui, GuiParent { private final Set parents = new HashSet<>(); private boolean frozen; + private boolean ignoreObscuredInventorySlots = true; private ItemProvider background; private Animation animation; private SlotElement[] animationElements; @@ -281,13 +285,13 @@ public abstract class AbstractGui implements Gui, GuiParent { } - @SuppressWarnings("deprecation") protected void handleInvDoubleClick(InventoryClickEvent event, Inventory inventory, Player player, ItemStack cursor) { - if (cursor == null) return; + if (cursor == null) + return; - UpdateReason updateReason = new PlayerUpdateReason(player, event); - cursor.setAmount(inventory.collectSimilar(updateReason, cursor)); - event.setCursor(cursor); + // windows handle cursor collect because it is a cross-inventory / cross-gui operation + Window window = WindowManager.getInstance().getOpenWindow(player); + ((AbstractWindow) window).handleCursorCollect(event); } public boolean handleItemDrag(UpdateReason updateReason, int slot, ItemStack oldStack, ItemStack newStack) { @@ -329,7 +333,7 @@ public abstract class AbstractGui implements Gui, GuiParent { } protected int putIntoFirstInventory(UpdateReason updateReason, ItemStack itemStack, Inventory... ignored) { - LinkedHashSet inventories = getAllInventories(ignored); + Collection inventories = getAllInventories(ignored); int originalAmount = itemStack.getAmount(); if (!inventories.isEmpty()) { @@ -343,15 +347,40 @@ public abstract class AbstractGui implements Gui, GuiParent { return originalAmount; } - public LinkedHashSet getAllInventories(Inventory... ignored) { - return Arrays.stream(slotElements) - .filter(Objects::nonNull) - .map(SlotElement::getHoldingElement) - .filter(element -> element instanceof SlotElement.InventorySlotElement) - .map(element -> ((SlotElement.InventorySlotElement) element).getInventory()) - .filter(vi -> Arrays.stream(ignored).noneMatch(vi::equals)) - .sorted((vi1, vi2) -> -Integer.compare(vi1.getGuiPriority(), vi2.getGuiPriority())) - .collect(Collectors.toCollection(LinkedHashSet::new)); + public Map getAllInventorySlots(Inventory... ignored) { + TreeMap slots = new TreeMap<>(Comparator.comparingInt(Inventory::getGuiPriority).reversed()); + Set ignoredSet = Arrays.stream(ignored).collect(Collectors.toSet()); + + for (SlotElement element : slotElements) { + if (element == null) + continue; + + element = element.getHoldingElement(); + if (element instanceof SlotElement.InventorySlotElement) { + SlotElement.InventorySlotElement invElement = (SlotElement.InventorySlotElement) element; + Inventory inventory = invElement.getInventory(); + if (ignoredSet.contains(inventory)) + continue; + + slots.computeIfAbsent(inventory, i -> new IntOpenHashSet()).add(invElement.getSlot()); + } + } + + return slots; + } + + public Collection getAllInventories(Inventory... ignored) { + if (!ignoreObscuredInventorySlots) + return getAllInventorySlots(ignored).keySet(); + + ArrayList inventories = new ArrayList<>(); + for (Map.Entry entry : getAllInventorySlots(ignored).entrySet()) { + Inventory inventory = entry.getKey(); + IntSet slots = entry.getValue(); + inventories.add(new ObscuredInventory(inventory, slot -> !slots.contains(slot))); + } + + return inventories; } // endregion @@ -590,6 +619,16 @@ public abstract class AbstractGui implements Gui, GuiParent { return frozen; } + @Override + public void setIgnoreObscuredInventorySlots(boolean ignoreObscuredInventorySlots) { + this.ignoreObscuredInventorySlots = ignoreObscuredInventorySlots; + } + + @Override + public boolean isIgnoreObscuredInventorySlots() { + return ignoreObscuredInventorySlots; + } + // region coordinate-based methods @Override public void setSlotElement(int x, int y, SlotElement slotElement) { @@ -714,6 +753,7 @@ public abstract class AbstractGui implements Gui, GuiParent { protected ItemProvider background; protected List> modifiers; protected boolean frozen; + protected boolean ignoreObscuredInventorySlots = true; @Override public @NotNull S setStructure(int width, int height, @NotNull String structureData) { @@ -805,6 +845,12 @@ public abstract class AbstractGui implements Gui, GuiParent { return (S) this; } + @Override + public @NotNull S setIgnoreObscuredInventorySlots(boolean ignoreObscuredInventorySlots) { + this.ignoreObscuredInventorySlots = ignoreObscuredInventorySlots; + return (S) this; + } + @Override public @NotNull S addModifier(@NotNull Consumer<@NotNull G> modifier) { if (modifiers == null) @@ -822,6 +868,7 @@ public abstract class AbstractGui implements Gui, GuiParent { protected void applyModifiers(@NotNull G gui) { gui.setFrozen(frozen); + gui.setIgnoreObscuredInventorySlots(ignoreObscuredInventorySlots); if (background != null) gui.setBackground(background); if (modifiers != null) modifiers.forEach(modifier -> modifier.accept(gui)); } diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/gui/Gui.java b/invui-core/src/main/java/xyz/xenondevs/invui/gui/Gui.java index aa9a04e..d487bc8 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/gui/Gui.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/gui/Gui.java @@ -298,6 +298,25 @@ public interface Gui { */ boolean isFrozen(); + /** + * Configures whether it is possible to shift-click items into and cursor collect items from all {@link Inventory} + * slots of partially obscured embedded {@link Inventory Inventories}. + *

+ * Defaults to true. + * + * @param ignoreObscuredInventorySlots Whether obscured {@link Inventory} slots should be ignored when shift-clicking + * and collecting to the cursor. + */ + void setIgnoreObscuredInventorySlots(boolean ignoreObscuredInventorySlots); + + /** + * Gets whether it is possible to shift-click items into and cursor collect items from all {@link Inventory} + * slots of partially obscured embedded {@link Inventory Inventories}. + * + * @return Whether obscured {@link Inventory} slots are ignored when shift-clicking and collecting to the cursor. + */ + boolean isIgnoreObscuredInventorySlots(); + // /** @@ -547,6 +566,17 @@ public interface Gui { @Contract("_ -> this") @NotNull S setFrozen(boolean frozen); + /** + * Sets whether it is possible to shift-click items into and cursor collect items from all {@link Inventory} + * slots of partially obscured embedded {@link Inventory Inventories}. + * + * @param ignoreObscuredInventorySlots Whether obscured {@link Inventory} slots should be ignored when shift-clicking + * and collecting to the cursor. + * @return This {@link Builder Gui Builder} + */ + @Contract("_ -> this") + @NotNull S setIgnoreObscuredInventorySlots(boolean ignoreObscuredInventorySlots); + /** * Sets the background of the {@link Gui}. * diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/inventory/ObscuredInventory.java b/invui-core/src/main/java/xyz/xenondevs/invui/inventory/ObscuredInventory.java new file mode 100644 index 0000000..e3cfda6 --- /dev/null +++ b/invui-core/src/main/java/xyz/xenondevs/invui/inventory/ObscuredInventory.java @@ -0,0 +1,101 @@ +package xyz.xenondevs.invui.inventory; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.IntPredicate; + +/** + * An {@link Inventory} that delegates to another {@link Inventory} while hiding certain slots. + */ +public class ObscuredInventory extends Inventory { + + private final @NotNull Inventory inventory; + private final int @NotNull [] slots; + + /** + * Constructs a new {@link ObscuredInventory}. + * + * @param inventory The {@link Inventory} to delegate to. + * @param isObscured A {@link IntPredicate} that returns true for slots that should be hidden. + */ + public ObscuredInventory(@NotNull Inventory inventory, @NotNull IntPredicate isObscured) { + this.inventory = inventory; + + IntArrayList slots = new IntArrayList(); + for (int slot = 0; slot < inventory.getSize(); slot++) { + if (isObscured.test(slot)) + continue; + + slots.add(slot); + } + + this.slots = slots.toIntArray(); + } + + @Override + public int getSize() { + return slots.length; + } + + @Override + public int @NotNull [] getMaxStackSizes() { + int[] maxStackSizes = new int[slots.length]; + for (int i = 0; i < slots.length; i++) { + maxStackSizes[i] = inventory.getMaxSlotStackSize(slots[i]); + } + return maxStackSizes; + } + + @Override + public int getMaxSlotStackSize(int slot) { + return inventory.getMaxSlotStackSize(slots[slot]); + } + + @Override + public @Nullable ItemStack @NotNull [] getItems() { + ItemStack[] items = new ItemStack[slots.length]; + for (int i = 0; i < slots.length; i++) { + items[i] = inventory.getItem(slots[i]); + } + return items; + } + + @Override + public @Nullable ItemStack @NotNull [] getUnsafeItems() { + ItemStack[] items = new ItemStack[slots.length]; + for (int i = 0; i < slots.length; i++) { + items[i] = inventory.getUnsafeItem(slots[i]); + } + return items; + } + + @Override + public @Nullable ItemStack getItem(int slot) { + return inventory.getItem(slots[slot]); + } + + @Override + public @Nullable ItemStack getUnsafeItem(int slot) { + return inventory.getUnsafeItem(slots[slot]); + } + + @Override + protected void setCloneBackingItem(int slot, @Nullable ItemStack itemStack) { + inventory.setCloneBackingItem(slots[slot], itemStack); + } + + @Override + protected void setDirectBackingItem(int slot, @Nullable ItemStack itemStack) { + inventory.setDirectBackingItem(slots[slot], itemStack); + } + + @Override + public void notifyWindows() { + super.notifyWindows(); + inventory.notifyWindows(); + } + +} diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractDoubleWindow.java b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractDoubleWindow.java index 88c292b..6e6e344 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractDoubleWindow.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractDoubleWindow.java @@ -131,11 +131,6 @@ public abstract class AbstractDoubleWindow extends AbstractWindow { // empty, should not be called by the WindowManager } - @Override - public void handleCursorCollect(InventoryClickEvent event) { - // empty, should not be called by the WindowManager - } - @Override public Inventory[] getInventories() { return isOpen() ? new Inventory[] {upperInventory, playerInventory} : new Inventory[] {upperInventory}; diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractMergedWindow.java b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractMergedWindow.java index 4cef469..05e8fa4 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractMergedWindow.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractMergedWindow.java @@ -7,9 +7,13 @@ import xyz.xenondevs.inventoryaccess.component.ComponentWrapper; import xyz.xenondevs.invui.gui.AbstractGui; import xyz.xenondevs.invui.gui.Gui; import xyz.xenondevs.invui.gui.SlotElement; +import xyz.xenondevs.invui.inventory.ReferencingInventory; import xyz.xenondevs.invui.util.Pair; import xyz.xenondevs.invui.util.SlotUtils; +import java.util.ArrayList; +import java.util.List; + /** * A {@link Window} where top and player {@link Inventory} are affected by the same {@link Gui}. */ @@ -53,4 +57,11 @@ public abstract class AbstractMergedWindow extends AbstractDoubleWindow { return new AbstractGui[] {gui}; } + @Override + protected List getContentInventories() { + List inventories = new ArrayList<>(gui.getAllInventories()); + inventories.add(ReferencingInventory.fromStorageContents(getViewer().getInventory())); + return inventories; + } + } diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSingleWindow.java b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSingleWindow.java index 42d747d..129aaaa 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSingleWindow.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSingleWindow.java @@ -10,15 +10,12 @@ import xyz.xenondevs.inventoryaccess.component.ComponentWrapper; import xyz.xenondevs.invui.gui.AbstractGui; import xyz.xenondevs.invui.gui.Gui; import xyz.xenondevs.invui.gui.SlotElement; -import xyz.xenondevs.invui.inventory.CompositeInventory; import xyz.xenondevs.invui.inventory.Inventory; import xyz.xenondevs.invui.inventory.ReferencingInventory; -import xyz.xenondevs.invui.inventory.event.PlayerUpdateReason; -import xyz.xenondevs.invui.inventory.event.UpdateReason; -import xyz.xenondevs.invui.util.InventoryUtils; import xyz.xenondevs.invui.util.Pair; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; /** @@ -77,30 +74,11 @@ public abstract class AbstractSingleWindow extends AbstractWindow { gui.handleItemShift(event); } - @SuppressWarnings("deprecation") @Override - public void handleCursorCollect(InventoryClickEvent event) { - // cancel event as we do the collection logic ourselves - event.setCancelled(true); - - Player player = (Player) event.getWhoClicked(); - - // the template item stack that is used to collect similar items - ItemStack template = event.getCursor(); - int maxStackSize = InventoryUtils.stackSizeProvider.getMaxStackSize(template); - - // create a composite inventory consisting of all the gui's inventories and the player's inventory - Set inventories = gui.getAllInventories(); - inventories.add(ReferencingInventory.fromStorageContents(player.getInventory())); - Inventory inventory = new CompositeInventory(inventories); - - // collect items from inventories until the cursor is full - UpdateReason updateReason = new PlayerUpdateReason(player, event); - int amount = inventory.collectSimilar(updateReason, template); - - // put collected items on cursor - template.setAmount(amount); - event.setCursor(template); + protected List getContentInventories() { + List inventories = new ArrayList<>(gui.getAllInventories()); + inventories.add(ReferencingInventory.fromStorageContents(getViewer().getInventory())); + return inventories; } @Override diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSplitWindow.java b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSplitWindow.java index f0de24a..d5b6b04 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSplitWindow.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractSplitWindow.java @@ -11,6 +11,8 @@ import xyz.xenondevs.invui.gui.SlotElement; import xyz.xenondevs.invui.util.Pair; import xyz.xenondevs.invui.util.SlotUtils; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; /** @@ -66,6 +68,14 @@ public abstract class AbstractSplitWindow extends AbstractDoubleWindow { return new AbstractGui[] {upperGui, lowerGui}; } + @Override + protected List getContentInventories() { + List inventories = new ArrayList<>(); + inventories.addAll(upperGui.getAllInventories()); + inventories.addAll(lowerGui.getAllInventories()); + return inventories; + } + @SuppressWarnings("unchecked") public static abstract class AbstractBuilder> extends AbstractWindow.AbstractBuilder diff --git a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java index 7006073..2c5e73b 100644 --- a/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java +++ b/invui-core/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java @@ -26,12 +26,14 @@ import xyz.xenondevs.invui.gui.AbstractGui; import xyz.xenondevs.invui.gui.Gui; import xyz.xenondevs.invui.gui.GuiParent; import xyz.xenondevs.invui.gui.SlotElement; +import xyz.xenondevs.invui.inventory.CompositeInventory; import xyz.xenondevs.invui.inventory.Inventory; import xyz.xenondevs.invui.inventory.event.PlayerUpdateReason; import xyz.xenondevs.invui.inventory.event.UpdateReason; import xyz.xenondevs.invui.item.Item; import xyz.xenondevs.invui.item.ItemProvider; import xyz.xenondevs.invui.util.ArrayUtils; +import xyz.xenondevs.invui.util.InventoryUtils; import xyz.xenondevs.invui.util.Pair; import java.util.*; @@ -197,6 +199,30 @@ public abstract class AbstractWindow implements Window, GuiParent { } } + @SuppressWarnings("deprecation") + public void handleCursorCollect(InventoryClickEvent event) { + // cancel event as we do the collection logic ourselves + event.setCancelled(true); + + Player player = (Player) event.getWhoClicked(); + + // the template item stack that is used to collect similar items + ItemStack template = event.getCursor(); + int maxStackSize = InventoryUtils.stackSizeProvider.getMaxStackSize(template); + + // create a composite inventory consisting of all the gui's inventories and the player's inventory + List inventories = getContentInventories(); + Inventory inventory = new CompositeInventory(inventories); + + // collect items from inventories until the cursor is full + UpdateReason updateReason = new PlayerUpdateReason(player, event); + int amount = inventory.collectSimilar(updateReason, template); + + // put collected items on cursor + template.setAmount(amount); + event.setCursor(template); + } + public void handleItemProviderUpdate(Item item) { getItemSlotElements(item).forEach((index, slotElement) -> redrawItem(index, slotElement, false)); @@ -431,6 +457,8 @@ public abstract class AbstractWindow implements Window, GuiParent { protected abstract org.bukkit.inventory.Inventory[] getInventories(); + protected abstract List getContentInventories(); + protected abstract void initItems(); protected abstract void handleOpened(); @@ -441,8 +469,6 @@ public abstract class AbstractWindow implements Window, GuiParent { protected abstract void handleItemShift(InventoryClickEvent event); - protected abstract void handleCursorCollect(InventoryClickEvent event); - public abstract void handleViewerDeath(PlayerDeathEvent event); @SuppressWarnings("unchecked")