From e989331cd4796dd7878f2dba9aae1da062a7a32a Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 22 Feb 2022 15:00:45 -0800
Subject: [PATCH] Fix custom inventory holders (#6199)

---
 build-data/paper.at                           |   4 +
 patches/server/Adventure.patch                |   5 -
 ...store-custom-InventoryHolder-support.patch | 365 ++++++++++++++++--
 3 files changed, 338 insertions(+), 36 deletions(-)

diff --git a/build-data/paper.at b/build-data/paper.at
index 6f731e0677..2e798b98b6 100644
--- a/build-data/paper.at
+++ b/build-data/paper.at
@@ -311,3 +311,7 @@ public-f net.minecraft.world.level.chunk.ChunkGenerator strongholdSeed
 
 # More Sculk Sensor API
 public-f net.minecraft.world.level.gameevent.vibrations.VibrationListener listenerRange
+
+# Fix custom inventory holders
+public-f net.minecraft.world.inventory.AbstractContainerMenu dataSlots
+public-f net.minecraft.world.inventory.AbstractContainerMenu remoteDataSlots
diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch
index 705ec69115..fbcfba54be 100644
--- a/patches/server/Adventure.patch
+++ b/patches/server/Adventure.patch
@@ -3278,11 +3278,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
  
 +    // Paper start
 +    public Inventory createInventory(InventoryHolder holder, InventoryType type, net.kyori.adventure.text.Component title) {
-+        // Paper start
-+        if (holder != null) {
-+            return DEFAULT_CONVERTER.createInventory(holder, type, title);
-+        }
-+        //noinspection ConstantConditions // Paper end
 +        return converterMap.get(type).createInventory(holder, type, title);
 +    }
 +    // Paper end
diff --git a/patches/server/Restore-custom-InventoryHolder-support.patch b/patches/server/Restore-custom-InventoryHolder-support.patch
index abeb3eb978..4b66dbc3f4 100644
--- a/patches/server/Restore-custom-InventoryHolder-support.patch
+++ b/patches/server/Restore-custom-InventoryHolder-support.patch
@@ -1,46 +1,349 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <theboyetronic@gmail.com>
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
 Date: Mon, 5 Nov 2018 04:23:51 +0000
 Subject: [PATCH] Restore custom InventoryHolder support
 
+Co-authored-by: Shane Freeder <theboyetronic@gmail.com>
+
 Upstream removed the ability to consistently use a custom InventoryHolder,
 However, the implementation does not use an InventoryHolder in any form
 outside of custom inventories.
 
-We can take that knowledge and apply some expected behavior, if we're given
-an inventory holder, we should use it and return a custom inventory with the
-holder, otherwise, create an inventory backed by the intended inventory, as
-per upstream behavior.
-
-This provides a "best of both worlds" scenario: plugins with InventoryHolder's
-will always work as intended in the past, those without will create implementation
-based inventories.
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java
-+++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftInventoryCreator.java
-@@ -0,0 +0,0 @@ public final class CraftInventoryCreator {
-     }
- 
-     public Inventory createInventory(InventoryHolder holder, InventoryType type) {
-+        // Paper start
-+        if (holder != null) {
-+            return this.DEFAULT_CONVERTER.createInventory(holder, type);
+diff --git a/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java b/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/inventory/PaperInventoryCustomHolderContainer.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.inventory;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.Component;
++import net.minecraft.world.Container;
++import net.minecraft.world.entity.player.Player;
++import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.InventoryType;
++import org.bukkit.inventory.InventoryHolder;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import java.util.List;
++
++@DefaultQualifier(NonNull.class)
++public final class PaperInventoryCustomHolderContainer implements Container {
++
++    private final InventoryHolder owner;
++    private final Container delegate;
++    private final InventoryType type;
++    private final String title;
++    private final Component adventure$title;
++
++    public PaperInventoryCustomHolderContainer(InventoryHolder owner, Container delegate, InventoryType type) {
++        this.owner = owner;
++        this.delegate = delegate;
++        this.type = type;
++        @Nullable Component adventure$title = null;
++        if (delegate instanceof BaseContainerBlockEntity blockEntity) {
++            adventure$title = blockEntity.getCustomName() != null ? PaperAdventure.asAdventure(blockEntity.getCustomName()) : null;
 +        }
-+        //noinspection ConstantConditions // Paper end
-         return this.converterMap.get(type).createInventory(holder, type);
++        if (adventure$title == null) {
++            adventure$title = type.defaultTitle();
++        }
++        this.adventure$title = adventure$title;
++        this.title = PaperAdventure.LEGACY_SECTION_UXRC.serialize(this.adventure$title);
++    }
++
++    public Component title() {
++        return this.adventure$title;
++    }
++
++    public String getTitle() {
++        return this.title;
++    }
++
++    public InventoryType getType() {
++        return this.type;
++    }
++
++    @Override
++    public int getContainerSize() {
++        return this.delegate.getContainerSize();
++    }
++
++    @Override
++    public boolean isEmpty() {
++        return this.delegate.isEmpty();
++    }
++
++    @Override
++    public ItemStack getItem(int slot) {
++        return this.delegate.getItem(slot);
++    }
++
++    @Override
++    public ItemStack removeItem(int slot, int amount) {
++        return this.delegate.removeItem(slot, amount);
++    }
++
++    @Override
++    public ItemStack removeItemNoUpdate(int slot) {
++        return this.delegate.removeItemNoUpdate(slot);
++    }
++
++    @Override
++    public void setItem(int slot, ItemStack stack) {
++        this.delegate.setItem(slot, stack);
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.delegate.getMaxStackSize();
++    }
++
++    @Override
++    public void setChanged() {
++        this.delegate.setChanged();
++    }
++
++    @Override
++    public boolean stillValid(Player player) {
++        return this.delegate.stillValid(player);
++    }
++
++    @Override
++    public List<ItemStack> getContents() {
++        return this.delegate.getContents();
++    }
++
++    @Override
++    public void onOpen(CraftHumanEntity who) {
++        this.delegate.onOpen(who);
++    }
++
++    @Override
++    public void onClose(CraftHumanEntity who) {
++        this.delegate.onClose(who);
++    }
++
++    @Override
++    public List<HumanEntity> getViewers() {
++        return this.delegate.getViewers();
++    }
++
++    @Override
++    public InventoryHolder getOwner() {
++        return this.owner;
++    }
++
++    @Override
++    public void setMaxStackSize(int size) {
++        this.delegate.setMaxStackSize(size);
++    }
++
++    @Override
++    public Location getLocation() {
++        return this.delegate.getLocation();
++    }
++
++    @Override
++    public void clearContent() {
++        this.delegate.clearContent();
++    }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+@@ -0,0 +0,0 @@ public class CraftContainer extends AbstractContainerMenu {
+             // Paper start
+             @Override
+             public net.kyori.adventure.text.Component title() {
+-                return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).title() : net.kyori.adventure.text.Component.text(inventory.getType().getDefaultTitle());
++                return inventory instanceof CraftInventoryCustom custom ? custom.title() : inventory.getType().defaultTitle(); // Paper
+             }
+             // Paper end
+ 
+             @Override
+             public String getTitle() {
+-                return inventory instanceof CraftInventoryCustom ? ((CraftInventoryCustom.MinecraftInventory) ((CraftInventory) inventory).getInventory()).getTitle() : inventory.getType().getDefaultTitle();
++                return inventory instanceof CraftInventoryCustom custom ? custom.getTitle() : inventory.getType().getDefaultTitle(); // Paper
+             }
+         }, player, id);
+     }
+@@ -0,0 +0,0 @@ public class CraftContainer extends AbstractContainerMenu {
+             this.lastSlots = delegate.lastSlots;
+             this.slots = delegate.slots;
+             this.remoteSlots = delegate.remoteSlots;
++            // Paper start - copy data slots for InventoryView#set/getProperty
++            this.dataSlots = delegate.dataSlots;
++            this.remoteDataSlots = delegate.remoteDataSlots;
++            // Paper end
+         }
+ 
+         // SPIGOT-4598 - we should still delegate the shift click handler
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+@@ -0,0 +0,0 @@ public class CraftInventory implements Inventory {
+             return InventoryType.BREWING;
+         } else if (this.inventory instanceof CraftInventoryCustom.MinecraftInventory) {
+             return ((CraftInventoryCustom.MinecraftInventory) this.inventory).getType();
++            // Paper start
++        } else if (this.inventory instanceof io.papermc.paper.inventory.PaperInventoryCustomHolderContainer holderContainer) {
++            return holderContainer.getType();
++        // Paper end
+         } else if (this.inventory instanceof PlayerEnderChestContainer) {
+             return InventoryType.ENDER_CHEST;
+         } else if (this.inventory instanceof MerchantContainer) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java
+@@ -0,0 +0,0 @@ import org.bukkit.event.inventory.InventoryType;
+ import org.bukkit.inventory.InventoryHolder;
+ 
+ public class CraftInventoryCustom extends CraftInventory {
++    // Paper start
++    public CraftInventoryCustom(InventoryHolder owner, InventoryType type, Container delegate) {
++        super(new io.papermc.paper.inventory.PaperInventoryCustomHolderContainer(owner, delegate, type));
++    }
++    // Paper end
+     public CraftInventoryCustom(InventoryHolder owner, InventoryType type) {
+         super(new MinecraftInventory(owner, type));
+     }
+@@ -0,0 +0,0 @@ public class CraftInventoryCustom extends CraftInventory {
+     public CraftInventoryCustom(InventoryHolder owner, int size, String title) {
+         super(new MinecraftInventory(owner, size, title));
+     }
++    // Paper start
++    public String getTitle() {
++        if (this.inventory instanceof MinecraftInventory minecraftInventory) {
++            return minecraftInventory.getTitle();
++        } else if (this.inventory instanceof io.papermc.paper.inventory.PaperInventoryCustomHolderContainer customHolderContainer) {
++            return customHolderContainer.getTitle();
++        } else {
++            throw new UnsupportedOperationException(this.inventory.getClass() + " isn't a recognized Container type here");
++        }
++    }
++
++    public net.kyori.adventure.text.Component title() {
++        if (this.inventory instanceof MinecraftInventory minecraftInventory) {
++            return minecraftInventory.title();
++        } else if (this.inventory instanceof io.papermc.paper.inventory.PaperInventoryCustomHolderContainer customHolderContainer) {
++            return customHolderContainer.title();
++        } else {
++            throw new UnsupportedOperationException(this.inventory.getClass() + " isn't a recognized Container type here");
++        }
++    }
++    // Paper end
+ 
+     static class MinecraftInventory implements Container {
+         private final NonNullList<ItemStack> items;
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+ 
+     @Override
+     public Inventory createInventory(InventoryHolder holder, InventoryType type) {
+-        return this.getInventory(this.getTileEntity());
++        return this.getInventory(holder, type, this.getTileEntity()); // Paper
      }
  
-@@ -0,0 +0,0 @@ public final class CraftInventoryCreator {
+     // Paper start
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+             ((RandomizableContainerBlockEntity) te).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title));
+         }
+ 
+-        return getInventory(te);
++        return this.getInventory(owner, type, te); // Paper
+     }
      // Paper end
  
-     public Inventory createInventory(InventoryHolder holder, InventoryType type, String title) {
-+        // Paper start
-+        if (holder != null) {
-+            return DEFAULT_CONVERTER.createInventory(holder, type, title);
-+        }
-+        //noinspection ConstantConditions // Paper end
-         return this.converterMap.get(type).createInventory(holder, type, title);
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+             ((RandomizableContainerBlockEntity) te).setCustomName(CraftChatMessage.fromStringOrNull(title));
+         }
+ 
+-        return this.getInventory(te);
++        return this.getInventory(holder, type, te); // Paper
      }
  
