From 14b0751f02e82c59c3508ced3130f27a17df3b97 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Fri, 12 Apr 2024 17:16:22 -0400
Subject: [PATCH] Deprecate ItemStack#setType & add ItemStack#withType (#10290)

---
 patches/api/Deprecate-ItemStack-setType.patch | 54 +++++++++++++++++++
 ...for-InventoryBlockStartEvent-subclas.patch |  4 +-
 .../api/Fix-Spigot-annotation-mistakes.patch  | 24 +++++++++
 patches/api/Item-Mutation-Fixes.patch         | 50 +++++++++++++++++
 patches/api/PlayerElytraBoostEvent.patch      | 16 +++++-
 .../server/Deprecate-ItemStack-setType.patch  | 30 +++++++++++
 patches/server/Item-Mutation-Fixes.patch      | 49 +++++++++++++++++
 patches/server/MC-Utils.patch                 |  4 ++
 patches/server/PlayerElytraBoostEvent.patch   |  2 +-
 9 files changed, 229 insertions(+), 4 deletions(-)
 create mode 100644 patches/api/Deprecate-ItemStack-setType.patch
 create mode 100644 patches/api/Item-Mutation-Fixes.patch
 create mode 100644 patches/server/Deprecate-ItemStack-setType.patch
 create mode 100644 patches/server/Item-Mutation-Fixes.patch

