diff --git a/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch index e41f6d341dc6..278109a95180 100644 --- a/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch @@ -6,6 +6,7 @@ private @Nullable EnderChestBlockEntity activeChest; - - public PlayerEnderChestContainer() { +- super(27); + // CraftBukkit start + private final Player owner; + @@ -20,7 +21,7 @@ + } + + public PlayerEnderChestContainer(Player owner) { - super(27); ++ super(io.papermc.paper.configuration.GlobalConfiguration.get().misc.enderChestSlotCount); // Paper - configurable ender chest slot count + this.owner = owner; + // CraftBukkit end } diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch index 30b8c15a5292..f99ed203cf62 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/EnderChestBlock.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/level/block/EnderChestBlock.java +++ b/net/minecraft/world/level/block/EnderChestBlock.java -@@ -78,16 +_,17 @@ +@@ -78,16 +_,30 @@ PlayerEnderChestContainer enderChestInventory = player.getEnderChestInventory(); if (enderChestInventory != null && level.getBlockEntity(pos) instanceof EnderChestBlockEntity enderChestBlockEntity) { BlockPos blockPos = pos.above(); @@ -19,7 +19,20 @@ + enderChestInventory.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations + if (level instanceof ServerLevel serverLevel && player.openMenu( + new SimpleMenuProvider( -+ (containerId, playerInventory, player1) -> ChestMenu.threeRows(containerId, playerInventory, enderChestInventory), CONTAINER_TITLE ++ (containerId, playerInventory, player1) -> { ++ final int rows = Math.clamp(enderChestInventory.getContainerSize() / 9, 1, 6); ++ // Paper start - configurable ender chest slot count ++ final net.minecraft.world.inventory.MenuType menuType = switch (rows) { ++ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; ++ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; ++ case 3 -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; ++ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; ++ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; ++ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; ++ }; ++ return new ChestMenu(menuType, containerId, playerInventory, enderChestInventory, rows); ++ // Paper end - configurable ender chest slot count ++ }, CONTAINER_TITLE + ) + ).isPresent()) { + // Paper end - Fix InventoryOpenEvent cancellation - moved up; diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch index 28174a42b059..44a42f62ef52 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch @@ -1,6 +1,17 @@ --- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java -@@ -49,6 +_,42 @@ +@@ -40,15 +_,51 @@ + public static final int OPENING_TICK_LENGTH = 10; + public static final float MAX_LID_HEIGHT = 0.5F; + public static final float MAX_LID_ROTATION = 270.0F; +- private static final int[] SLOTS = IntStream.range(0, 27).toArray(); ++ private static final int[] SLOTS = IntStream.range(0, io.papermc.paper.configuration.GlobalConfiguration.get().misc.shulkerBoxSlotCount).toArray(); // Paper - configurable shulker box slot count + private static final Component DEFAULT_NAME = Component.translatable("container.shulkerBox"); +- private NonNullList itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); ++ private NonNullList itemStacks = NonNullList.withSize(io.papermc.paper.configuration.GlobalConfiguration.get().misc.shulkerBoxSlotCount, ItemStack.EMPTY); // Paper - configurable shulker box slot count + public int openCount; + private ShulkerBoxBlockEntity.AnimationStatus animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED; + private float progress; private float progressOld; private final @Nullable DyeColor color; @@ -59,3 +70,36 @@ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); if (this.openCount <= 0) { this.level.gameEvent(user.getLivingEntity(), GameEvent.CONTAINER_CLOSE, this.worldPosition); +@@ -231,7 +_,8 @@ + + @Override + public int[] getSlotsForFace(Direction side) { +- return SLOTS; ++ // Paper - configurable shulker box slot count ++ return this.getContainerSize() == SLOTS.length ? SLOTS : IntStream.range(0, this.getContainerSize()).toArray(); + } + + @Override +@@ -254,7 +_,21 @@ + + @Override + protected AbstractContainerMenu createMenu(int id, Inventory player) { +- return new ShulkerBoxMenu(id, player, this); ++ // Paper start - configurable shulker box slot count ++ final int rows = Math.clamp(this.getContainerSize() / 9, 1, 6); ++ if (rows == 3) { ++ return new ShulkerBoxMenu(id, player, this); ++ } ++ final net.minecraft.world.inventory.MenuType menuType = switch (rows) { ++ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; ++ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; ++ case 3 -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; ++ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; ++ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; ++ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; ++ }; ++ return new net.minecraft.world.inventory.ChestMenu(menuType, id, player, this, rows); ++ // Paper end - configurable shulker box slot count + } + + public boolean isClosed() { diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java index e47f279b66b1..6c05e97964bb 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java @@ -350,6 +350,25 @@ private void postProcess() { public boolean enableNether = true; @Comment("Keeps Paper's fix for MC-159283 enabled. Disable to use vanilla End ring terrain.") public boolean fixFarEndTerrainGeneration = true; + @Comment("Number of ender chest slots. Supports 9-54 slots and is normalized to a multiple of 9.") + public int enderChestSlotCount = 27; + @Comment("Number of shulker box slots. Supports 9-54 slots and is normalized to a multiple of 9.") + public int shulkerBoxSlotCount = 27; + + @PostProcess + private void postProcess() { + this.enderChestSlotCount = this.normalizeContainerSlotCount(this.enderChestSlotCount, "misc.ender-chest-slot-count"); + this.shulkerBoxSlotCount = this.normalizeContainerSlotCount(this.shulkerBoxSlotCount, "misc.shulker-box-slot-count"); + } + + private int normalizeContainerSlotCount(final int configured, final String key) { + final int clamped = Math.clamp(configured, 9, 54); + final int normalized = Math.clamp(((clamped + 4) / 9) * 9, 9, 54); + if (configured != normalized) { + LOGGER.warn("Invalid {} value '{}', using '{}'. Valid values are multiples of 9 between 9 and 54.", key, configured, normalized); + } + return normalized; + } } public BlockUpdates blockUpdates; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 2f41a92465b9..b3da62fc63dd 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -111,6 +111,24 @@ public InventoryView getBukkitView() { public static net.minecraft.world.inventory.MenuType getNotchInventoryType(Inventory inventory) { final InventoryType type = inventory.getType(); switch (type) { + case SHULKER_BOX: + if (inventory.getSize() == 27) { + return net.minecraft.world.inventory.MenuType.SHULKER_BOX; + } + switch (inventory.getSize()) { + case 9: + return net.minecraft.world.inventory.MenuType.GENERIC_9x1; + case 18: + return net.minecraft.world.inventory.MenuType.GENERIC_9x2; + case 36: + return net.minecraft.world.inventory.MenuType.GENERIC_9x4; + case 45: + return net.minecraft.world.inventory.MenuType.GENERIC_9x5; + case 54: + return net.minecraft.world.inventory.MenuType.GENERIC_9x6; + default: + throw new IllegalArgumentException("Unsupported shulker inventory size " + inventory.getSize()); + } case CHEST: case ENDER_CHEST: case BARREL: @@ -178,7 +196,11 @@ private void setupSlots(Container top, net.minecraft.world.entity.player.Invento this.delegate = new BeaconMenu(windowId, bottom); break; case SHULKER_BOX: - this.delegate = new ShulkerBoxMenu(windowId, bottom, top); + if (top.getContainerSize() == 27) { + this.delegate = new ShulkerBoxMenu(windowId, bottom, top); + } else { + this.delegate = new ChestMenu(CraftContainer.getNotchInventoryType(view.getTopInventory()), windowId, bottom, top, top.getContainerSize() / 9); + } break; case BLAST_FURNACE: this.delegate = new BlastFurnaceMenu(windowId, bottom, top, new SimpleContainerData(4));