++    @Deprecated // Paper - use getInventory with owner and type
+     public Inventory getInventory(Container tileEntity) {
++        // Paper start
++        return this.getInventory(null, null, tileEntity);
++    }
++
++    public Inventory getInventory(InventoryHolder owner, InventoryType type, Container tileEntity) { // Paper
++        if (owner != null) return new org.bukkit.craftbukkit.inventory.CraftInventoryCustom(owner, type, tileEntity); // Paper
++        // Paper end
+         return new CraftInventory(tileEntity);
+     }
+ 
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+         public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) {
+             Container tileEntity = getTileEntity();
+             ((AbstractFurnaceBlockEntity) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title));
+-            return getInventory(tileEntity);
++            return this.getInventory(owner, type, tileEntity); // Paper
+         }
+         // Paper end
+ 
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+         public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) {
+             Container tileEntity = this.getTileEntity();
+             ((AbstractFurnaceBlockEntity) tileEntity).setCustomName(CraftChatMessage.fromStringOrNull(title));
+-            return this.getInventory(tileEntity);
++            return this.getInventory(owner, type, tileEntity); // Paper
+         }
+ 
+         @Override
+         public Inventory getInventory(Container tileEntity) {
++            // Paper start
++            return getInventory(null, null, tileEntity);
++        }
++
++        @Override
++        public Inventory getInventory(InventoryHolder owner, InventoryType type, net.minecraft.world.Container tileEntity) { // Paper
++            if (owner != null) return new org.bukkit.craftbukkit.inventory.CraftInventoryCustom(owner, type, tileEntity); // Paper
++            // Paper end
+             return new CraftInventoryFurnace((AbstractFurnaceBlockEntity) tileEntity);
+         }
+     }
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+             if (tileEntity instanceof BrewingStandBlockEntity) {
+                 ((BrewingStandBlockEntity) tileEntity).setCustomName(io.papermc.paper.adventure.PaperAdventure.asVanilla(title));
+             }
+-            return getInventory(tileEntity);
++            return this.getInventory(owner, type, tileEntity); // Paper
+         }
+         // Paper end
+ 
+@@ -0,0 +0,0 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+             if (tileEntity instanceof BrewingStandBlockEntity) {
+                 ((BrewingStandBlockEntity) tileEntity).setCustomName(CraftChatMessage.fromStringOrNull(title));
+             }
+-            return this.getInventory(tileEntity);
++            return this.getInventory(holder, type, tileEntity); // Paper
+         }
+ 
+         @Override
+         public Inventory getInventory(Container tileEntity) {
++            // Paper start
++            return getInventory(null, null, tileEntity);
++        }
++
++        @Override
++        public Inventory getInventory(InventoryHolder owner, InventoryType type, net.minecraft.world.Container tileEntity) { // Paper
++            if (owner != null) return new org.bukkit.craftbukkit.inventory.CraftInventoryCustom(owner, type, tileEntity); // Paper
++            // Paper end
+             return new CraftInventoryBrewer(tileEntity);
+         }
+     }