Compare commits

..
Author SHA1 Message Date
Jikoo 8b486e5836 Update internal API dependency 2026-10-23 20:37:11 -04:00
Jikoo 53414759c3 Copypasta sauce for my spighetti 2026-10-23 20:05:17 -04:00
Jikoo 609736d94c Also reimplement for self inventory
Probably why there are so many Bukkit wrappers in CraftInventory, more reusable without modifying Container.
2026-10-23 19:54:29 -04:00
Jikoo 33d738afc7 Reimplement required removals
#removeItemAnySlot uses full contents, so slots will line up there.
2026-10-23 17:52:30 -04:00
Jikoo 394908a959 Reimplement addItem 2026-10-18 20:09:17 -04:00
Jikoo 6829be335e Reimplement more API for Paper
Corrects slots for all, first, and firstEmpty.
Still need to reimplement addItem and removeItem, then port to Spigot.
2026-10-18 11:36:37 -04:00
Jikoo 09e218831b 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.
2026-10-17 18:59:17 -04:00
12 changed files with 992 additions and 39 deletions

No files matched your search

+2 -2
View File
@@ -3,10 +3,10 @@ spigotapi = "1.21.3-R0.1-SNAPSHOT"
planarwrappers = "3.3.0"
jspecify = "1.0.1"
annotations = "26.1.0"
paperweight = "2.0.0-beta.24"
paperweight = "2.0.0-beta.21"
shadow = "9.6.1"
folia-scheduler-wrapper = "v0.0.3"
errorprone-core = "2.51.0"
errorprone-core = "2.50.0"
errorprone-gradle = "5.1.1"
slf4j = "2.0.20"
sqlite-jdbc = "3.49.1.0"
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
@@ -59,14 +59,14 @@ public class OpenEnderChestMenu extends OpenSyncMenu<OpenEnderChest> {
}
@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);
}
}
@@ -26,14 +26,14 @@ public class OpenInventoryMenu extends BaseOpenInventoryMenu {
}
@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);
}
}
@@ -187,7 +187,7 @@ public abstract class BaseOpenInventory implements Container, InternalOwned<Serv
@Override
public org.bukkit.inventory.Inventory getBukkitInventory() {
if (bukkitEntity == null) {
bukkitEntity = new OpenPlayerInventory(this);
bukkitEntity = new OpenPlayerInventory(factory, this);
}
return bukkitEntity;
}
@@ -1,12 +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.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 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,13 +22,22 @@ import org.bukkit.inventory.PlayerInventory;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@NullMarked
public class OpenPlayerInventory extends CraftInventory implements PlayerInventory {
public OpenPlayerInventory(BaseOpenInventory inventory) {
protected final InventoryFactory<ServerPlayer, net.minecraft.world.item.ItemStack, Container, Slot, EquipmentSlot> factory;
public OpenPlayerInventory(
InventoryFactory<ServerPlayer, net.minecraft.world.item.ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory
) {
super(inventory);
this.factory = factory;
}
@Override
@@ -29,16 +46,34 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
public @Nullable ItemStack[] 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 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) {
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,8 +83,21 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
}
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 tried to put an item in a display slot. That would delete it!");
}
}
@Override
public ItemStack[] getStorageContents() {
public void setItem(int index, @Nullable ItemStack item) {
validateSet(index);
super.setItem(index, item);
}
@Override
public @Nullable ItemStack[] getStorageContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getNonEquipmentItems());
}
@@ -63,6 +111,294 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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<Integer, ItemStack> all(@Nullable ItemStack item) {
HashMap<Integer, ItemStack> 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<net.minecraft.world.item.ItemStack, net.minecraft.world.item.ItemStack> predicate;
ItemMatcher(BiPredicate<net.minecraft.world.item.ItemStack, net.minecraft.world.item.ItemStack> predicate) {
this.predicate = predicate;
}
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
Preconditions.checkNotNull(items, "items cannot be null");
HashMap<Integer, ItemStack> leftover = new HashMap<>();
BaseOpenInventory 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<Integer, ItemStack> 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.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<Integer, ItemStack> removeItem(ItemStack... items) {
Preconditions.checkNotNull(items, "items cannot be null");
HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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() {
return InventoryType.PLAYER;
@@ -74,7 +410,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack[] getArmorContents() {
public @Nullable ItemStack[] getArmorContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getArmorContents());
}
@@ -91,7 +427,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack[] getExtraContents() {
public @Nullable ItemStack[] getExtraContents() {
return asCraftMirror(List.of(getInventory().getOwnerHandle().getInventory().equipment.get(EquipmentSlot.OFFHAND)));
}
@@ -1,28 +1,169 @@
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 {
private final int offset;
public OpenPlayerInventorySelf(BaseOpenInventory inventory, int offset) {
super(inventory);
public OpenPlayerInventorySelf(
InventoryFactory<ServerPlayer, net.minecraft.world.item.ItemStack, Container, Slot, EquipmentSlot> factory,
BaseOpenInventory inventory,
int offset
) {
super(factory, inventory);
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<Integer, ItemStack> all(Material material) {
// No storage contents, no results.
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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<Integer, ItemStack> addItem(ItemStack... items) {
HashMap<Integer, ItemStack> map = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
map.put(index, items[index]);
}
return map;
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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.
}
}
@@ -112,7 +112,7 @@ public class BaseOpenInventoryMenu extends OpenSyncMenu<BaseOpenInventory> {
if (viewOnly) {
bukkitInventory = new OpenDummyPlayerInventory(container);
} else if (ownContainer) {
bukkitInventory = new OpenPlayerInventorySelf(container, offset);
bukkitInventory = new OpenPlayerInventorySelf(factory, container, offset);
} else {
bukkitInventory = container.getBukkitInventory();
}
+4 -2
View File
@@ -20,8 +20,10 @@ tasks.withType<JavaCompile>().configureEach {
apply<SpigotSetup>()
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")
@@ -3,9 +3,14 @@ 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 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;
@@ -13,6 +18,10 @@ import org.bukkit.inventory.PlayerInventory;
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 {
@@ -26,16 +35,34 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public ItemStack[] getContents() {
return asCraftMirror(getInventory().getOwnerHandle().getInventory().getContents());
public @Nullable ItemStack[] 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) {
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) {
internal.setItem(index, CraftItemStack.asNMSCopy(items[index]));
@@ -45,13 +72,26 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
}
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 tried to put an item in a display slot. That would delete it!");
}
}
@Override
public ItemStack[] getStorageContents() {
public void setItem(int index, @Nullable ItemStack item) {
validateSet(index);
super.setItem(index, item);
}
@Override
public @Nullable 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<net.minecraft.world.item.ItemStack> list = getInventory().getOwnerHandle().getInventory().getNonEquipmentItems();
int size = list.size();
Preconditions.checkArgument(items.length <= size, "items.length must be <= %s", size);
@@ -60,6 +100,294 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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<Integer, ItemStack> all(@Nullable ItemStack item) {
HashMap<Integer, ItemStack> 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<net.minecraft.world.item.ItemStack, net.minecraft.world.item.ItemStack> predicate;
ItemMatcher(BiPredicate<net.minecraft.world.item.ItemStack, net.minecraft.world.item.ItemStack> predicate) {
this.predicate = predicate;
}
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> addItem(ItemStack... items) {
Preconditions.checkNotNull(items, "items cannot be null");
HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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<Integer, ItemStack> removeItem(ItemStack... items) {
Preconditions.checkNotNull(items, "items cannot be null");
HashMap<Integer, ItemStack> 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<Integer, ItemStack> 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;
@@ -76,7 +404,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public void setArmorContents(ItemStack[] items) {
public void setArmorContents(@Nullable ItemStack[] items) {
getInventory().getOwnerHandle().getBukkitEntity().getInventory().setArmorContents(items);
}
@@ -86,7 +414,7 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
}
@Override
public void setExtraContents(ItemStack[] items) {
public void setExtraContents(@Nullable ItemStack[] items) {
getInventory().getOwnerHandle().getBukkitEntity().getInventory().setExtraContents(items);
}
@@ -117,39 +445,37 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
@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")
@@ -180,12 +506,28 @@ public class OpenPlayerInventory extends CraftInventory implements PlayerInvento
@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);
}
}
}
@@ -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,141 @@ public class OpenPlayerInventorySelf extends OpenPlayerInventory {
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<Integer, ItemStack> all(Material material) {
// No storage contents, no results.
return new HashMap<>();
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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<Integer, ItemStack> addItem(ItemStack... items) {
HashMap<Integer, ItemStack> map = new HashMap<>();
for (int index = 0; index < items.length; ++index) {
map.put(index, items[index]);
}
return map;
}
@SuppressWarnings("NonApiType")
@Override
public HashMap<Integer, ItemStack> 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.
}
}