diff --git a/patches/api/Deprecate-ItemStack-setType.patch b/patches/api/Deprecate-ItemStack-setType.patch
new file mode 100644
index 0000000000..f6f1cd5b9f
--- /dev/null
+++ b/patches/api/Deprecate-ItemStack-setType.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
+Date: Thu, 29 Feb 2024 17:54:26 -0500
+Subject: [PATCH] Deprecate ItemStack#setType
+
+
+diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/inventory/ItemStack.java
++++ b/src/main/java/org/bukkit/inventory/ItemStack.java
+@@ -0,0 +0,0 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
+      * {@link Material#isItem()} returns false.</b>
+      *
+      * @param type New type to set the items in this stack to
++     * @deprecated <b>Setting the material type of ItemStacks is no longer supported.</b>
++     * <p>
++     * This method is deprecated due to potential illegal behavior that may occur
++     * during the context of which this ItemStack is being used, allowing for certain item validation to be bypassed.
++     * It is recommended to instead create a new ItemStack object with the desired
++     * Material type, and if possible, set it in the appropriate context.
++     *
++     * Using this method in ItemStacks passed in events will result in undefined behavior.
++     * @see ItemStack#withType(Material)
+      */
+     @Utility
++    @Deprecated // Paper
+     public void setType(@NotNull Material type) {
+         Preconditions.checkArgument(type != null, "Material cannot be null");
+         this.type = type;
+@@ -0,0 +0,0 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
+             this.data = null;
+         }
+     }
++    // Paper start
++    /**
++     * Creates a new ItemStack with the specified Material type, where the item count and item meta is preserved.
++     *
++     * @param type The Material type of the new ItemStack.
++     * @return A new ItemStack instance with the specified Material type.
++     */
++    @NotNull
++    @org.jetbrains.annotations.Contract(value = "_ -> new", pure = true)
++    public ItemStack withType(@NotNull Material type) {
++        ItemStack itemStack = new ItemStack(type, this.amount);
++        if (this.hasItemMeta()) {
++            itemStack.setItemMeta(this.getItemMeta());
++        }
++
++        return itemStack;
++    }
++    // Paper end
+ 
+     /**
+      * Gets the amount of items in this stack
diff --git a/patches/api/Fix-HandlerList-for-InventoryBlockStartEvent-subclas.patch b/patches/api/Fix-HandlerList-for-InventoryBlockStartEvent-subclas.patch
index e18527824d..7e9bd53440 100644
--- a/patches/api/Fix-HandlerList-for-InventoryBlockStartEvent-subclas.patch
+++ b/patches/api/Fix-HandlerList-for-InventoryBlockStartEvent-subclas.patch
@@ -9,7 +9,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/org/bukkit/event/block/BrewingStartEvent.java
 +++ b/src/main/java/org/bukkit/event/block/BrewingStartEvent.java
 @@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
-  */
+ @org.jetbrains.annotations.ApiStatus.Experimental // Paper
  public class BrewingStartEvent extends InventoryBlockStartEvent {
  
 -    private static final HandlerList handlers = new HandlerList();
@@ -38,7 +38,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 --- a/src/main/java/org/bukkit/event/block/CampfireStartEvent.java
 +++ b/src/main/java/org/bukkit/event/block/CampfireStartEvent.java
 @@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
-  */
+ @org.jetbrains.annotations.ApiStatus.Experimental // Paper
  public class CampfireStartEvent extends InventoryBlockStartEvent {
  
 -    private static final HandlerList handlers = new HandlerList();
diff --git a/patches/api/Fix-Spigot-annotation-mistakes.patch b/patches/api/Fix-Spigot-annotation-mistakes.patch
index 3aef8679bc..dd625d8162 100644
--- a/patches/api/Fix-Spigot-annotation-mistakes.patch
+++ b/patches/api/Fix-Spigot-annotation-mistakes.patch
@@ -982,6 +982,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      public static final MemoryKey<Location> SNIFFER_EXPLORED_POSITIONS = new MemoryKey<>(NamespacedKey.minecraft("sniffer_explored_positions"), Location.class);
  
      /**
+diff --git a/src/main/java/org/bukkit/event/block/BrewingStartEvent.java b/src/main/java/org/bukkit/event/block/BrewingStartEvent.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/event/block/BrewingStartEvent.java
++++ b/src/main/java/org/bukkit/event/block/BrewingStartEvent.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
+ /**
+  * Called when a brewing stand starts to brew.
+  */
++@org.jetbrains.annotations.ApiStatus.Experimental // Paper
+ public class BrewingStartEvent extends InventoryBlockStartEvent {
+ 
+     private static final HandlerList handlers = new HandlerList();
+diff --git a/src/main/java/org/bukkit/event/block/CampfireStartEvent.java b/src/main/java/org/bukkit/event/block/CampfireStartEvent.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/event/block/CampfireStartEvent.java
++++ b/src/main/java/org/bukkit/event/block/CampfireStartEvent.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
+ /**
+  * Called when a Campfire starts to cook.
+  */
++@org.jetbrains.annotations.ApiStatus.Experimental // Paper
+ public class CampfireStartEvent extends InventoryBlockStartEvent {
+ 
+     private static final HandlerList handlers = new HandlerList();
 diff --git a/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java b/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/event/enchantment/PrepareItemEnchantEvent.java
diff --git a/patches/api/Item-Mutation-Fixes.patch b/patches/api/Item-Mutation-Fixes.patch
new file mode 100644
index 0000000000..8122eecdec
--- /dev/null
+++ b/patches/api/Item-Mutation-Fixes.patch
@@ -0,0 +1,50 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
+Date: Wed, 20 Mar 2024 20:42:31 -0400
+Subject: [PATCH] Item Mutation Fixes
+
+
+diff --git a/src/main/java/org/bukkit/event/block/InventoryBlockStartEvent.java b/src/main/java/org/bukkit/event/block/InventoryBlockStartEvent.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/event/block/InventoryBlockStartEvent.java
++++ b/src/main/java/org/bukkit/event/block/InventoryBlockStartEvent.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
+ public class InventoryBlockStartEvent extends BlockEvent {
+ 
+     private static final HandlerList handlers = new HandlerList();
+-    private final ItemStack source;
++    protected ItemStack source; // Paper
+ 
+     public InventoryBlockStartEvent(@NotNull final Block block, @NotNull ItemStack source) {
+         super(block);
+diff --git a/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java b/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java
++++ b/src/main/java/org/bukkit/event/enchantment/EnchantItemEvent.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull;
+ public class EnchantItemEvent extends InventoryEvent implements Cancellable {
+     private static final HandlerList handlers = new HandlerList();
+     private final Block table;
+-    private final ItemStack item;
++    private ItemStack item; // Paper
+     private int level;
+     private boolean cancelled;
+     private final Map<Enchantment, Integer> enchants;
+@@ -0,0 +0,0 @@ public class EnchantItemEvent extends InventoryEvent implements Cancellable {
+         return item;
+     }
+ 
++    // Paper start
++    /**
++     * Sets the item to be enchanted
++     *
++     * @param item item
++     */
++    public void setItem(@NotNull final ItemStack item) {
++        this.item = item;
++    }
++    // Paper end
++
+     /**
+      * Gets the cost (minimum level) which is displayed as a number on the right
+      * hand side of the enchantment offer.
diff --git a/patches/api/PlayerElytraBoostEvent.patch b/patches/api/PlayerElytraBoostEvent.patch
index b947bdf0a4..ae001dfca5 100644
--- a/patches/api/PlayerElytraBoostEvent.patch
+++ b/patches/api/PlayerElytraBoostEvent.patch
@@ -17,6 +17,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +import org.bukkit.event.Cancellable;
 +import org.bukkit.event.HandlerList;
 +import org.bukkit.event.player.PlayerEvent;
++import org.bukkit.inventory.EquipmentSlot;
 +import org.bukkit.inventory.ItemStack;
 +import org.jetbrains.annotations.ApiStatus;
 +import org.jetbrains.annotations.NotNull;
@@ -31,14 +32,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    @NotNull private final ItemStack itemStack;
 +    @NotNull private final Firework firework;
 +    private boolean consume = true;
++    @NotNull
++    private final EquipmentSlot hand;
 +
 +    private boolean cancelled;
 +
 +    @ApiStatus.Internal
-+    public PlayerElytraBoostEvent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull Firework firework) {
++    public PlayerElytraBoostEvent(@NotNull Player player, @NotNull ItemStack itemStack, @NotNull Firework firework, @NotNull EquipmentSlot hand) {
 +        super(player);
 +        this.itemStack = itemStack;
 +        this.firework = firework;
++        this.hand = hand;
 +    }
 +
 +    /**
@@ -79,6 +83,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        this.consume = consume;
 +    }
 +
++    /**
++     * Gets the hand holding the firework used for boosting this player.
++     *
++     * @return interaction hand
++     */
++    @NotNull
++    public EquipmentSlot getHand() {
++        return this.hand;
++    }
++
 +    @Override
 +    public boolean isCancelled() {
 +        return this.cancelled;
diff --git a/patches/server/Deprecate-ItemStack-setType.patch b/patches/server/Deprecate-ItemStack-setType.patch
new file mode 100644
index 0000000000..6e35974967
--- /dev/null
+++ b/patches/server/Deprecate-ItemStack-setType.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
+Date: Tue, 26 Mar 2024 21:42:23 -0400
+Subject: [PATCH] Deprecate ItemStack#setType
+
+
+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 @@ public final class CraftItemStack extends ItemStack {
+     static boolean hasItemMeta(net.minecraft.world.item.ItemStack item) {
+         return !(item == null || item.getTag() == null || item.getTag().isEmpty());
+     }
++    // Paper start - with type
++    @Override
++    public ItemStack withType(final Material type) {
++        if (type == Material.AIR) return CraftItemStack.asCraftMirror(null);
++
++        final net.minecraft.world.item.ItemStack copy = new net.minecraft.world.item.ItemStack(
++            CraftItemType.bukkitToMinecraft(type), this.getAmount()
++        );
++        if (this.handle != null && this.handle.getTag() != null) copy.setTag(this.handle.getTag().copy());
++
++        final CraftItemStack mirrored = CraftItemStack.asCraftMirror(copy);
++        mirrored.setItemMeta(mirrored.getItemMeta());
++        return mirrored;
++    }
++    // Paper end
+ }
diff --git a/patches/server/Item-Mutation-Fixes.patch b/patches/server/Item-Mutation-Fixes.patch
new file mode 100644
index 0000000000..6d36480ae4
--- /dev/null
+++ b/patches/server/Item-Mutation-Fixes.patch
@@ -0,0 +1,49 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
+Date: Wed, 20 Mar 2024 20:41:35 -0400
+Subject: [PATCH] Item Mutation Fixes
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
+@@ -0,0 +0,0 @@ public class EnchantmentMenu extends AbstractContainerMenu {
+                 return false;
+             } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) {
+                 this.access.execute((world, blockposition) -> {
+-                    ItemStack itemstack2 = itemstack;
++                    ItemStack itemstack2 = itemstack; // Paper - diff on change
+                     List<EnchantmentInstance> list = this.getEnchantmentList(itemstack, id, this.costs[id]);
+ 
+                     // CraftBukkit start
+@@ -0,0 +0,0 @@ public class EnchantmentMenu extends AbstractContainerMenu {
+                             return;
+                         }
+                         // CraftBukkit end
+-                        boolean flag = itemstack.is(Items.BOOK);
++                        // Paper start
++                        itemstack2 = org.bukkit.craftbukkit.inventory.CraftItemStack.getOrCloneOnMutation(item, event.getItem());
++                        if (itemstack2 != itemstack) {
++                            this.enchantSlots.setItem(0, itemstack2);
++                        }
++                        boolean flag = itemstack2.is(Items.BOOK);
++                        // Paper end
+ 
+                         if (flag) {
++                            CompoundTag nbttagcompound = itemstack2.getTag(); // Paper - move up
+                             itemstack2 = new ItemStack(Items.ENCHANTED_BOOK);
+-                            CompoundTag nbttagcompound = itemstack.getTag();
++                            // Paper - move up
+ 
+                             if (nbttagcompound != null) {
+                                 itemstack2.setTag(nbttagcompound.copy());
+@@ -0,0 +0,0 @@ public class EnchantmentMenu extends AbstractContainerMenu {
+                                     EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue());
+                                     EnchantedBookItem.addEnchantment(itemstack2, weightedrandomenchant);
+                                 } else {
+-                                    item.addUnsafeEnchantment(entry.getKey(), entry.getValue());
++                                    CraftItemStack.asCraftMirror(itemstack2).addUnsafeEnchantment(entry.getKey(), entry.getValue()); // Paper
+                                 }
+                             } catch (IllegalArgumentException e) {
+                                 /* Just swallow invalid enchantments */
diff --git a/patches/server/MC-Utils.patch b/patches/server/MC-Utils.patch
index 6a69de83f5..e089158042 100644
--- a/patches/server/MC-Utils.patch
+++ b/patches/server/MC-Utils.patch
@@ -7907,6 +7907,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +            return asNMSCopy(bukkit);
 +        }
 +    }
++
++    public static net.minecraft.world.item.ItemStack getOrCloneOnMutation(ItemStack old, ItemStack newInstance) {
++        return old == newInstance ? unwrap(old) : asNMSCopy(newInstance);
++    }
 +    // Paper end - MC Utils
 +
      public static net.minecraft.world.item.ItemStack asNMSCopy(ItemStack original) {
diff --git a/patches/server/PlayerElytraBoostEvent.patch b/patches/server/PlayerElytraBoostEvent.patch
index e7396f4455..e9dd2a4715 100644
--- a/patches/server/PlayerElytraBoostEvent.patch
+++ b/patches/server/PlayerElytraBoostEvent.patch
@@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 -                world.addFreshEntity(fireworkRocketEntity);
 -                if (!user.getAbilities().instabuild) {
 +                // Paper start - PlayerElytraBoostEvent
-+                com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity());
++                com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand));
 +                if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) {
 +                    user.awardStat(Stats.ITEM_USED.get(this));
 +                    if (event.shouldConsume() && !user.getAbilities().instabuild) {