From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 14 May 2024 11:57:43 -0700
Subject: [PATCH] Proxy ItemStack to CraftItemStack


diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -0,0 +0,0 @@ import org.bukkit.material.MaterialData;
 @DelegateDeserialization(ItemStack.class)
 public final class CraftItemStack extends ItemStack {
 
-    // Paper start - MC Utils
-    public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) {
-        if (bukkit instanceof CraftItemStack craftItemStack) {
-            return craftItemStack.handle != null ? craftItemStack.handle : net.minecraft.world.item.ItemStack.EMPTY;
+    // Paper start - delegate api-ItemStack to CraftItemStack
+    private static final java.lang.invoke.VarHandle API_ITEM_STACK_CRAFT_DELEGATE_FIELD;
+    static {
+        try {
+            API_ITEM_STACK_CRAFT_DELEGATE_FIELD = java.lang.invoke.MethodHandles.privateLookupIn(
+                ItemStack.class,
+                java.lang.invoke.MethodHandles.lookup()
+            ).findVarHandle(ItemStack.class, "craftDelegate", ItemStack.class);
+        } catch (final IllegalAccessException | NoSuchFieldException exception) {
+            throw new RuntimeException(exception);
+        }
+    }
+
+    private static CraftItemStack getCraftStack(final ItemStack bukkit) {
+        if (bukkit instanceof final CraftItemStack craftItemStack) {
+            return craftItemStack;
         } else {
-            return asNMSCopy(bukkit);
+            return  (CraftItemStack) API_ITEM_STACK_CRAFT_DELEGATE_FIELD.get(bukkit);
         }
     }
 
+    @Override
+    public int hashCode() {
+        if (this.handle == null || this.handle.isEmpty()) {
+            return net.minecraft.world.item.ItemStack.EMPTY.hashCode();
+        } else {
+            int hash = net.minecraft.world.item.ItemStack.hashItemAndComponents(this.handle);
+            hash = hash * 31 + this.handle.getCount();
+            return hash;
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (!(obj instanceof final org.bukkit.inventory.ItemStack bukkit)) return false;
+        final CraftItemStack craftStack = getCraftStack(bukkit);
+        if (this.handle == craftStack.handle) return true;
+        else if (this.handle == null || craftStack.handle == null) return false;
+        else if (this.handle.isEmpty() && craftStack.handle.isEmpty()) return true;
+        else return net.minecraft.world.item.ItemStack.matches(this.handle, craftStack.handle);
+    }
+    // Paper end
+
+    // Paper start - MC Utils
+    public static net.minecraft.world.item.ItemStack unwrap(ItemStack bukkit) {
+        // Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack
+        final CraftItemStack craftItemStack = getCraftStack(bukkit);
+        return craftItemStack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : craftItemStack.handle;
+        // Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack
+    }
+
     public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) {
         return old == newInstance ? unwrap(old) : asNMSCopy(newInstance);
     }
@@ -0,0 +0,0 @@ public final class CraftItemStack extends ItemStack {
     // Paper end - override isEmpty to use vanilla's impl
 
     public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
-        if (original instanceof CraftItemStack) {
-            CraftItemStack stack = (CraftItemStack) original;
-            return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy();
-        }
-        if (original == null || original.isEmpty()) { // Paper - override isEmpty to use vanilla's impl; use isEmpty
+        // Paper start - re-implement after delegating all api ItemStack calls to CraftItemStack
+        if (original == null || original.isEmpty()) {
             return net.minecraft.world.item.ItemStack.EMPTY;
         }
-
-        Item item = CraftItemType.bukkitToMinecraft(original.getType());
-
-        if (item == null) {
-            return net.minecraft.world.item.ItemStack.EMPTY;
-        }
-
-        net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(item, original.getAmount());
-        if (original.hasItemMeta()) {
-            CraftItemStack.setItemMeta(stack, original.getItemMeta());
-        }
-        return stack;
+        final CraftItemStack stack = getCraftStack(original);
+        return stack.handle == null ? net.minecraft.world.item.ItemStack.EMPTY : stack.handle.copy();
+        // Paper end - re-implement after delegating all api ItemStack calls to CraftItemStack
     }
 
     // Paper start
@@ -0,0 +0,0 @@ public final class CraftItemStack extends ItemStack {
      * Copies the NMS stack to return as a strictly-Bukkit stack
      */
     public static ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack original) {
-        if (original.isEmpty()) {
-            return new ItemStack(Material.AIR);
-        }
-        ItemStack stack = new ItemStack(CraftItemType.minecraftToBukkit(original.getItem()), original.getCount());
-        if (CraftItemStack.hasItemMeta(original)) {
-            stack.setItemMeta(CraftItemStack.getItemMeta(original));
-        }
-        return stack;
+        // Paper start - no such thing as a "strictly-Bukkit stack" anymore
+        // we copy the stack since it should be a complete copy not a mirror
+        return asCraftMirror(original.copy());
+        // Paper end
     }
 
     public static CraftItemStack asCraftMirror(net.minecraft.world.item.ItemStack original) {
@@ -0,0 +0,0 @@ public final class CraftItemStack extends ItemStack {
 
     @Override
     public CraftItemStack clone() {
-        CraftItemStack itemStack = (CraftItemStack) super.clone();
-        if (this.handle != null) {
-            itemStack.handle = this.handle.copy();
-        }
-        return itemStack;
+        return new org.bukkit.craftbukkit.inventory.CraftItemStack(this.handle != null ? this.handle.copy() : null); // Paper
     }
 
     @Override
@@ -0,0 +0,0 @@ public final class CraftItemStack extends ItemStack {
         if (stack == this) {
             return true;
         }
-        if (!(stack instanceof CraftItemStack)) {
-            return stack.getClass() == ItemStack.class && stack.isSimilar(this);
-        }
-
-        CraftItemStack that = (CraftItemStack) stack;
+        final CraftItemStack that = getCraftStack(stack); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack
         if (this.handle == that.handle) {
             return true;
         }
         if (this.handle == null || that.handle == null) {
             return false;
         }
-        Material comparisonType = CraftLegacy.fromLegacy(that.getType()); // This may be called from legacy item stacks, try to get the right material
-        if (!(comparisonType == this.getType() && this.getDurability() == that.getDurability())) {
-            return false;
-        }
-        return this.hasItemMeta() ? that.hasItemMeta() && this.handle.getComponents().equals(that.handle.getComponents()) : !that.hasItemMeta();
+        return net.minecraft.world.item.ItemStack.isSameItemSameComponents(this.handle, that.handle); // Paper - re-implement after delegating all api ItemStack calls to CraftItemStack
     }
 
     @Override
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
@@ -0,0 +0,0 @@ public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Han
     @NotNull
     @Override
     public ItemStack createItemStack(final int amount, @Nullable final Consumer<? super M> metaConfigurator) {
-        final ItemStack itemStack = new ItemStack(this.asMaterial(), amount);
+        // Paper start - re-implement to return CraftItemStack
+        final net.minecraft.world.item.ItemStack stack = new net.minecraft.world.item.ItemStack(this.item, amount);
+        final CraftItemStack mirror = CraftItemStack.asCraftMirror(stack);
         if (metaConfigurator != null) {
-            final ItemMeta itemMeta = itemStack.getItemMeta();
-            metaConfigurator.accept((M) itemMeta);
-            itemStack.setItemMeta(itemMeta);
+            mirror.editMeta(this.getItemMetaClass(), metaConfigurator);
         }
-        return itemStack;
+        return mirror;
+        // Paper start - reimplement to return CraftItemStack
     }
 
     @Override
diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
+++ b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
@@ -0,0 +0,0 @@ public class MaterialRerouting {
         return itemStack.withType(material);
     }
     // Paper end - register paper API specific material consumers in rerouting
+
+    // Paper start - methods added post 1.13, no-op
+    @RerouteStatic("org/bukkit/inventory/ItemStack")
+    public static ItemStack of(final Material material) {
+        return ItemStack.of(material);
+    }
+
+    @RerouteStatic("org/bukkit/inventory/ItemStack")
+    public static ItemStack of(final Material material, final int amount) {
+        return ItemStack.of(material, amount);
+    }
+    // Paper end
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues {
     }
     // Paper end - hack to get tags for non server-backed registries
 
+    // Paper start - proxy ItemStack
+    @Override
+    public org.bukkit.inventory.ItemStack createEmptyStack() {
+        return CraftItemStack.asCraftMirror(null);
+    }
+    // Paper end - proxy ItemStack
+
     /**
      * This helper class represents the different NBT Tags.
      * <p>
diff --git a/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java b/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/configuration/ConfigurationSectionTest.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.configuration;
+
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.support.AbstractTestingBase;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public abstract class ConfigurationSectionTest extends AbstractTestingBase {
+    public abstract ConfigurationSection getConfigurationSection();
+
+    @Test
+    public void testGetItemStack_String() {
+        ConfigurationSection section = getConfigurationSection();
+        String key = "exists";
+        ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
+
+        section.set(key, value);
+
+        assertEquals(value, section.getItemStack(key));
+        assertNull(section.getString("doesntExist"));
+    }
+
+    @Test
+    public void testGetItemStack_String_ItemStack() {
+        ConfigurationSection section = getConfigurationSection();
+        String key = "exists";
+        ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
+        ItemStack def = new ItemStack(Material.STONE, 1);
+
+        section.set(key, value);
+
+        assertEquals(value, section.getItemStack(key, def));
+        assertEquals(def, section.getItemStack("doesntExist", def));
+    }
+
+    @Test
+    public void testIsItemStack() {
+        ConfigurationSection section = getConfigurationSection();
+        String key = "exists";
+        ItemStack value = new ItemStack(Material.ACACIA_WOOD, 50);
+
+        section.set(key, value);
+
+        assertTrue(section.isItemStack(key));
+        assertFalse(section.isItemStack("doesntExist"));
+    }
+}
diff --git a/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java b/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/test/java/io/papermc/paper/configuration/MemorySectionTest.java
@@ -0,0 +0,0 @@
+package io.papermc.paper.configuration;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.MemoryConfiguration;
+
+public class MemorySectionTest extends ConfigurationSectionTest {
+    @Override
+    public ConfigurationSection getConfigurationSection() {
+        return new MemoryConfiguration().createSection("section");
+    }
+}
