From 09e218831b1a17df7c2e303e6ee5b4529b37d11f Mon Sep 17 00:00:00 2001 From: Jikoo Date: Sat, 17 Oct 2026 18:59:17 -0400 Subject: [PATCH 1/7] Error when plugins set disallowed slots Less useful for preventing bad sorting plugin behavior, but more useful for most sane cases like inventory-wide item manipulation. --- .../container/menu/OpenEnderChestMenu.java | 4 +- .../container/menu/OpenInventoryMenu.java | 4 +- .../container/bukkit/OpenPlayerInventory.java | 36 +++++++++++++++- .../container/bukkit/OpenPlayerInventory.java | 43 ++++++++++++++++--- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenEnderChestMenu.java b/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenEnderChestMenu.java index dc45137e..163a24ea 100644 --- a/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenEnderChestMenu.java +++ b/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenEnderChestMenu.java @@ -59,14 +59,14 @@ public ItemStack quickMoveStack(Player player, int index) { } @Override - public void clicked(int i, int j, ClickType clickType, Player player) { + public void clicked(int slotIndex, int button, ClickType clickType, Player player) { if (viewOnly) { if (clickType == ClickType.QUICK_CRAFT) { sendAllDataToRemote(); } return; } - super.clicked(i, j, clickType, player); + super.clicked(slotIndex, button, clickType, player); } } diff --git a/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenInventoryMenu.java b/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenInventoryMenu.java index 3bc0f134..b2fc2d0e 100644 --- a/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenInventoryMenu.java +++ b/internal/paper1_21_11/src/main/java/com/lishid/openinv/internal/paper1_21_11/container/menu/OpenInventoryMenu.java @@ -26,14 +26,14 @@ public OpenInventoryMenu( } @Override - public void clicked(int i, int j, ClickType clickType, Player player) { + public void clicked(int slotIndex, int button, ClickType clickType, Player player) { if (viewOnly) { if (clickType == ClickType.QUICK_CRAFT) { sendAllDataToRemote(); } return; } - super.clicked(i, j, clickType, player); + super.clicked(slotIndex, button, clickType, player); } } diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java index 9c957e92..2a08038c 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java @@ -3,6 +3,7 @@ import com.google.common.base.Preconditions; import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory; import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Inventory; import org.bukkit.craftbukkit.inventory.CraftInventory; @@ -30,15 +31,33 @@ public BaseOpenInventory getInventory() { @Override public ItemStack[] getContents() { - return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents()); + return asCraftMirror(getInventory().getContents()); } @Override public void setContents(@Nullable ItemStack[] items) { - Inventory internal = getInventory().getOwnerHandle().getInventory(); + Container internal = getInventory(); int size = internal.getContainerSize(); Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + if (items.length < size) { + // If size matches normal player inventory size, redirect to i. + Container ownerInternal = getInventory().getOwnerHandle().getInventory(); + if (items.length == ownerInternal.getContainerSize()) { + internal = ownerInternal; + size = items.length; + } + } else { + // Otherwise, ensure that the display-only slots are not in use. + for (int index = 0; index < size; ++index) { + ItemStack itemStack = items[index]; + if (itemStack == null || itemStack.isEmpty()) { + continue; + } + validateSet(index); + } + } + for (int index = 0; index < size; ++index) { if (index < items.length) { internal.setItem(index, CraftItemStack.asNMSCopy(items[index])); @@ -48,6 +67,19 @@ public void setContents(@Nullable ItemStack[] items) { } } + private void validateSet(int index) { + InventoryType.SlotType slotType = getInventory().getSlotType(index); + if (slotType == InventoryType.SlotType.OUTSIDE || slotType == InventoryType.SlotType.RESULT) { + throw new IllegalArgumentException("A plugin attempted to add an item to a display slot, deleting it!"); + } + } + + @Override + public void setItem(int index, @Nullable ItemStack item) { + validateSet(index); + super.setItem(index, item); + } + @Override public ItemStack[] getStorageContents() { return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems()); diff --git a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java index 89f4cbfe..5a9a493a 100644 --- a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java @@ -3,6 +3,7 @@ import com.github.jikoo.openinv.internal.spigot26_3.container.OpenInventory; import com.google.common.base.Preconditions; import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; import net.minecraft.world.entity.player.Inventory; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftItemStack; @@ -27,15 +28,29 @@ public OpenInventory getInventory() { @Override public ItemStack[] getContents() { - return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents()); + return asCraftMirror(getInventory().getContents()); } @Override - public void setContents(ItemStack[] items) { - Inventory internal = getInventory().getOwnerHandle().getInventory(); + public void setContents(@Nullable ItemStack[] items) { + Container internal = getInventory(); int size = internal.getContainerSize(); Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + if (items.length < size) { + // If size matches normal player inventory size, redirect to it. + Container ownerInternal = getInventory().getOwnerHandle().getInventory(); + if (items.length == ownerInternal.getContainerSize()) { + internal = ownerInternal; + size = items.length; + } + } else { + // Otherwise, ensure that the display-only slots are not in use. + for (int index = 0; index < size; ++index) { + validateSet(index, items[index]); + } + } + for (int index = 0; index < size; ++index) { if (index < items.length) { internal.setItem(index, CraftItemStack.asNMSCopy(items[index])); @@ -45,13 +60,29 @@ public void setContents(ItemStack[] items) { } } + private void validateSet(int index, @Nullable ItemStack itemStack) { + if (itemStack == null || itemStack.getType().isAir() || itemStack.getAmount() <= 0) { + return; + } + InventoryType.SlotType slotType = getInventory().getSlotType(index); + if (slotType == InventoryType.SlotType.OUTSIDE || slotType == InventoryType.SlotType.RESULT) { + throw new IllegalArgumentException("A plugin attempted to add an item to a display slot, deleting it!"); + } + } + + @Override + public void setItem(int index, @Nullable ItemStack item) { + validateSet(index, item); + super.setItem(index, item); + } + @Override public ItemStack[] getStorageContents() { return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems()); } @Override - public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgumentException { NonNullList list = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems(); int size = list.size(); Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); @@ -76,7 +107,7 @@ public ItemStack[] getArmorContents() { } @Override - public void setArmorContents(ItemStack[] items) { + public void setArmorContents(@Nullable ItemStack[] items) { getInventory().getOwnerHandle().getBukkitEntity().getInventory().setArmorContents(items); } @@ -86,7 +117,7 @@ public ItemStack[] getExtraContents() { } @Override - public void setExtraContents(ItemStack[] items) { + public void setExtraContents(@Nullable ItemStack[] items) { getInventory().getOwnerHandle().getBukkitEntity().getInventory().setExtraContents(items); } From 6829be335e43ea0554baa07b728fb8063cbda183 Mon Sep 17 00:00:00 2001 From: Jikoo Date: Sun, 18 Oct 2026 11:36:37 -0400 Subject: [PATCH 2/7] Reimplement more API for Paper Corrects slots for all, first, and firstEmpty. Still need to reimplement addItem and removeItem, then port to Spigot. --- .../container/BaseOpenInventory.java | 2 +- .../container/bukkit/OpenPlayerInventory.java | 117 +++++++++++++++++- .../bukkit/OpenPlayerInventorySelf.java | 13 +- .../container/menu/BaseOpenInventoryMenu.java | 2 +- 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/BaseOpenInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/BaseOpenInventory.java index e7b11c94..c7451dba 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/BaseOpenInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/BaseOpenInventory.java @@ -187,7 +187,7 @@ public ServerPlayer getOwnerHandle() { @Override public org.bukkit.inventory.Inventory getBukkitInventory() { if (bukkitEntity == null) { - bukkitEntity = new OpenPlayerInventory(this); + bukkitEntity = new OpenPlayerInventory(factory, this); } return bukkitEntity; } diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java index 2a08038c..e9ac0c69 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java @@ -1,13 +1,19 @@ package com.lishid.openinv.internal.paper26_3.container.bukkit; +import com.github.jikoo.openinv.internal.container.slot.InventoryFactory; import com.google.common.base.Preconditions; import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory; import net.minecraft.core.NonNullList; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.Item; +import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; @@ -15,13 +21,21 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.HashMap; import java.util.List; +import java.util.function.BiPredicate; @NullMarked public class OpenPlayerInventory extends CraftInventory implements PlayerInventory { - public OpenPlayerInventory(BaseOpenInventory inventory) { + protected final InventoryFactory factory; + + public OpenPlayerInventory( + InventoryFactory factory, + BaseOpenInventory inventory + ) { super(inventory); + this.factory = factory; } @Override @@ -30,7 +44,7 @@ public BaseOpenInventory getInventory() { } @Override - public ItemStack[] getContents() { + public @Nullable ItemStack[] getContents() { return asCraftMirror(getInventory().getContents()); } @@ -81,7 +95,7 @@ public void setItem(int index, @Nullable ItemStack item) { } @Override - public ItemStack[] getStorageContents() { + public @Nullable ItemStack[] getStorageContents() { return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems()); } @@ -95,6 +109,99 @@ public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgume } } + @SuppressWarnings("NonApiType") + @Override + public HashMap all(Material material) { + Preconditions.checkNotNull(material, "Material cannot be null"); + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + + BaseOpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + HashMap slots = new HashMap<>(); + for (int index = 0; index < size; ++index) { + net.minecraft.world.item.ItemStack itemStack = inventory.getItem(index); + if (!itemStack.isEmpty() && item.equals(itemStack.getItem())) { + slots.put(index, factory.asBukkitMirror(itemStack)); + } + } + + return slots; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(@Nullable ItemStack item) { + HashMap slots = new HashMap<>(); + + if (item == null) { + return slots; + } + + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.unwrap(item); + BaseOpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int index = 0; index < size; ++index) { + net.minecraft.world.item.ItemStack slotItemStack = inventory.getItem(index); + if (ItemMatcher.EXACT.predicate.test(itemStack, slotItemStack)) { + slots.put(index, factory.asBukkitMirror(slotItemStack)); + } + } + + return slots; + } + + @Override + public int first(Material material) { + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(item); + int size = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(itemStack, ItemMatcher.TYPE, 0, size); + } + + @Override + public int first(ItemStack item) { + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.unwrap(item); + int size = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(itemStack, ItemMatcher.EXACT, 0, size); + } + + @Override + public int firstEmpty() { + BaseOpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(net.minecraft.world.item.ItemStack.EMPTY, ItemMatcher.EMPTY, 0, size); + } + + private int first(net.minecraft.world.item.ItemStack itemStack, ItemMatcher matcher, int startIndex, int endIndex) { + for (int index = startIndex; index < endIndex; ++index) { + net.minecraft.world.item.ItemStack inventoryStack = inventory.getItem(index); + if (matcher.predicate.test(itemStack, inventoryStack)) { + return index; + } + } + return -1; + } + + @SuppressWarnings("ImmutableEnumChecker") // Ignore Google's style preference for predicate-like enums + private enum ItemMatcher { + EMPTY((ignored, b) -> b.isEmpty()), + TYPE(net.minecraft.world.item.ItemStack::isSameItem), + SIMILAR(net.minecraft.world.item.ItemStack::isSameItemSameComponents), + EXACT((a, b) -> net.minecraft.world.item.ItemStack.isSameItemSameComponents(a, b) && a.count() == b.count()); + + private final BiPredicate predicate; + + ItemMatcher(BiPredicate predicate) { + this.predicate = predicate; + } + } + + // TODO addItem, removeItem + @Override public InventoryType getType() { return InventoryType.PLAYER; @@ -106,7 +213,7 @@ public Player getHolder() { } @Override - public ItemStack[] getArmorContents() { + public @Nullable ItemStack[] getArmorContents() { return asCraftMirror(getInventory().getOwnerHandle().getInventory().getArmorContents()); } @@ -123,7 +230,7 @@ public void setArmorContents(@Nullable ItemStack[] items) { } @Override - public ItemStack[] getExtraContents() { + public @Nullable ItemStack[] getExtraContents() { return asCraftMirror(List.of(getInventory().getOwnerHandle().getInventory().equipment.get(EquipmentSlot.OFFHAND))); } diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java index 1d7eb3b4..c513c763 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java @@ -1,6 +1,11 @@ package com.lishid.openinv.internal.paper26_3.container.bukkit; +import com.github.jikoo.openinv.internal.container.slot.InventoryFactory; import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.inventory.Slot; import org.bukkit.inventory.ItemStack; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -10,8 +15,12 @@ public class OpenPlayerInventorySelf extends OpenPlayerInventory { private final int offset; - public OpenPlayerInventorySelf(BaseOpenInventory inventory, int offset) { - super(inventory); + public OpenPlayerInventorySelf( + InventoryFactory factory, + BaseOpenInventory inventory, + int offset + ) { + super(factory, inventory); this.offset = offset; } diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/menu/BaseOpenInventoryMenu.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/menu/BaseOpenInventoryMenu.java index d013f0c6..9c34d8cb 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/menu/BaseOpenInventoryMenu.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/menu/BaseOpenInventoryMenu.java @@ -112,7 +112,7 @@ protected CraftInventoryView, Inventory> create if (viewOnly) { bukkitInventory = new OpenDummyPlayerInventory(container); } else if (ownContainer) { - bukkitInventory = new OpenPlayerInventorySelf(container, offset); + bukkitInventory = new OpenPlayerInventorySelf(factory, container, offset); } else { bukkitInventory = container.getBukkitInventory(); } From 394908a9595c1bc6e9e55e1efe3acd4797917079 Mon Sep 17 00:00:00 2001 From: Jikoo Date: Sun, 18 Oct 2026 20:09:17 -0400 Subject: [PATCH 3/7] Reimplement addItem --- .../container/bukkit/OpenPlayerInventory.java | 111 +++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java index e9ac0c69..053df054 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java @@ -200,7 +200,116 @@ private enum ItemMatcher { } } - // TODO addItem, removeItem + @SuppressWarnings("NonApiType") + @Override + public HashMap addItem(ItemStack... items) { + Preconditions.checkNotNull(items, "items cannot be null"); + HashMap leftover = new HashMap<>(); + + BaseOpenInventory inventory = getInventory(); + int storageSize = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int i = 0; i < items.length; i++) { + addItem(items, i, storageSize, leftover); + } + + return leftover; + } + + private void addItem( + ItemStack[] items, + int leftoverIndex, + int storageSize, + HashMap leftover + ) { + ItemStack bukkitStack = items[leftoverIndex]; + Preconditions.checkNotNull(bukkitStack, "ItemStack cannot be null"); + if (bukkitStack.isEmpty()) { + return; + } + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.unwrap(bukkitStack); + Preconditions.checkNotNull(nmsStack, "ItemStack cannot be empty"); + + int maxStackSize = inventory.getMaxStackSize(nmsStack); + int stackCount = nmsStack.count(); + + // Try to fill existing stacks first. Waste of time for items that don't stack. + if (maxStackSize > 1) { + stackCount = addPartial(storageSize, nmsStack, maxStackSize, stackCount); + if (nmsStack.count() < 1) { + // All done adding, next item. + return; + } + } + + // Add to empty slots. + stackCount = addEmpty(storageSize, stackCount, maxStackSize, nmsStack); + + if (stackCount > 0) { + bukkitStack.setAmount(stackCount); + leftover.put(leftoverIndex, bukkitStack); + } + } + + private int addPartial( + int storageSize, + net.minecraft.world.item.ItemStack nmsStack, + int maxStackSize, + int stackCount + ) { + for ( + int firstPartial = first(nmsStack, ItemMatcher.SIMILAR, 0, storageSize); + firstPartial < storageSize && firstPartial > -1; + firstPartial = first(nmsStack, ItemMatcher.EXACT, firstPartial + 1, storageSize) + ) { + net.minecraft.world.item.ItemStack existing = inventory.getItem(firstPartial); + int existingCount = existing.count(); + if (existingCount >= maxStackSize) { + continue; + } + + int fit = maxStackSize - existingCount; + + if (fit >= stackCount) { + nmsStack.setCount(0); + existing.setCount(existingCount + stackCount); + return 0; + } + + stackCount -= fit; + nmsStack.setCount(stackCount); + existing.setCount(maxStackSize); + } + return stackCount; + } + + private int addEmpty( + int storageSize, + int stackCount, + int maxStackSize, + net.minecraft.world.item.ItemStack nmsStack + ) { + for ( + int firstEmpty = firstEmpty(); + firstEmpty < storageSize && firstEmpty > -1; + firstEmpty = first(net.minecraft.world.item.ItemStack.EMPTY, ItemMatcher.EMPTY, firstEmpty, storageSize) + ) { + if (stackCount <= maxStackSize) { + inventory.setItem(firstEmpty, nmsStack.copy()); + return 0; + } + + var newStack = nmsStack.copy(); + stackCount -= maxStackSize; + nmsStack.setCount(stackCount); + newStack.setCount(maxStackSize); + inventory.setItem(firstEmpty, newStack); + } + + return stackCount; + } + + // TODO removeItem @Override public InventoryType getType() { From 33d738afc796c6d855e2bf3f1361ea074d5ac465 Mon Sep 17 00:00:00 2001 From: Jikoo Date: Fri, 23 Oct 2026 17:52:30 -0400 Subject: [PATCH 4/7] Reimplement required removals #removeItemAnySlot uses full contents, so slots will line up there. --- .../container/bukkit/OpenPlayerInventory.java | 100 ++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java index 053df054..8db16596 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java @@ -10,6 +10,7 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftItemStack; @@ -23,6 +24,7 @@ import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.BiPredicate; @NullMarked @@ -55,7 +57,7 @@ public void setContents(@Nullable ItemStack[] items) { Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); if (items.length < size) { - // If size matches normal player inventory size, redirect to i. + // If size matches normal player inventory size, redirect to it. Container ownerInternal = getInventory().getOwnerHandle().getInventory(); if (items.length == ownerInternal.getContainerSize()) { internal = ownerInternal; @@ -209,8 +211,8 @@ public HashMap addItem(ItemStack... items) { BaseOpenInventory inventory = getInventory(); int storageSize = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); - for (int i = 0; i < items.length; i++) { - addItem(items, i, storageSize, leftover); + for (int index = 0; index < items.length; ++index) { + addItem(items, index, storageSize, leftover); } return leftover; @@ -220,7 +222,7 @@ private void addItem( ItemStack[] items, int leftoverIndex, int storageSize, - HashMap leftover + Map leftover ) { ItemStack bukkitStack = items[leftoverIndex]; Preconditions.checkNotNull(bukkitStack, "ItemStack cannot be null"); @@ -260,7 +262,7 @@ private int addPartial( for ( int firstPartial = first(nmsStack, ItemMatcher.SIMILAR, 0, storageSize); firstPartial < storageSize && firstPartial > -1; - firstPartial = first(nmsStack, ItemMatcher.EXACT, firstPartial + 1, storageSize) + firstPartial = first(nmsStack, ItemMatcher.SIMILAR, firstPartial + 1, storageSize) ) { net.minecraft.world.item.ItemStack existing = inventory.getItem(firstPartial); int existingCount = existing.count(); @@ -309,7 +311,93 @@ private int addEmpty( return stackCount; } - // TODO removeItem + @SuppressWarnings("NonApiType") + @Override + public HashMap removeItem(ItemStack... items) { + Preconditions.checkNotNull(items, "items cannot be null"); + HashMap leftover = new HashMap<>(); + + BaseOpenInventory inventory = getInventory(); + int storageSize = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int index = 0; index < items.length; ++index) { + removeItem(items[index], index, storageSize, leftover); + } + + return leftover; + } + + private void removeItem( + ItemStack bukkitStack, + int leftoverIndex, + int storageSize, + Map leftover + ) { + Preconditions.checkNotNull(bukkitStack, "ItemStack cannot be null"); + if (bukkitStack.isEmpty()) { + return; + } + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.unwrap(bukkitStack); + Preconditions.checkNotNull(nmsStack, "ItemStack cannot be empty"); + + int stackCount = nmsStack.count(); + + for ( + int first = first(nmsStack, ItemMatcher.SIMILAR, 0, storageSize); + first < storageSize && first > -1; + first = first(nmsStack, ItemMatcher.SIMILAR, first + 1, storageSize) + ) { + net.minecraft.world.item.ItemStack existing = inventory.getItem(first); + int existingCount = existing.count(); + if (existingCount > stackCount) { + existing.setCount(existingCount - stackCount); + bukkitStack.setAmount(0); + return; + } + + stackCount -= existingCount; + inventory.setItem(first, net.minecraft.world.item.ItemStack.EMPTY); + + if (stackCount <= 0) { + break; + } + } + + bukkitStack.setAmount(stackCount); + + if (stackCount > 0) { + leftover.put(leftoverIndex, bukkitStack); + } + } + + @Override + public void remove(Material material) { + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + + if (!item.equals(Items.AIR)) { + removeAll(new net.minecraft.world.item.ItemStack(item), ItemMatcher.SIMILAR); + } + } + + @Override + public void remove(ItemStack bukkitStack) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.unwrap(bukkitStack); + + if (!nmsStack.isEmpty()) { + removeAll(nmsStack, ItemMatcher.EXACT); + } + } + + private void removeAll(net.minecraft.world.item.ItemStack itemStack, ItemMatcher matcher) { + int storageSize = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + for (int index = 0; index < storageSize; ++index) { + net.minecraft.world.item.ItemStack content = inventory.getItem(index); + if (matcher.predicate.test(itemStack, content)) { + inventory.setItem(index, net.minecraft.world.item.ItemStack.EMPTY); + } + } + } @Override public InventoryType getType() { From 609736d94ccae5561651a266a85c248bf7bd6a38 Mon Sep 17 00:00:00 2001 From: Jikoo Date: Fri, 23 Oct 2026 19:54:29 -0400 Subject: [PATCH 5/7] Also reimplement for self inventory Probably why there are so many Bukkit wrappers in CraftInventory, more reusable without modifying Container. --- .../container/bukkit/OpenPlayerInventory.java | 4 +- .../bukkit/OpenPlayerInventorySelf.java | 131 ++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java index 8db16596..16551fa4 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventory.java @@ -83,10 +83,10 @@ public void setContents(@Nullable ItemStack[] items) { } } - private void validateSet(int index) { + protected void validateSet(int index) { InventoryType.SlotType slotType = getInventory().getSlotType(index); if (slotType == InventoryType.SlotType.OUTSIDE || slotType == InventoryType.SlotType.RESULT) { - throw new IllegalArgumentException("A plugin attempted to add an item to a display slot, deleting it!"); + throw new IllegalArgumentException("A plugin tried to put an item in a display slot. That would delete it!"); } } diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java index c513c763..d16660ca 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java @@ -1,15 +1,20 @@ package com.lishid.openinv.internal.paper26_3.container.bukkit; import com.github.jikoo.openinv.internal.container.slot.InventoryFactory; +import com.google.common.base.Preconditions; import com.lishid.openinv.internal.paper26_3.container.BaseOpenInventory; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.Container; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.inventory.Slot; +import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.HashMap; + @NullMarked public class OpenPlayerInventorySelf extends OpenPlayerInventory { @@ -24,14 +29,140 @@ public OpenPlayerInventorySelf( this.offset = offset; } + @Override + public int getSize() { + return super.getSize() - offset; + } + @Override public @Nullable ItemStack getItem(int index) { return super.getItem(offset + index); } + @Override + public @Nullable ItemStack[] getStorageContents() { + return new ItemStack[0]; + } + + @Override + public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgumentException { + Preconditions.checkArgument(items.length == 0, "items.length must be <= 0"); + } + + @Override + public @Nullable ItemStack[] getContents() { + @Nullable ItemStack[] contents = new ItemStack[getSize()]; + for (int i = 0; i < contents.length; ++i) { + contents[i] = getItem(i); + } + return contents; + } + + @Override + public void setContents(@Nullable ItemStack[] items) { + int size = getSize(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + + for (int index = 0; index < size; ++index) { + ItemStack itemStack = items[index]; + if (itemStack == null || itemStack.isEmpty()) { + continue; + } + validateSet(index); + } + + for (int index = 0; index < size; ++index) { + if (index < items.length) { + inventory.setItem(offset + index, CraftItemStack.asNMSCopy(items[index])); + } else { + inventory.setItem(offset + index, net.minecraft.world.item.ItemStack.EMPTY); + } + } + } + @Override public void setItem(int index, @Nullable ItemStack item) { super.setItem(offset + index, item); } + @Override + public boolean contains(Material material) { + // Only checks the storage contents. + return false; + } + + @Override + public boolean contains(ItemStack item) { + return false; + } + + @Override + public boolean contains(Material material, int amount) { + return false; + } + + @Override + public boolean contains(ItemStack item, int amount) { + return false; + } + + @Override + public boolean containsAtLeast(ItemStack item, int amount) { + return false; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(Material material) { + // No storage contents, no results. + return new HashMap<>(); + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(@Nullable ItemStack item) { + return new HashMap<>(); + } + + @Override + public int first(Material material) { + return -1; + } + + @Override + public int first(ItemStack item) { + return -1; + } + + @Override + public int firstEmpty() { + return -1; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap addItem(ItemStack... items) { + HashMap map = new HashMap<>(); + for (int index = 0; index < items.length; ++index) { + map.put(index, items[index]); + } + return map; + } + + @Override + public HashMap removeItem(ItemStack... items) { + return addItem(items); + } + + @Override + public void remove(Material material) { + Preconditions.checkNotNull(material, "Material cannot be null"); + Preconditions.checkArgument(material.isItem(), "Material must be an item"); + } + + @Override + public void remove(ItemStack itemStack) { + // Sure did. + } + } From 53414759c306e6a564a6edb37f73c637d26fa10d Mon Sep 17 00:00:00 2001 From: Jikoo Date: Fri, 23 Oct 2026 20:05:17 -0400 Subject: [PATCH 6/7] Copypasta sauce for my spighetti --- .../container/bukkit/OpenPlayerInventory.java | 351 +++++++++++++++++- .../bukkit/OpenPlayerInventorySelf.java | 131 +++++++ 2 files changed, 462 insertions(+), 20 deletions(-) diff --git a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java index 5a9a493a..3f825fca 100644 --- a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java +++ b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventory.java @@ -5,8 +5,12 @@ import net.minecraft.core.NonNullList; import net.minecraft.world.Container; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.ItemStack; @@ -14,6 +18,10 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiPredicate; + @NullMarked public class OpenPlayerInventory extends CraftInventory implements PlayerInventory { @@ -27,7 +35,7 @@ public OpenInventory getInventory() { } @Override - public ItemStack[] getContents() { + public @Nullable ItemStack[] getContents() { return asCraftMirror(getInventory().getContents()); } @@ -47,7 +55,11 @@ public void setContents(@Nullable ItemStack[] items) { } else { // Otherwise, ensure that the display-only slots are not in use. for (int index = 0; index < size; ++index) { - validateSet(index, items[index]); + ItemStack itemStack = items[index]; + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() <= 0) { + continue; + } + validateSet(index); } } @@ -60,24 +72,21 @@ public void setContents(@Nullable ItemStack[] items) { } } - private void validateSet(int index, @Nullable ItemStack itemStack) { - if (itemStack == null || itemStack.getType().isAir() || itemStack.getAmount() <= 0) { - return; - } + protected void validateSet(int index) { InventoryType.SlotType slotType = getInventory().getSlotType(index); if (slotType == InventoryType.SlotType.OUTSIDE || slotType == InventoryType.SlotType.RESULT) { - throw new IllegalArgumentException("A plugin attempted to add an item to a display slot, deleting it!"); + throw new IllegalArgumentException("A plugin tried to put an item in a display slot. That would delete it!"); } } @Override public void setItem(int index, @Nullable ItemStack item) { - validateSet(index, item); + validateSet(index); super.setItem(index, item); } @Override - public ItemStack[] getStorageContents() { + public @Nullable ItemStack[] getStorageContents() { return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems()); } @@ -91,6 +100,294 @@ public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgume } } + @SuppressWarnings("NonApiType") + @Override + public HashMap all(Material material) { + Preconditions.checkNotNull(material, "Material cannot be null"); + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + + OpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + HashMap slots = new HashMap<>(); + for (int index = 0; index < size; ++index) { + net.minecraft.world.item.ItemStack itemStack = inventory.getItem(index); + if (!itemStack.isEmpty() && item.equals(itemStack.getItem())) { + slots.put(index, CraftItemStack.asCraftMirror(itemStack)); + } + } + + return slots; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(@Nullable ItemStack item) { + HashMap slots = new HashMap<>(); + + if (item == null) { + return slots; + } + + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(item); + OpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int index = 0; index < size; ++index) { + net.minecraft.world.item.ItemStack slotItemStack = inventory.getItem(index); + if (ItemMatcher.EXACT.predicate.test(itemStack, slotItemStack)) { + slots.put(index, CraftItemStack.asCraftMirror(slotItemStack)); + } + } + + return slots; + } + + @Override + public int first(Material material) { + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + net.minecraft.world.item.ItemStack itemStack = new net.minecraft.world.item.ItemStack(item); + int size = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(itemStack, ItemMatcher.TYPE, 0, size); + } + + @Override + public int first(ItemStack item) { + net.minecraft.world.item.ItemStack itemStack = CraftItemStack.asNMSCopy(item); + int size = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(itemStack, ItemMatcher.EXACT, 0, size); + } + + @Override + public int firstEmpty() { + OpenInventory inventory = getInventory(); + int size = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + return first(net.minecraft.world.item.ItemStack.EMPTY, ItemMatcher.EMPTY, 0, size); + } + + private int first(net.minecraft.world.item.ItemStack itemStack, ItemMatcher matcher, int startIndex, int endIndex) { + for (int index = startIndex; index < endIndex; ++index) { + net.minecraft.world.item.ItemStack inventoryStack = inventory.getItem(index); + if (matcher.predicate.test(itemStack, inventoryStack)) { + return index; + } + } + return -1; + } + + @SuppressWarnings("ImmutableEnumChecker") // Ignore Google's style preference for predicate-like enums + private enum ItemMatcher { + EMPTY((ignored, b) -> b.isEmpty()), + TYPE(net.minecraft.world.item.ItemStack::isSameItem), + SIMILAR(net.minecraft.world.item.ItemStack::isSameItemSameComponents), + EXACT((a, b) -> net.minecraft.world.item.ItemStack.isSameItemSameComponents(a, b) && a.count() == b.count()); + + private final BiPredicate predicate; + + ItemMatcher(BiPredicate predicate) { + this.predicate = predicate; + } + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap addItem(ItemStack... items) { + Preconditions.checkNotNull(items, "items cannot be null"); + HashMap leftover = new HashMap<>(); + + OpenInventory inventory = getInventory(); + int storageSize = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int index = 0; index < items.length; ++index) { + addItem(items, index, storageSize, leftover); + } + + return leftover; + } + + private void addItem( + ItemStack[] items, + int leftoverIndex, + int storageSize, + Map leftover + ) { + ItemStack bukkitStack = items[leftoverIndex]; + Preconditions.checkNotNull(bukkitStack, "ItemStack cannot be null"); + if (bukkitStack.getType() == Material.AIR || bukkitStack.getAmount() <= 0) { + return; + } + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(bukkitStack); + Preconditions.checkNotNull(nmsStack, "ItemStack cannot be empty"); + + int maxStackSize = inventory.getMaxStackSize(nmsStack); + int stackCount = nmsStack.count(); + + // Try to fill existing stacks first. Waste of time for items that don't stack. + if (maxStackSize > 1) { + stackCount = addPartial(storageSize, nmsStack, maxStackSize, stackCount); + if (nmsStack.count() < 1) { + // All done adding, next item. + return; + } + } + + // Add to empty slots. + stackCount = addEmpty(storageSize, stackCount, maxStackSize, nmsStack); + + if (stackCount > 0) { + bukkitStack.setAmount(stackCount); + leftover.put(leftoverIndex, bukkitStack); + } + } + + private int addPartial( + int storageSize, + net.minecraft.world.item.ItemStack nmsStack, + int maxStackSize, + int stackCount + ) { + for ( + int firstPartial = first(nmsStack, ItemMatcher.SIMILAR, 0, storageSize); + firstPartial < storageSize && firstPartial > -1; + firstPartial = first(nmsStack, ItemMatcher.SIMILAR, firstPartial + 1, storageSize) + ) { + net.minecraft.world.item.ItemStack existing = inventory.getItem(firstPartial); + int existingCount = existing.count(); + if (existingCount >= maxStackSize) { + continue; + } + + int fit = maxStackSize - existingCount; + + if (fit >= stackCount) { + nmsStack.setCount(0); + existing.setCount(existingCount + stackCount); + return 0; + } + + stackCount -= fit; + nmsStack.setCount(stackCount); + existing.setCount(maxStackSize); + } + return stackCount; + } + + private int addEmpty( + int storageSize, + int stackCount, + int maxStackSize, + net.minecraft.world.item.ItemStack nmsStack + ) { + for ( + int firstEmpty = firstEmpty(); + firstEmpty < storageSize && firstEmpty > -1; + firstEmpty = first(net.minecraft.world.item.ItemStack.EMPTY, ItemMatcher.EMPTY, firstEmpty, storageSize) + ) { + if (stackCount <= maxStackSize) { + inventory.setItem(firstEmpty, nmsStack.copy()); + return 0; + } + + var newStack = nmsStack.copy(); + stackCount -= maxStackSize; + nmsStack.setCount(stackCount); + newStack.setCount(maxStackSize); + inventory.setItem(firstEmpty, newStack); + } + + return stackCount; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap removeItem(ItemStack... items) { + Preconditions.checkNotNull(items, "items cannot be null"); + HashMap leftover = new HashMap<>(); + + OpenInventory inventory = getInventory(); + int storageSize = inventory.getOwnerHandle().getInventory().getNonEquipmentItems().size(); + + for (int index = 0; index < items.length; ++index) { + removeItem(items[index], index, storageSize, leftover); + } + + return leftover; + } + + private void removeItem( + ItemStack bukkitStack, + int leftoverIndex, + int storageSize, + Map leftover + ) { + Preconditions.checkNotNull(bukkitStack, "ItemStack cannot be null"); + if (bukkitStack.getType() == Material.AIR || bukkitStack.getAmount() <= 0) { + return; + } + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(bukkitStack); + Preconditions.checkNotNull(nmsStack, "ItemStack cannot be empty"); + + int stackCount = nmsStack.count(); + + for ( + int first = first(nmsStack, ItemMatcher.SIMILAR, 0, storageSize); + first < storageSize && first > -1; + first = first(nmsStack, ItemMatcher.SIMILAR, first + 1, storageSize) + ) { + net.minecraft.world.item.ItemStack existing = inventory.getItem(first); + int existingCount = existing.count(); + if (existingCount > stackCount) { + existing.setCount(existingCount - stackCount); + bukkitStack.setAmount(0); + return; + } + + stackCount -= existingCount; + inventory.setItem(first, net.minecraft.world.item.ItemStack.EMPTY); + + if (stackCount <= 0) { + break; + } + } + + bukkitStack.setAmount(stackCount); + + if (stackCount > 0) { + leftover.put(leftoverIndex, bukkitStack); + } + } + + @Override + public void remove(Material material) { + Item item = CraftItemType.bukkitToMinecraft(material); + Preconditions.checkNotNull(item, "Material must be an item"); + + if (!item.equals(Items.AIR)) { + removeAll(new net.minecraft.world.item.ItemStack(item), ItemMatcher.SIMILAR); + } + } + + @Override + public void remove(ItemStack bukkitStack) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(bukkitStack); + + if (!nmsStack.isEmpty()) { + removeAll(nmsStack, ItemMatcher.EXACT); + } + } + + private void removeAll(net.minecraft.world.item.ItemStack itemStack, ItemMatcher matcher) { + int storageSize = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems().size(); + for (int index = 0; index < storageSize; ++index) { + net.minecraft.world.item.ItemStack content = inventory.getItem(index); + if (matcher.predicate.test(itemStack, content)) { + inventory.setItem(index, net.minecraft.world.item.ItemStack.EMPTY); + } + } + } + @Override public InventoryType getType() { return InventoryType.PLAYER; @@ -148,39 +445,37 @@ public void setChestplate(@Nullable ItemStack chestplate) { @Override public void setLeggings(@Nullable ItemStack leggings) { - getInventory().getOwnerHandle().getBukkitEntity().getInventory().setLeggings(leggings); + getInventory().getOwner().getInventory().setLeggings(leggings); } @Override public @Nullable ItemStack getBoots() { - return getInventory().getOwnerHandle().getBukkitEntity().getInventory().getBoots(); + return getInventory().getOwner().getInventory().getBoots(); } @Override public void setBoots(@Nullable ItemStack boots) { - getInventory().getOwnerHandle().getBukkitEntity().getInventory().setBoots(boots); + getInventory().getOwner().getInventory().setBoots(boots); } @Override public ItemStack getItemInMainHand() { - Inventory internal = getInventory().getOwnerHandle().getInventory(); - return CraftItemStack.asCraftMirror(internal.getSelectedItem()); + return getInventory().getOwner().getInventory().getItemInMainHand(); } @Override public void setItemInMainHand(@Nullable ItemStack item) { - Inventory internal = getInventory().getOwnerHandle().getInventory(); - internal.setSelectedItem(CraftItemStack.asNMSCopy(item)); + getInventory().getOwner().getInventory().setItemInMainHand(item); } @Override public ItemStack getItemInOffHand() { - return getInventory().getOwnerHandle().getBukkitEntity().getInventory().getItemInOffHand(); + return getInventory().getOwner().getInventory().getItemInOffHand(); } @Override public void setItemInOffHand(@Nullable ItemStack item) { - getInventory().getOwnerHandle().getBukkitEntity().getInventory().setItemInOffHand(item); + getInventory().getOwner().getInventory().setItemInOffHand(item); } @SuppressWarnings("InlineMeSuggester") @@ -211,12 +506,28 @@ public void setHeldItemSlot(int slot) { @Override public @Nullable ItemStack getItem(org.bukkit.inventory.EquipmentSlot slot) { - return getInventory().getOwnerHandle().getBukkitEntity().getInventory().getItem(slot); + return switch (slot) { + case HAND -> getItemInMainHand(); + case OFF_HAND -> getItemInOffHand(); + case FEET -> getBoots(); + case LEGS -> getLeggings(); + case CHEST -> getChestplate(); + case HEAD -> getHelmet(); + default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot); + }; } @Override public void setItem(org.bukkit.inventory.EquipmentSlot slot, @Nullable ItemStack item) { - getInventory().getOwnerHandle().getBukkitEntity().getInventory().setItem(slot, item); + switch (slot) { + case HAND -> setItemInMainHand(item); + case OFF_HAND -> setItemInOffHand(item); + case FEET -> setBoots(item); + case LEGS -> setLeggings(item); + case CHEST -> setChestplate(item); + case HEAD -> setHelmet(item); + default -> throw new IllegalArgumentException("Unsupported EquipmentSlot " + slot); + } } } diff --git a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java index 02ef39d5..bdc62c6a 100644 --- a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java +++ b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java @@ -1,10 +1,15 @@ package com.github.jikoo.openinv.internal.spigot26_3.container.bukkit; import com.github.jikoo.openinv.internal.spigot26_3.container.OpenInventory; +import com.google.common.base.Preconditions; +import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.HashMap; + @NullMarked public class OpenPlayerInventorySelf extends OpenPlayerInventory { @@ -15,14 +20,140 @@ public OpenPlayerInventorySelf(OpenInventory inventory, int offset) { this.offset = offset; } + @Override + public int getSize() { + return super.getSize() - offset; + } + @Override public @Nullable ItemStack getItem(int index) { return super.getItem(offset + index); } + @Override + public @Nullable ItemStack[] getStorageContents() { + return new ItemStack[0]; + } + + @Override + public void setStorageContents(@Nullable ItemStack[] items) throws IllegalArgumentException { + Preconditions.checkArgument(items.length == 0, "items.length must be <= 0"); + } + + @Override + public @Nullable ItemStack[] getContents() { + @Nullable ItemStack[] contents = new ItemStack[getSize()]; + for (int i = 0; i < contents.length; ++i) { + contents[i] = getItem(i); + } + return contents; + } + + @Override + public void setContents(@Nullable ItemStack[] items) { + int size = getSize(); + Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size); + + for (int index = 0; index < size; ++index) { + ItemStack itemStack = items[index]; + if (itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() <= 0) { + continue; + } + validateSet(index); + } + + for (int index = 0; index < size; ++index) { + if (index < items.length) { + inventory.setItem(offset + index, CraftItemStack.asNMSCopy(items[index])); + } else { + inventory.setItem(offset + index, net.minecraft.world.item.ItemStack.EMPTY); + } + } + } + @Override public void setItem(int index, @Nullable ItemStack item) { super.setItem(offset + index, item); } + @Override + public boolean contains(Material material) { + // Only checks the storage contents. + return false; + } + + @Override + public boolean contains(ItemStack item) { + return false; + } + + @Override + public boolean contains(Material material, int amount) { + return false; + } + + @Override + public boolean contains(ItemStack item, int amount) { + return false; + } + + @Override + public boolean containsAtLeast(ItemStack item, int amount) { + return false; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(Material material) { + // No storage contents, no results. + return new HashMap<>(); + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap all(@Nullable ItemStack item) { + return new HashMap<>(); + } + + @Override + public int first(Material material) { + return -1; + } + + @Override + public int first(ItemStack item) { + return -1; + } + + @Override + public int firstEmpty() { + return -1; + } + + @SuppressWarnings("NonApiType") + @Override + public HashMap addItem(ItemStack... items) { + HashMap map = new HashMap<>(); + for (int index = 0; index < items.length; ++index) { + map.put(index, items[index]); + } + return map; + } + + @Override + public HashMap removeItem(ItemStack... items) { + return addItem(items); + } + + @Override + public void remove(Material material) { + Preconditions.checkNotNull(material, "Material cannot be null"); + Preconditions.checkArgument(material.isItem(), "Material must be an item"); + } + + @Override + public void remove(ItemStack itemStack) { + // Sure did. + } + } From 8b486e583625abc878fcbf243519c4a14ea7b59c Mon Sep 17 00:00:00 2001 From: Jikoo Date: Fri, 23 Oct 2026 20:37:11 -0400 Subject: [PATCH 7/7] Update internal API dependency --- .../paper26_3/container/bukkit/OpenPlayerInventorySelf.java | 1 + internal/spigot26_3/build.gradle.kts | 6 ++++-- .../container/bukkit/OpenPlayerInventorySelf.java | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java index d16660ca..70e05b95 100644 --- a/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java +++ b/internal/paper26_3/src/main/java/com/lishid/openinv/internal/paper26_3/container/bukkit/OpenPlayerInventorySelf.java @@ -149,6 +149,7 @@ public HashMap addItem(ItemStack... items) { return map; } + @SuppressWarnings("NonApiType") @Override public HashMap removeItem(ItemStack... items) { return addItem(items); diff --git a/internal/spigot26_3/build.gradle.kts b/internal/spigot26_3/build.gradle.kts index d7721ce4..763925d0 100644 --- a/internal/spigot26_3/build.gradle.kts +++ b/internal/spigot26_3/build.gradle.kts @@ -20,8 +20,10 @@ tasks.withType().configureEach { apply() dependencies { - compileOnly(libs.spigotapi) - extensions.getByType(SpigotDependencyExtension::class.java).version = "26.3-R0.1-SNAPSHOT" + val spigotVersion = "26.3-R0.1-SNAPSHOT" + extensions.getByType(SpigotDependencyExtension::class.java).version = spigotVersion + val spigotApi = libs.spigotapi.get() + compileOnly("${spigotApi.group}:${spigotApi.name}:$spigotVersion") compileOnly("com.mojang:logging:1.7.12") compileOnly("com.mojang:brigadier:1.3.11") compileOnly("com.mojang:datafixerupper:10.0.21") diff --git a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java index bdc62c6a..fb79acd3 100644 --- a/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java +++ b/internal/spigot26_3/src/main/java/com/github/jikoo/openinv/internal/spigot26_3/container/bukkit/OpenPlayerInventorySelf.java @@ -140,6 +140,7 @@ public HashMap addItem(ItemStack... items) { return map; } + @SuppressWarnings("NonApiType") @Override public HashMap removeItem(ItemStack... items) { return addItem(items);