Ignore obscured embedded inventory slots by default

This commit is contained in:
NichtStudioCode 2023-09-30 17:13:01 +02:00
parent f105a9aabc
commit bc1345da59
9 changed files with 254 additions and 50 deletions

@ -25,6 +25,12 @@
<version>1.19.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>it.unimi.dsi</groupId>
<artifactId>fastutil</artifactId>
<version>8.5.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>

@ -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<GuiParent> 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<Inventory> inventories = getAllInventories(ignored);
Collection<Inventory> 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<Inventory> 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<Inventory, IntSet> getAllInventorySlots(Inventory... ignored) {
TreeMap<Inventory, IntSet> slots = new TreeMap<>(Comparator.comparingInt(Inventory::getGuiPriority).reversed());
Set<Inventory> 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<Inventory> getAllInventories(Inventory... ignored) {
if (!ignoreObscuredInventorySlots)
return getAllInventorySlots(ignored).keySet();
ArrayList<Inventory> inventories = new ArrayList<>();
for (Map.Entry<Inventory, IntSet> 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<Consumer<G>> 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));
}

@ -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}.
* <p>
* 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();
//<editor-fold desc="fill methods">
/**
@ -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}.
*

@ -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();
}
}

@ -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};

@ -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<xyz.xenondevs.invui.inventory.Inventory> getContentInventories() {
List<xyz.xenondevs.invui.inventory.Inventory> inventories = new ArrayList<>(gui.getAllInventories());
inventories.add(ReferencingInventory.fromStorageContents(getViewer().getInventory()));
return inventories;
}
}

@ -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<Inventory> 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<Inventory> getContentInventories() {
List<Inventory> inventories = new ArrayList<>(gui.getAllInventories());
inventories.add(ReferencingInventory.fromStorageContents(getViewer().getInventory()));
return inventories;
}
@Override

@ -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<xyz.xenondevs.invui.inventory.Inventory> getContentInventories() {
List<xyz.xenondevs.invui.inventory.Inventory> inventories = new ArrayList<>();
inventories.addAll(upperGui.getAllInventories());
inventories.addAll(lowerGui.getAllInventories());
return inventories;
}
@SuppressWarnings("unchecked")
public static abstract class AbstractBuilder<W extends Window, S extends Window.Builder.Double<W, S>>
extends AbstractWindow.AbstractBuilder<W, S>

@ -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<Inventory> 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<xyz.xenondevs.invui.inventory.Inventory> 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")