diff --git a/build-data/paper.at b/build-data/paper.at index 4671b6eb7ff64..88ff23bf04da8 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -332,3 +332,6 @@ public net.minecraft.world.entity.projectile.FireworkRocketEntity life # More Projectile API public net.minecraft.world.entity.projectile.FishingHook timeUntilLured + +# Fix many issues with custom inventories +public org.bukkit.craftbukkit.inventory.CraftContainer delegate diff --git a/patches/server/0902-Fix-many-issues-with-custom-inventories.patch b/patches/server/0902-Fix-many-issues-with-custom-inventories.patch new file mode 100644 index 0000000000000..9fff02aad63a3 --- /dev/null +++ b/patches/server/0902-Fix-many-issues-with-custom-inventories.patch @@ -0,0 +1,638 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic +Date: Sun, 2 Jan 2022 22:09:46 -0800 +Subject: [PATCH] Fix many issues with custom inventories + +There are lots of issues with custom inventories and this tries +to fix a bunch of them. Creating inventories with Bukkit.createInventory +with types from non-tile-entities that have crafting detection did not +work, namely anvils and smithing tables. Enchantment tables did +literally nothing + +diff --git a/build.gradle.kts b/build.gradle.kts +index dd8f449dddbe0838835ae8f8d5033aa422db403d..ae47325d803402445dfbc3819e44f9e8ac10b91b 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -82,9 +82,9 @@ publishing { + relocation { + // Order matters here - e.g. craftbukkit proper must be relocated before any of the libs are relocated into the cb package + val packageVersion = "1_18_R2" +- relocate("org.bukkit.craftbukkit" to "org.bukkit.craftbukkit.v$packageVersion") { +- exclude("org.bukkit.craftbukkit.Main*") +- } ++// relocate("org.bukkit.craftbukkit" to "org.bukkit.craftbukkit.v$packageVersion") { ++// exclude("org.bukkit.craftbukkit.Main*") ++// } + } + + tasks.shadowJar { +diff --git a/src/main/java/io/papermc/paper/inventory/PaperContainerWrapper.java b/src/main/java/io/papermc/paper/inventory/PaperContainerWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..94e4198a5b23f79e04a004e5a377d1ece979a533 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/inventory/PaperContainerWrapper.java +@@ -0,0 +1,146 @@ ++package io.papermc.paper.inventory; ++ ++import net.minecraft.core.NonNullList; ++import net.minecraft.world.Container; ++import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.inventory.ItemCombinerMenu; ++import net.minecraft.world.inventory.ResultContainer; ++import net.minecraft.world.item.ItemStack; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftHumanEntity; ++import org.bukkit.craftbukkit.inventory.CraftContainer; ++import org.bukkit.entity.HumanEntity; ++import org.bukkit.inventory.InventoryHolder; ++ ++import java.util.List; ++ ++public abstract class PaperContainerWrapper implements Container { ++ ++ private final Container wrapped; ++ private final boolean handlesSetChanged; ++ ++ protected PaperContainerWrapper(Container wrapped) { ++ this(wrapped, false); ++ } ++ ++ protected PaperContainerWrapper(Container wrapped, boolean skipSlotHandlingSetChanged) { ++ this.wrapped = wrapped; ++ this.handlesSetChanged = skipSlotHandlingSetChanged; ++ } ++ ++ public boolean handlesSetChanged() { ++ return this.handlesSetChanged; ++ } ++ ++ @Override ++ public void clearContent() { ++ this.wrapped.clearContent(); ++ } ++ ++ @Override ++ public int getContainerSize() { ++ return this.wrapped.getContainerSize(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ return this.wrapped.isEmpty(); ++ } ++ ++ @Override ++ public ItemStack getItem(int slot) { ++ return this.wrapped.getItem(slot); ++ } ++ ++ @Override ++ public ItemStack removeItem(int slot, int amount) { ++ return this.wrapped.removeItem(slot, amount); ++ } ++ ++ @Override ++ public ItemStack removeItemNoUpdate(int slot) { ++ return this.wrapped.removeItemNoUpdate(slot); ++ } ++ ++ @Override ++ public void setItem(int slot, ItemStack stack) { ++ this.wrapped.setItem(slot, stack); ++ } ++ ++ @Override ++ public int getMaxStackSize() { ++ return this.wrapped.getMaxStackSize(); ++ } ++ ++ @Override ++ public void setChanged() { ++ this.wrapped.setChanged(); ++ } ++ ++ @Override ++ public boolean stillValid(Player player) { ++ return this.wrapped.stillValid(player); ++ } ++ ++ @Override ++ public List getContents() { ++ return this.wrapped.getContents(); ++ } ++ ++ @Override ++ public void onOpen(CraftHumanEntity who) { ++ this.wrapped.onOpen(who); ++ } ++ ++ @Override ++ public void onClose(CraftHumanEntity who) { ++ this.wrapped.onClose(who); ++ } ++ ++ @Override ++ public List getViewers() { ++ return this.wrapped.getViewers(); ++ } ++ ++ @Override ++ public InventoryHolder getOwner() { ++ return this.wrapped.getOwner(); ++ } ++ ++ @Override ++ public void setMaxStackSize(int size) { ++ this.wrapped.setMaxStackSize(size); ++ } ++ ++ @Override ++ public Location getLocation() { ++ return this.wrapped.getLocation(); ++ } ++ ++ public static Container forItemCombinerMenu(Container top, CraftContainer currentMenu) { ++ return new PaperContainerWrapper(top, true) { ++ @Override ++ public void setItem(int slot, ItemStack stack) { ++ super.setItem(slot, stack); ++ if (slot < 2) { ++ this.setChanged(); ++ } ++ } ++ ++ @Override ++ public void setChanged() { ++ currentMenu.slotsChanged(this); ++ if (currentMenu.delegate instanceof ItemCombinerMenu menu) { ++ menu.slotsChanged(this); ++ } ++ } ++ }; ++ } ++ ++ public static class Result extends ResultContainer { ++ ++ public Result(Container wrapped, int slot) { ++ super(new NonNullList<>(wrapped.getContents().subList(slot, slot + 1), ItemStack.EMPTY) {}); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/inventory/PaperSliceContainer.java b/src/main/java/io/papermc/paper/inventory/PaperSliceContainer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7c5990a69c28689ea80828566e5400849fc38500 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/inventory/PaperSliceContainer.java +@@ -0,0 +1,79 @@ ++package io.papermc.paper.inventory; ++ ++import com.google.common.base.Preconditions; ++import net.minecraft.core.NonNullList; ++import net.minecraft.world.Container; ++import net.minecraft.world.ContainerHelper; ++import net.minecraft.world.item.ItemStack; ++ ++public class PaperSliceContainer extends PaperContainerWrapper { ++ ++ private final NonNullList items; ++ ++ /** ++ * @param start inclusive ++ * @param end exclusive ++ */ ++ public PaperSliceContainer(Container delegate, int start, int end) { ++ super(delegate, false); ++ Preconditions.checkArgument(start < end, "start must be less than end; start: " + start + ", end: " + end); ++ this.items = new NonNullList<>(delegate.getContents().subList(start, end), ItemStack.EMPTY) {}; ++ } ++ ++ @Override // copied from SimpleContainer ++ public void clearContent() { ++ this.items.clear(); ++ this.setChanged(); ++ } ++ ++ @Override ++ public int getContainerSize() { ++ return this.items.size(); ++ } ++ ++ @Override ++ public boolean isEmpty() { ++ for (ItemStack item : this.items) { ++ if (!item.isEmpty()) { ++ return false; ++ } ++ } ++ return true; ++ } ++ ++ @Override // copied from SimpleContainer ++ public ItemStack getItem(int slot) { ++ return slot >= 0 && slot < this.items.size() ? this.items.get(slot) : ItemStack.EMPTY; ++ } ++ ++ @Override // copied from SimpleContainer ++ public ItemStack removeItem(int slot, int amount) { ++ ItemStack itemStack = ContainerHelper.removeItem(this.items, slot, amount); ++ if (!itemStack.isEmpty()) { ++ this.setChanged(); ++ } ++ ++ return itemStack; ++ } ++ ++ @Override // copied from SimpleContainer ++ public ItemStack removeItemNoUpdate(int slot) { ++ ItemStack itemStack = this.items.get(slot); ++ if (itemStack.isEmpty()) { ++ return ItemStack.EMPTY; ++ } else { ++ this.items.set(slot, ItemStack.EMPTY); ++ return itemStack; ++ } ++ } ++ ++ @Override // copied from SimpleContainer ++ public void setItem(int slot, ItemStack stack) { ++ this.items.set(slot, stack); ++ if (!stack.isEmpty() && stack.getCount() > this.getMaxStackSize()) { ++ stack.setCount(this.getMaxStackSize()); ++ } ++ ++ this.setChanged(); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 1a87f61d534ed531132fb43a9d2a45a4b604a6fc..339f94fe527a5199d6a87ceb46d1a20652abe497 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -975,8 +975,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + @Override + public void handleRenameItem(ServerboundRenameItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); +- if (this.player.containerMenu instanceof AnvilMenu) { +- AnvilMenu containeranvil = (AnvilMenu) this.player.containerMenu; ++ // Paper start - fix custom anvils ++ if (this.player.containerMenu instanceof AnvilMenu || (this.player.containerMenu instanceof org.bukkit.craftbukkit.inventory.CraftContainer menu && menu.delegate instanceof AnvilMenu)) { ++ AnvilMenu containeranvil = (AnvilMenu) (this.player.containerMenu instanceof org.bukkit.craftbukkit.inventory.CraftContainer menu ? menu.delegate : this.player.containerMenu); ++ // Paper end - fix custom anvils + String s = SharedConstants.filterText(packet.getName()); + + if (s.length() <= 50) { +diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +index ec89526f1eabef4681fed57b74fc1bef5be4c0c1..ebb6435bb69300f5f35a71839aebda087d018cb4 100644 +--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +@@ -47,7 +47,12 @@ public class AnvilMenu extends ItemCombinerMenu { + } + + public AnvilMenu(int syncId, Inventory inventory, ContainerLevelAccess context) { +- super(MenuType.ANVIL, syncId, inventory, context); ++ // Paper start ++ this(syncId, inventory, context, null, null); ++ } ++ public AnvilMenu(int syncId, Inventory inventory, ContainerLevelAccess context, @javax.annotation.Nullable ResultContainer resultSlots, @javax.annotation.Nullable net.minecraft.world.Container inputSlots) { ++ super(MenuType.ANVIL, syncId, inventory, context, resultSlots, inputSlots); ++ // Paper end + this.cost = DataSlot.standalone(); + this.addDataSlot(this.cost); + } +diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +index ea7b670aa6308bbe9919afced02a9067da20f0ce..922d4732e687e2fc5f417a88a926aa0719d4a17e 100644 +--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +@@ -57,8 +57,13 @@ public class EnchantmentMenu extends AbstractContainerMenu { + } + + public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { ++ // Paper start ++ this(syncId, playerInventory, context, null); ++ } ++ public EnchantmentMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context, @javax.annotation.Nullable Container enchantSlots) { ++ // Paper end + super(MenuType.ENCHANTMENT, syncId); +- this.enchantSlots = new SimpleContainer(2) { ++ this.enchantSlots = enchantSlots != null ? enchantSlots : new SimpleContainer(2) { // Paper - custom enchant slots + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +index 0a30b051e2fb4f081d0d579b30732aa8289c3389..f9624bfe76753d133f098fc11bf655a6779f347d 100644 +--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +@@ -17,8 +17,8 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + private static final int INV_SLOT_END = 30; + private static final int USE_ROW_SLOT_START = 30; + private static final int USE_ROW_SLOT_END = 39; +- protected final ResultContainer resultSlots = new ResultContainer(); +- protected final Container inputSlots = new SimpleContainer(2) { ++ protected ResultContainer resultSlots = new ResultContainer(); ++ protected Container inputSlots = new SimpleContainer(2) { + @Override + public void setChanged() { + super.setChanged(); +@@ -35,7 +35,16 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { + protected abstract boolean isValidBlock(BlockState state); + + public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context) { ++ // Paper start ++ this(type, syncId, playerInventory, context, null, null); ++ } ++ public ItemCombinerMenu(@Nullable MenuType type, int syncId, Inventory playerInventory, ContainerLevelAccess context, @Nullable ResultContainer resultSlots, @Nullable Container inputSlots) { ++ // Paper end + super(type, syncId); ++ // Paper start ++ this.resultSlots = java.util.Objects.requireNonNullElse(resultSlots, this.resultSlots); ++ this.inputSlots = java.util.Objects.requireNonNullElse(inputSlots, this.inputSlots); ++ // Paper end + this.access = context; + this.player = playerInventory.player; + this.addSlot(new Slot(this.inputSlots, 0, 27, 47)); +diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +index 1fefb682d34ab165444c45439dbc1ea28dc9079f..1779b111fcfbf55fae2942f1d7e4a014e2410a7c 100644 +--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java +@@ -60,11 +60,16 @@ public class LoomMenu extends AbstractContainerMenu { + } + + public LoomMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { ++ // Paper start ++ this(syncId, playerInventory, context, null, null); ++ } ++ public LoomMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context, @javax.annotation.Nullable Container inputContainer, @javax.annotation.Nullable Container outputContainer) { ++ // Paper end + super(MenuType.LOOM, syncId); + this.selectedBannerPatternIndex = DataSlot.standalone(); + this.slotUpdateListener = () -> { + }; +- this.inputContainer = new SimpleContainer(3) { ++ this.inputContainer = inputContainer != null ? inputContainer : new SimpleContainer(3) { // Paper + @Override + public void setChanged() { + super.setChanged(); +@@ -79,7 +84,7 @@ public class LoomMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.outputContainer = new SimpleContainer(1) { ++ this.outputContainer = outputContainer != null ? outputContainer : new SimpleContainer(1) { // Paper + @Override + public void setChanged() { + super.setChanged(); +diff --git a/src/main/java/net/minecraft/world/inventory/ResultContainer.java b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +index 8bed6dde4bdaae2f2cb8aa018f2d9a093191bbb3..b03db7058df7ab7b39de59731ac2546fe35f597d 100644 +--- a/src/main/java/net/minecraft/world/inventory/ResultContainer.java ++++ b/src/main/java/net/minecraft/world/inventory/ResultContainer.java +@@ -57,6 +57,13 @@ public class ResultContainer implements Container, RecipeHolder { + this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY); + } + ++ // Paper start - create ResultContainer with existing list ++ public ResultContainer(NonNullList list) { ++ com.google.common.base.Preconditions.checkArgument(list.size() == 1); ++ this.itemStacks = list; ++ } ++ // Paper end ++ + @Override + public int getContainerSize() { + return 1; +diff --git a/src/main/java/net/minecraft/world/inventory/Slot.java b/src/main/java/net/minecraft/world/inventory/Slot.java +index 4468c415905f4b48aaa4269832ef725a8b9bc3ef..2da513621212bd27cb21bdce8539e69c599ce098 100644 +--- a/src/main/java/net/minecraft/world/inventory/Slot.java ++++ b/src/main/java/net/minecraft/world/inventory/Slot.java +@@ -57,6 +57,7 @@ public class Slot { + + public void set(ItemStack stack) { + this.container.setItem(this.slot, stack); ++ if (container instanceof io.papermc.paper.inventory.PaperContainerWrapper wrapper && wrapper.handlesSetChanged()) return; // Paper + this.setChanged(); + } + +diff --git a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +index cb3f522a586b841056c35378a49dd50bfa673f61..ed49db21c0d0ad16ce1de76e7d975610241969b5 100644 +--- a/src/main/java/net/minecraft/world/inventory/SmithingMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/SmithingMenu.java +@@ -27,7 +27,12 @@ public class SmithingMenu extends ItemCombinerMenu { + } + + public SmithingMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) { +- super(MenuType.SMITHING, syncId, playerInventory, context); ++ // Paper start ++ this(syncId, playerInventory, context, null, null); ++ } ++ public SmithingMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context, @Nullable ResultContainer resultSlots, @Nullable net.minecraft.world.Container inputSlots) { ++ super(MenuType.SMITHING, syncId, playerInventory, context, resultSlots, inputSlots); ++ // Paper end + this.level = playerInventory.player.level; + this.recipes = this.level.getRecipeManager().getAllRecipesFor(RecipeType.SMITHING); + } +diff --git a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +index c597139b2b88edf629bc0021ebb65d8bea2e6a7d..c4f7a3090dec3e7139eb10683c1af85c73bbb9dc 100644 +--- a/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java ++++ b/src/main/java/net/minecraft/world/inventory/StonecutterMenu.java +@@ -61,13 +61,18 @@ public class StonecutterMenu extends AbstractContainerMenu { + } + + public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) { ++ // Paper start ++ this(syncId, playerInventory, context, null, null); ++ } ++ public StonecutterMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context, @javax.annotation.Nullable Container container, @javax.annotation.Nullable ResultContainer resultContainer) { ++ // Paper end + super(MenuType.STONECUTTER, syncId); + this.selectedRecipeIndex = DataSlot.shared(new int[1], 0); // Paper - allow replication + this.recipes = Lists.newArrayList(); + this.input = ItemStack.EMPTY; + this.slotUpdateListener = () -> { + }; +- this.container = new SimpleContainer(1) { ++ this.container = container != null ? container : new SimpleContainer(1) { + @Override + public void setChanged() { + super.setChanged(); +@@ -82,7 +87,7 @@ public class StonecutterMenu extends AbstractContainerMenu { + } + // CraftBukkit end + }; +- this.resultContainer = new ResultContainer(); ++ this.resultContainer = java.util.Objects.requireNonNullElse(resultContainer, new ResultContainer()); // Paper + this.access = context; + this.level = playerInventory.player.level; + this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33)); +diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +index 8308de50b50779d59d30e48e5f29ea4551c3fbf9..b9db53a70acc9465356b30cca65cfb098d85e669 100644 +--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +@@ -48,39 +48,16 @@ public class CraftContainer extends AbstractContainerMenu { + } + + public CraftContainer(final Inventory inventory, final Player player, int id) { +- this(new InventoryView() { +- @Override +- public Inventory getTopInventory() { +- return inventory; +- } +- +- @Override +- public Inventory getBottomInventory() { +- return getPlayer().getInventory(); +- } +- +- @Override +- public HumanEntity getPlayer() { +- return player.getBukkitEntity(); +- } +- +- @Override +- public InventoryType getType() { +- return inventory.getType(); +- } +- +- // Paper start +- @Override +- public net.kyori.adventure.text.Component title() { +- return inventory instanceof CraftInventoryCustom custom ? custom.title() : inventory.getType().defaultTitle(); // Paper +- } +- // Paper end +- +- @Override +- public String getTitle() { +- return inventory instanceof CraftInventoryCustom custom ? custom.getTitle() : inventory.getType().getDefaultTitle(); // Paper +- } +- }, player, id); ++ // Paper start - fix custom inventories ++ super(CraftContainer.getNotchInventoryType(inventory), id); ++ // TODO: Do we need to check that it really is a CraftInventory? ++ Container top = ((CraftInventory) inventory).getInventory(); ++ net.minecraft.world.entity.player.Inventory bottom = (net.minecraft.world.entity.player.Inventory) ((CraftInventory) player.getBukkitEntity().getInventory()).getInventory(); ++ this.cachedType = inventory.getType(); ++ this.setupSlots(top, bottom, player); ++ this.setTitle(io.papermc.paper.adventure.PaperAdventure.asVanilla(inventory instanceof CraftInventoryCustom custom ? custom.title() : inventory.getType().defaultTitle())); ++ this.view = new CraftInventoryView(player.getBukkitEntity(), inventory, this); ++ // Paper end + } + + @Override +@@ -180,7 +157,25 @@ public class CraftContainer extends AbstractContainerMenu { + this.setupWorkbench(top, bottom); // SPIGOT-3812 - manually set up slots so we can use the delegated inventory and not the automatically created one + break; + case ENCHANTING: +- this.delegate = new EnchantmentMenu(windowId, bottom); ++ // Paper start - fix custom inventories ++ AbstractContainerMenu.checkContainerSize(top, 2); ++ final Container topEnchantWrapped = new io.papermc.paper.inventory.PaperContainerWrapper(top, false) { ++ ++ @Override ++ public void setChanged() { ++ CraftContainer.this.slotsChanged(this); ++ if (CraftContainer.this.delegate instanceof EnchantmentMenu menu) { ++ menu.slotsChanged(this); ++ } ++ } ++ }; ++ this.delegate = new EnchantmentMenu(windowId, bottom, net.minecraft.world.inventory.ContainerLevelAccess.create(entityhuman.level, entityhuman.blockPosition()), topEnchantWrapped) { ++ @Override ++ public void broadcastChanges() { ++ CraftContainer.this.broadcastChanges(); ++ } ++ }; ++ // Paper end + break; + case BREWING: + this.delegate = new BrewingStandMenu(windowId, bottom, top, new SimpleContainerData(2)); +@@ -189,10 +184,18 @@ public class CraftContainer extends AbstractContainerMenu { + this.delegate = new HopperMenu(windowId, bottom, top); + break; + case ANVIL: +- this.setupAnvil(top, bottom); // SPIGOT-6783 - manually set up slots so we can use the delegated inventory and not the automatically created one ++ // Paper start - fix custom inventories ++ AbstractContainerMenu.checkContainerSize(top, 3); ++ final Container topAnvilWrapped = io.papermc.paper.inventory.PaperContainerWrapper.forItemCombinerMenu(top, this); ++ this.delegate = new AnvilMenu(windowId, bottom, net.minecraft.world.inventory.ContainerLevelAccess.NULL, new io.papermc.paper.inventory.PaperContainerWrapper.Result(topAnvilWrapped, 2), topAnvilWrapped); ++ // Paper end + break; + case SMITHING: +- this.delegate = new SmithingMenu(windowId, bottom); ++ // Paper start - fix custom inventories ++ AbstractContainerMenu.checkContainerSize(top, 3); ++ final Container topSmithingWrapped = io.papermc.paper.inventory.PaperContainerWrapper.forItemCombinerMenu(top, this); ++ this.delegate = new SmithingMenu(windowId, bottom, net.minecraft.world.inventory.ContainerLevelAccess.NULL, new io.papermc.paper.inventory.PaperContainerWrapper.Result(topSmithingWrapped, 2), topSmithingWrapped); ++ // Paper end + break; + case BEACON: + this.delegate = new BeaconMenu(windowId, bottom); +@@ -210,7 +213,16 @@ public class CraftContainer extends AbstractContainerMenu { + this.delegate = new SmokerMenu(windowId, bottom, top, new SimpleContainerData(4)); + break; + case LOOM: +- this.delegate = new LoomMenu(windowId, bottom); ++ // Paper start - fix custom inventories ++ AbstractContainerMenu.checkContainerSize(top, 4); ++ final Container wrappedLoomInputContainer = new io.papermc.paper.inventory.PaperSliceContainer(top, 0, 3) { ++ @Override ++ public void setChanged() { ++ CraftContainer.this.delegate.slotsChanged(this); ++ } ++ }; ++ this.delegate = new LoomMenu(windowId, bottom, net.minecraft.world.inventory.ContainerLevelAccess.NULL, wrappedLoomInputContainer, new io.papermc.paper.inventory.PaperSliceContainer(top, 3, 4)); ++ // Paper end + break; + case CARTOGRAPHY: + this.delegate = new CartographyTableMenu(windowId, bottom); +@@ -219,7 +231,21 @@ public class CraftContainer extends AbstractContainerMenu { + this.delegate = new GrindstoneMenu(windowId, bottom); + break; + case STONECUTTER: +- this.delegate = new StonecutterMenu(windowId, bottom); ++ // Paper start - fix custom inventories ++ AbstractContainerMenu.checkContainerSize(top, 2); ++ final Container wrappedStonecutterInputContainer = new io.papermc.paper.inventory.PaperSliceContainer(top, 0, 1) { ++ @Override ++ public void setChanged() { ++ CraftContainer.this.delegate.slotsChanged(this); ++ } ++ }; ++ this.delegate = new StonecutterMenu(windowId, bottom, net.minecraft.world.inventory.ContainerLevelAccess.NULL, wrappedStonecutterInputContainer, new io.papermc.paper.inventory.PaperContainerWrapper.Result(top, 1)) { ++ @Override ++ public void broadcastChanges() { ++ CraftContainer.this.broadcastChanges(); ++ } ++ }; ++ // Paper end + break; + case MERCHANT: + this.delegate = new MerchantMenu(windowId, bottom); +@@ -241,12 +267,19 @@ public class CraftContainer extends AbstractContainerMenu { + case WORKBENCH: + this.delegate = new CraftingMenu(windowId, bottom); + break; +- case ANVIL: +- this.delegate = new AnvilMenu(windowId, bottom); +- break; ++ // case ANVIL: // Paper - dramatically improve spigot's fix for anvils ++ // this.delegate = new AnvilMenu(windowId, bottom); ++ // break; + } + } + ++ // Paper start - delegate menu button click ++ @Override ++ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) { ++ return this.delegate.clickMenuButton(player, id); ++ } ++ // Paper end ++ + private void setupWorkbench(Container top, Container bottom) { + // This code copied from ContainerWorkbench + this.addSlot(new Slot(top, 0, 124, 35)); +@@ -272,7 +305,7 @@ public class CraftContainer extends AbstractContainerMenu { + // End copy from ContainerWorkbench + } + +- private void setupAnvil(Container top, Container bottom) { ++ private void setupAnvil(Container top, Container bottom) { // Paper - don't use this/dramatically improve spigot's fix for anvils + // This code copied from ContainerAnvilAbstract + this.addSlot(new Slot(top, 0, 27, 47)); + this.addSlot(new Slot(top, 1, 76, 47));