From bd3e1a8d82c172427b569bd6e2abc06d814bab41 Mon Sep 17 00:00:00 2001 From: Dev-CGC Date: Tue, 10 Mar 2026 03:04:20 +0800 Subject: [PATCH] update --- README.md | 17 +- build.gradle | 3 +- .../betterenderchests/block/ModBlocks.java | 2 +- .../block/TieredEnderChestBlock.java | 26 ++- .../betterenderchests/client/ClientSetup.java | 19 ++ .../client/TieredEnderChestScreen.java | 139 +++++++++++++++ .../item/ModCreativeTabs.java | 34 ++-- .../com/betterenderchests/item/ModItems.java | 29 ++- .../item/PortableEnderChestItem.java | 31 ++-- .../com/betterenderchests/menu/ModMenus.java | 2 +- .../menu/TieredEnderChestMenu.java | 165 +++++++++++++++--- .../storage/PlayerSharedEnderStorage.java | 66 +++++++ .../recipe/portable_copper_ender_chest.json | 4 +- .../recipe/portable_diamond_ender_chest.json | 4 +- .../recipe/portable_dragon_ender_chest.json | 4 +- .../recipe/portable_gold_ender_chest.json | 4 +- .../recipe/portable_iron_ender_chest.json | 4 +- .../portable_netherite_ender_chest.json | 4 +- 18 files changed, 468 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/betterenderchests/client/ClientSetup.java create mode 100644 src/main/java/com/betterenderchests/client/TieredEnderChestScreen.java create mode 100644 src/main/java/com/betterenderchests/storage/PlayerSharedEnderStorage.java diff --git a/README.md b/README.md index 6a76252..11c1e20 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,18 @@ A Minecraft mod that adds tiered Ender Chests with massive storage capacity! ### Tiered Ender Chests | Tier | Storage Slots | Rows | |------|---------------|------| -| Copper | 27 | 1 | -| Iron | 54 | 2 | -| Gold | 81 | 3 | -| Diamond | 108 | 4 | -| Netherite | 135 | 5 | -| Dragon | 2000 | ~222 | +| Copper | 27 | 1 | +| Iron | 54 | 2 | +| Gold | 81 | 3 | +| Diamond | 108 | 4 | +| Netherite | 135 | 5 | +| Dragon | 1998 | ~222 | ### Portable Versions All tiers have handheld versions crafted with 2 sticks + the chest block. ## Building -### Using Docker (Recommended) -```bash -docker build -t betterenderchests . -docker run --rm -v $(pwd)/build:/app/build betterenderchests -``` ### Using Gradle ```bash diff --git a/build.gradle b/build.gradle index ff2e3cb..96cf070 100644 --- a/build.gradle +++ b/build.gradle @@ -56,9 +56,10 @@ repositories { } dependencies { - implementation "net.neoforged:neoforge:${neo_version}" + implementation 'net.neoforged:neoforge:' + neo_version } + tasks.withType(ProcessResources).configureEach { var replaceProperties = [ minecraft_version : minecraft_version, diff --git a/src/main/java/com/betterenderchests/block/ModBlocks.java b/src/main/java/com/betterenderchests/block/ModBlocks.java index 9abc1fb..448afd2 100644 --- a/src/main/java/com/betterenderchests/block/ModBlocks.java +++ b/src/main/java/com/betterenderchests/block/ModBlocks.java @@ -35,7 +35,7 @@ public class ModBlocks { // Dragon Breath Ender Chest - Massive storage public static final DeferredBlock DRAGON_ENDER_CHEST = BLOCKS.registerBlock("dragon_ender_chest", - props -> new TieredEnderChestBlock(props, 2000), // 10^1024 concept - using large GUI instead + props -> new TieredEnderChestBlock(props, 1998), BlockBehaviour.Properties.ofFullCopy(Blocks.OBSIDIAN).strength(50f, 1200f).sound(SoundType.STONE)); public static void register(IEventBus eventBus) { diff --git a/src/main/java/com/betterenderchests/block/TieredEnderChestBlock.java b/src/main/java/com/betterenderchests/block/TieredEnderChestBlock.java index 8b22ad3..da03bad 100644 --- a/src/main/java/com/betterenderchests/block/TieredEnderChestBlock.java +++ b/src/main/java/com/betterenderchests/block/TieredEnderChestBlock.java @@ -1,6 +1,7 @@ package com.betterenderchests.block; import com.betterenderchests.menu.TieredEnderChestMenu; +import com.betterenderchests.storage.PlayerSharedEnderStorage; import com.mojang.serialization.MapCodec; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -59,7 +60,7 @@ public class TieredEnderChestBlock extends BaseEntityBlock { @Override protected RenderShape getRenderShape(BlockState state) { - return RenderShape.ENTITYBLOCK_ANIMATED; + return RenderShape.MODEL; } @Override @@ -71,11 +72,26 @@ public class TieredEnderChestBlock extends BaseEntityBlock { protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { if (level.isClientSide) { return InteractionResult.SUCCESS; - } else { - player.openMenu(state.getMenuProvider(level, pos)); - player.awardStat(Stats.OPEN_ENDERCHEST); - return InteractionResult.CONSUME; } + + if (player instanceof ServerPlayer serverPlayer) { + int normalizedSize = TieredEnderChestMenu.normalizeSize(inventorySize); + serverPlayer.openMenu( + new SimpleMenuProvider( + (containerId, playerInventory, p) -> new TieredEnderChestMenu( + containerId, + playerInventory, + PlayerSharedEnderStorage.getContainer(serverPlayer), + normalizedSize + ), + containerTitle + ), + buffer -> buffer.writeVarInt(normalizedSize) + ); + player.awardStat(Stats.OPEN_ENDERCHEST); + } + + return InteractionResult.CONSUME; } @Nullable diff --git a/src/main/java/com/betterenderchests/client/ClientSetup.java b/src/main/java/com/betterenderchests/client/ClientSetup.java new file mode 100644 index 0000000..7bfc41f --- /dev/null +++ b/src/main/java/com/betterenderchests/client/ClientSetup.java @@ -0,0 +1,19 @@ +package com.betterenderchests.client; + +import com.betterenderchests.BetterEnderChests; +import com.betterenderchests.menu.ModMenus; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; + +@EventBusSubscriber(modid = BetterEnderChests.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) +public final class ClientSetup { + private ClientSetup() { + } + + @SubscribeEvent + public static void onRegisterMenuScreens(RegisterMenuScreensEvent event) { + event.register(ModMenus.TIERED_ENDER_CHEST.get(), TieredEnderChestScreen::new); + } +} diff --git a/src/main/java/com/betterenderchests/client/TieredEnderChestScreen.java b/src/main/java/com/betterenderchests/client/TieredEnderChestScreen.java new file mode 100644 index 0000000..16f9abe --- /dev/null +++ b/src/main/java/com/betterenderchests/client/TieredEnderChestScreen.java @@ -0,0 +1,139 @@ +package com.betterenderchests.client; + +import com.betterenderchests.menu.TieredEnderChestMenu; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Inventory; + +public class TieredEnderChestScreen extends AbstractContainerScreen { + private static final ResourceLocation CONTAINER_BACKGROUND = + ResourceLocation.withDefaultNamespace("textures/gui/container/generic_54.png"); + + private static final int SCROLL_X_OFFSET = 176; + private static final int SCROLL_Y_OFFSET = 18; + private static final int SCROLL_THUMB_HEIGHT = 15; + + private final int visibleRows; + + private boolean scrolling; + + public TieredEnderChestScreen(TieredEnderChestMenu menu, Inventory playerInventory, Component title) { + super(menu, playerInventory, title); + this.visibleRows = menu.getRowCount(); + this.imageHeight = 114 + this.visibleRows * 18; + this.inventoryLabelY = this.imageHeight - 94; + } + + @Override + protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) { + int x = this.leftPos; + int y = this.topPos; + + guiGraphics.blit(CONTAINER_BACKGROUND, x, y, 0, 0, this.imageWidth, this.visibleRows * 18 + 17); + guiGraphics.blit(CONTAINER_BACKGROUND, x, y + this.visibleRows * 18 + 17, 0, 126, this.imageWidth, 96); + + if (this.menu.getMaxScrollRows() > 0) { + int scrollX = x + SCROLL_X_OFFSET; + int scrollY = y + SCROLL_Y_OFFSET; + guiGraphics.fill(scrollX, scrollY, scrollX + 6, scrollY + getScrollTrackHeight(), 0xFF3F3F3F); + int thumbY = getThumbY(); + guiGraphics.fill(scrollX, thumbY, scrollX + 6, thumbY + SCROLL_THUMB_HEIGHT, 0xFFCFCFCF); + } + } + + @Override + protected void renderLabels(GuiGraphics guiGraphics, int mouseX, int mouseY) { + super.renderLabels(guiGraphics, mouseX, mouseY); + if (this.menu.getMaxScrollRows() > 0) { + String page = (this.menu.getScrollRow() + 1) + "/" + (this.menu.getMaxScrollRows() + 1); + guiGraphics.drawString(this.font, page, this.imageWidth - 44, 6, 0x404040, false); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0 && this.menu.getMaxScrollRows() > 0 && isOverScrollbar(mouseX, mouseY)) { + this.scrolling = true; + updateScrollFromMouse(mouseY); + return true; + } + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { + if (this.scrolling) { + updateScrollFromMouse(mouseY); + return true; + } + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + this.scrolling = false; + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (this.menu.getMaxScrollRows() > 0) { + int target = this.menu.getScrollRow() - (int) Math.signum(scrollY); + sendScrollRow(target); + return true; + } + return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); + } + + private boolean isOverScrollbar(double mouseX, double mouseY) { + int x = this.leftPos + SCROLL_X_OFFSET; + int y = this.topPos + SCROLL_Y_OFFSET; + return mouseX >= x && mouseX < x + 6 && mouseY >= y && mouseY < y + getScrollTrackHeight(); + } + + private int getScrollTrackHeight() { + return this.visibleRows * 18; + } + + private int getThumbY() { + int x = this.topPos + SCROLL_Y_OFFSET; + int range = getScrollTrackHeight() - SCROLL_THUMB_HEIGHT; + if (this.menu.getMaxScrollRows() <= 0) { + return x; + } + float fraction = (float) this.menu.getScrollRow() / (float) this.menu.getMaxScrollRows(); + return x + Mth.floor(fraction * range); + } + + private void updateScrollFromMouse(double mouseY) { + int top = this.topPos + SCROLL_Y_OFFSET; + int range = getScrollTrackHeight() - SCROLL_THUMB_HEIGHT; + float fraction = (float) ((mouseY - top) - (SCROLL_THUMB_HEIGHT / 2.0)) / (float) range; + int target = Math.round(Mth.clamp(fraction, 0.0F, 1.0F) * this.menu.getMaxScrollRows()); + sendScrollRow(target); + } + + private void sendScrollRow(int targetRow) { + int clamped = Mth.clamp(targetRow, 0, this.menu.getMaxScrollRows()); + if (clamped == this.menu.getScrollRow()) { + return; + } + + this.menu.setScrollRow(clamped); + Minecraft minecraft = this.minecraft; + if (minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(this.menu.containerId, clamped); + } + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + this.renderBackground(guiGraphics, mouseX, mouseY, partialTick); + super.render(guiGraphics, mouseX, mouseY, partialTick); + this.renderTooltip(guiGraphics, mouseX, mouseY); + } +} diff --git a/src/main/java/com/betterenderchests/item/ModCreativeTabs.java b/src/main/java/com/betterenderchests/item/ModCreativeTabs.java index 8a2ddff..94ccfa9 100644 --- a/src/main/java/com/betterenderchests/item/ModCreativeTabs.java +++ b/src/main/java/com/betterenderchests/item/ModCreativeTabs.java @@ -1,11 +1,9 @@ package com.betterenderchests.item; import com.betterenderchests.BetterEnderChests; -import com.betterenderchests.block.ModBlocks; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.ItemStack; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; @@ -18,23 +16,23 @@ public class ModCreativeTabs { public static final Supplier BETTER_ENDER_CHESTS_TAB = CREATIVE_MODE_TABS.register("betterenderchests_tab", () -> CreativeModeTab.builder() .title(Component.translatable("itemGroup.betterenderchests")) - .icon(() -> new ItemStack(ModBlocks.DIAMOND_ENDER_CHEST.get())) + .icon(() -> ModItems.DIAMOND_ENDER_CHEST.toStack()) .displayItems((parameters, output) -> { - // Blocks - output.accept(ModBlocks.COPPER_ENDER_CHEST.get()); - output.accept(ModBlocks.IRON_ENDER_CHEST.get()); - output.accept(ModBlocks.GOLD_ENDER_CHEST.get()); - output.accept(ModBlocks.DIAMOND_ENDER_CHEST.get()); - output.accept(ModBlocks.NETHERITE_ENDER_CHEST.get()); - output.accept(ModBlocks.DRAGON_ENDER_CHEST.get()); - - // Portable Items - output.accept(ModItems.PORTABLE_COPPER_ENDER_CHEST.get()); - output.accept(ModItems.PORTABLE_IRON_ENDER_CHEST.get()); - output.accept(ModItems.PORTABLE_GOLD_ENDER_CHEST.get()); - output.accept(ModItems.PORTABLE_DIAMOND_ENDER_CHEST.get()); - output.accept(ModItems.PORTABLE_NETHERITE_ENDER_CHEST.get()); - output.accept(ModItems.PORTABLE_DRAGON_ENDER_CHEST.get()); + // Placeable chest blocks + output.accept(ModItems.COPPER_ENDER_CHEST); + output.accept(ModItems.IRON_ENDER_CHEST); + output.accept(ModItems.GOLD_ENDER_CHEST); + output.accept(ModItems.DIAMOND_ENDER_CHEST); + output.accept(ModItems.NETHERITE_ENDER_CHEST); + output.accept(ModItems.DRAGON_ENDER_CHEST); + + // Portable chest items + output.accept(ModItems.PORTABLE_COPPER_ENDER_CHEST); + output.accept(ModItems.PORTABLE_IRON_ENDER_CHEST); + output.accept(ModItems.PORTABLE_GOLD_ENDER_CHEST); + output.accept(ModItems.PORTABLE_DIAMOND_ENDER_CHEST); + output.accept(ModItems.PORTABLE_NETHERITE_ENDER_CHEST); + output.accept(ModItems.PORTABLE_DRAGON_ENDER_CHEST); }) .build()); diff --git a/src/main/java/com/betterenderchests/item/ModItems.java b/src/main/java/com/betterenderchests/item/ModItems.java index e972816..e3327b4 100644 --- a/src/main/java/com/betterenderchests/item/ModItems.java +++ b/src/main/java/com/betterenderchests/item/ModItems.java @@ -1,6 +1,8 @@ package com.betterenderchests.item; import com.betterenderchests.BetterEnderChests; +import com.betterenderchests.block.ModBlocks; +import net.minecraft.world.item.BlockItem; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredItem; import net.neoforged.neoforge.registries.DeferredRegister; @@ -8,6 +10,31 @@ import net.neoforged.neoforge.registries.DeferredRegister; public class ModItems { public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(BetterEnderChests.MOD_ID); + // Block items for placeable chests + public static final DeferredItem COPPER_ENDER_CHEST = ITEMS.registerItem( + "copper_ender_chest", + props -> new BlockItem(ModBlocks.COPPER_ENDER_CHEST.get(), props)); + + public static final DeferredItem IRON_ENDER_CHEST = ITEMS.registerItem( + "iron_ender_chest", + props -> new BlockItem(ModBlocks.IRON_ENDER_CHEST.get(), props)); + + public static final DeferredItem GOLD_ENDER_CHEST = ITEMS.registerItem( + "gold_ender_chest", + props -> new BlockItem(ModBlocks.GOLD_ENDER_CHEST.get(), props)); + + public static final DeferredItem DIAMOND_ENDER_CHEST = ITEMS.registerItem( + "diamond_ender_chest", + props -> new BlockItem(ModBlocks.DIAMOND_ENDER_CHEST.get(), props)); + + public static final DeferredItem NETHERITE_ENDER_CHEST = ITEMS.registerItem( + "netherite_ender_chest", + props -> new BlockItem(ModBlocks.NETHERITE_ENDER_CHEST.get(), props.fireResistant())); + + public static final DeferredItem DRAGON_ENDER_CHEST = ITEMS.registerItem( + "dragon_ender_chest", + props -> new BlockItem(ModBlocks.DRAGON_ENDER_CHEST.get(), props.fireResistant())); + // Portable Ender Chests (Handheld versions) public static final DeferredItem PORTABLE_COPPER_ENDER_CHEST = ITEMS.registerItem( "portable_copper_ender_chest", @@ -31,7 +58,7 @@ public class ModItems { public static final DeferredItem PORTABLE_DRAGON_ENDER_CHEST = ITEMS.registerItem( "portable_dragon_ender_chest", - props -> new PortableEnderChestItem(props, 2000).fireResistant()); + props -> new PortableEnderChestItem(props, 1998).fireResistant()); public static void register(IEventBus eventBus) { ITEMS.register(eventBus); diff --git a/src/main/java/com/betterenderchests/item/PortableEnderChestItem.java b/src/main/java/com/betterenderchests/item/PortableEnderChestItem.java index 16d44fe..e18ffb8 100644 --- a/src/main/java/com/betterenderchests/item/PortableEnderChestItem.java +++ b/src/main/java/com/betterenderchests/item/PortableEnderChestItem.java @@ -1,19 +1,17 @@ package com.betterenderchests.item; import com.betterenderchests.menu.TieredEnderChestMenu; +import com.betterenderchests.storage.PlayerSharedEnderStorage; import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; -import net.minecraft.world.MenuProvider; import net.minecraft.world.SimpleMenuProvider; -import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; +import net.minecraft.server.level.ServerPlayer; public class PortableEnderChestItem extends Item { private final int inventorySize; @@ -28,16 +26,24 @@ public class PortableEnderChestItem extends Item { @Override public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); - + if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) { - player.openMenu(new SimpleMenuProvider( - (containerId, playerInventory, p) -> - new TieredEnderChestMenu(containerId, playerInventory, inventorySize), - containerTitle - )); + int normalizedSize = TieredEnderChestMenu.normalizeSize(inventorySize); + serverPlayer.openMenu( + new SimpleMenuProvider( + (containerId, playerInventory, p) -> new TieredEnderChestMenu( + containerId, + playerInventory, + PlayerSharedEnderStorage.getContainer(serverPlayer), + normalizedSize + ), + containerTitle + ), + buffer -> buffer.writeVarInt(normalizedSize) + ); player.awardStat(Stats.OPEN_ENDERCHEST); } - + return InteractionResultHolder.sidedSuccess(stack, level.isClientSide); } @@ -48,4 +54,7 @@ public class PortableEnderChestItem extends Item { public PortableEnderChestItem fireResistant() { return this; } + + + } diff --git a/src/main/java/com/betterenderchests/menu/ModMenus.java b/src/main/java/com/betterenderchests/menu/ModMenus.java index 18da3f0..f5f89f0 100644 --- a/src/main/java/com/betterenderchests/menu/ModMenus.java +++ b/src/main/java/com/betterenderchests/menu/ModMenus.java @@ -16,7 +16,7 @@ public class ModMenus { public static final Supplier> TIERED_ENDER_CHEST = MENUS.register( "tiered_ender_chest", () -> IMenuTypeExtension.create((containerId, playerInventory, buf) -> - new TieredEnderChestMenu(containerId, playerInventory))); + new TieredEnderChestMenu(containerId, playerInventory, buf))); public static void register(IEventBus eventBus) { MENUS.register(eventBus); diff --git a/src/main/java/com/betterenderchests/menu/TieredEnderChestMenu.java b/src/main/java/com/betterenderchests/menu/TieredEnderChestMenu.java index a113d3e..c7383c1 100644 --- a/src/main/java/com/betterenderchests/menu/TieredEnderChestMenu.java +++ b/src/main/java/com/betterenderchests/menu/TieredEnderChestMenu.java @@ -1,6 +1,7 @@ package com.betterenderchests.menu; import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.util.Mth; import net.minecraft.world.Container; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -10,53 +11,104 @@ import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; public class TieredEnderChestMenu extends AbstractContainerMenu { + public static final int VISIBLE_ROWS = 6; + public static final int VISIBLE_SLOTS = VISIBLE_ROWS * 9; + public static final int MAX_LOGICAL_SIZE = 1998; + private final Container container; - private final int containerRows; - private final int containerCols = 9; + private final int logicalSize; + private final int logicalRows; + private final int visibleRows; + private final int visibleSlots; + private final int maxScrollRows; + private int scrollRow; public TieredEnderChestMenu(int containerId, Inventory playerInventory) { - this(containerId, playerInventory, 27); // Default 3 rows + this(containerId, playerInventory, 27); } public TieredEnderChestMenu(int containerId, RegistryFriendlyByteBuf buffer) { - this(containerId, null, 27); // Network sync constructor - will be filled from client + this(containerId, null, readSize(buffer)); + } + + public TieredEnderChestMenu(int containerId, Inventory playerInventory, RegistryFriendlyByteBuf buffer) { + this(containerId, playerInventory, readSize(buffer)); + } + + public static int normalizeSize(int requestedSize) { + int clamped = Mth.clamp(requestedSize, 9, MAX_LOGICAL_SIZE); + return clamped - (clamped % 9); + } + + private static int readSize(RegistryFriendlyByteBuf buffer) { + return normalizeSize(buffer.readVarInt()); } public TieredEnderChestMenu(int containerId, Inventory playerInventory, int size) { - this(containerId, playerInventory, new SimpleContainer(size), size); + this(containerId, playerInventory, new SimpleContainer(normalizeSize(size)), normalizeSize(size)); } public TieredEnderChestMenu(int containerId, Inventory playerInventory, Container container, int size) { super(ModMenus.TIERED_ENDER_CHEST.get(), containerId); + this.logicalSize = normalizeSize(size); + this.logicalRows = this.logicalSize / 9; + this.visibleRows = Math.min(VISIBLE_ROWS, this.logicalRows); + this.visibleSlots = this.visibleRows * 9; + this.maxScrollRows = Math.max(0, this.logicalRows - this.visibleRows); this.container = container; - this.containerRows = size / 9; - - checkContainerSize(container, size); - container.startOpen(playerInventory.player); - // Container inventory slots + if (container.getContainerSize() < this.logicalSize) { + throw new IllegalArgumentException("Container is smaller than logical size: " + container.getContainerSize() + " < " + this.logicalSize); + } + + if (playerInventory != null) { + container.startOpen(playerInventory.player); + } + int slotY = 17; - for (int row = 0; row < containerRows; row++) { + for (int row = 0; row < this.visibleRows; row++) { for (int col = 0; col < 9; col++) { - this.addSlot(new Slot(container, col + row * 9, 8 + col * 18, slotY + row * 18)); + int visibleIndex = col + row * 9; + this.addSlot(new ScrollingSlot(container, visibleIndex, 8 + col * 18, slotY + row * 18)); } } - // Player inventory slots - int playerInventoryY = slotY + containerRows * 18 + 14; - for (int row = 0; row < 3; row++) { + int playerInventoryY = slotY + this.visibleRows * 18 + 14; + if (playerInventory != null) { + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 9; col++) { + this.addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, playerInventoryY + row * 18)); + } + } + + int hotbarY = playerInventoryY + 58; for (int col = 0; col < 9; col++) { - this.addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, playerInventoryY + row * 18)); + this.addSlot(new Slot(playerInventory, col, 8 + col * 18, hotbarY)); } } + } - // Player hotbar slots - int hotbarY = playerInventoryY + 58; - for (int col = 0; col < 9; col++) { - this.addSlot(new Slot(playerInventory, col, 8 + col * 18, hotbarY)); + private int toLogicalIndex(int visibleSlot) { + return visibleSlot + this.scrollRow * 9; + } + + public void setScrollRow(int row) { + int clamped = Mth.clamp(row, 0, this.maxScrollRows); + if (this.scrollRow != clamped) { + this.scrollRow = clamped; + this.broadcastChanges(); } } + @Override + public boolean clickMenuButton(Player player, int id) { + if (id >= 0 && id <= this.maxScrollRows) { + setScrollRow(id); + return true; + } + return false; + } + @Override public boolean stillValid(Player player) { return this.container.stillValid(player); @@ -66,16 +118,16 @@ public class TieredEnderChestMenu extends AbstractContainerMenu { public ItemStack quickMoveStack(Player player, int index) { ItemStack itemstack = ItemStack.EMPTY; Slot slot = this.slots.get(index); - + if (slot.hasItem()) { ItemStack slotStack = slot.getItem(); itemstack = slotStack.copy(); - if (index < this.containerRows * 9) { - if (!this.moveItemStackTo(slotStack, this.containerRows * 9, this.slots.size(), true)) { + if (index < this.visibleSlots) { + if (!this.moveItemStackTo(slotStack, this.visibleSlots, this.slots.size(), true)) { return ItemStack.EMPTY; } - } else if (!this.moveItemStackTo(slotStack, 0, this.containerRows * 9, false)) { + } else if (!this.moveItemStackTo(slotStack, 0, this.visibleSlots, false)) { return ItemStack.EMPTY; } @@ -95,11 +147,68 @@ public class TieredEnderChestMenu extends AbstractContainerMenu { this.container.stopOpen(player); } - public Container getContainer() { - return container; + public int getRowCount() { + return this.visibleRows; } - public int getRowCount() { - return containerRows; + public int getLogicalRows() { + return this.logicalRows; + } + + public int getMaxScrollRows() { + return this.maxScrollRows; + } + + public int getScrollRow() { + return this.scrollRow; + } + + private class ScrollingSlot extends Slot { + private final int visibleIndex; + + private ScrollingSlot(Container container, int visibleIndex, int x, int y) { + super(container, visibleIndex, x, y); + this.visibleIndex = visibleIndex; + } + + private int logicalIndex() { + return toLogicalIndex(this.visibleIndex); + } + + @Override + public int getContainerSlot() { + return logicalIndex(); + } + + @Override + public ItemStack getItem() { + return container.getItem(logicalIndex()); + } + + @Override + public boolean hasItem() { + return !getItem().isEmpty(); + } + + @Override + public void set(ItemStack stack) { + container.setItem(logicalIndex(), stack); + setChanged(); + } + + @Override + public ItemStack remove(int amount) { + return container.removeItem(logicalIndex(), amount); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return container.canPlaceItem(logicalIndex(), stack); + } + + @Override + public void setChanged() { + container.setChanged(); + } } } diff --git a/src/main/java/com/betterenderchests/storage/PlayerSharedEnderStorage.java b/src/main/java/com/betterenderchests/storage/PlayerSharedEnderStorage.java new file mode 100644 index 0000000..e02aec1 --- /dev/null +++ b/src/main/java/com/betterenderchests/storage/PlayerSharedEnderStorage.java @@ -0,0 +1,66 @@ +package com.betterenderchests.storage; + +import com.betterenderchests.menu.TieredEnderChestMenu; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.item.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class PlayerSharedEnderStorage { + private static final String STORAGE_TAG = "betterenderchests_shared_storage"; + private static final Map CACHE = new HashMap<>(); + + private PlayerSharedEnderStorage() { + } + + public static SimpleContainer getContainer(ServerPlayer player) { + return CACHE.computeIfAbsent(player.getUUID(), ignored -> createAndLoad(player)); + } + + private static PersistentContainer createAndLoad(ServerPlayer player) { + PersistentContainer container = new PersistentContainer(player); + load(container, player); + return container; + } + + private static void load(PersistentContainer container, ServerPlayer player) { + CompoundTag tag = player.getPersistentData().getCompound(STORAGE_TAG); + NonNullList items = NonNullList.withSize(TieredEnderChestMenu.MAX_LOGICAL_SIZE, ItemStack.EMPTY); + ContainerHelper.loadAllItems(tag, items, player.registryAccess()); + for (int i = 0; i < items.size(); i++) { + container.setItem(i, items.get(i)); + } + } + + private static void save(PersistentContainer container, ServerPlayer player) { + NonNullList items = NonNullList.withSize(TieredEnderChestMenu.MAX_LOGICAL_SIZE, ItemStack.EMPTY); + for (int i = 0; i < items.size(); i++) { + items.set(i, container.getItem(i)); + } + CompoundTag tag = new CompoundTag(); + ContainerHelper.saveAllItems(tag, items, player.registryAccess()); + player.getPersistentData().put(STORAGE_TAG, tag); + } + + private static class PersistentContainer extends SimpleContainer { + private final ServerPlayer owner; + + private PersistentContainer(ServerPlayer owner) { + super(TieredEnderChestMenu.MAX_LOGICAL_SIZE); + this.owner = owner; + } + + @Override + public void setChanged() { + super.setChanged(); + save(this, owner); + } + } +} + diff --git a/src/main/resources/data/betterenderchests/recipe/portable_copper_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_copper_ender_chest.json index 4d0c657..c5c4992 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_copper_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_copper_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:copper_ender_chest" } }, diff --git a/src/main/resources/data/betterenderchests/recipe/portable_diamond_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_diamond_ender_chest.json index 5a6543a..ddf5109 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_diamond_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_diamond_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:diamond_ender_chest" } }, diff --git a/src/main/resources/data/betterenderchests/recipe/portable_dragon_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_dragon_ender_chest.json index 210a48c..14681a2 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_dragon_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_dragon_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:dragon_ender_chest" } }, diff --git a/src/main/resources/data/betterenderchests/recipe/portable_gold_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_gold_ender_chest.json index 396814a..9a8a785 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_gold_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_gold_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:gold_ender_chest" } }, diff --git a/src/main/resources/data/betterenderchests/recipe/portable_iron_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_iron_ender_chest.json index e32a22e..9e6119f 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_iron_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_iron_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:iron_ender_chest" } }, diff --git a/src/main/resources/data/betterenderchests/recipe/portable_netherite_ender_chest.json b/src/main/resources/data/betterenderchests/recipe/portable_netherite_ender_chest.json index 19eaf58..2a09cdd 100644 --- a/src/main/resources/data/betterenderchests/recipe/portable_netherite_ender_chest.json +++ b/src/main/resources/data/betterenderchests/recipe/portable_netherite_ender_chest.json @@ -2,14 +2,14 @@ "type": "minecraft:crafting_shaped", "pattern": [ " S ", - "SES", + "SCS", " S " ], "key": { "S": { "item": "minecraft:stick" }, - "E": { + "C": { "item": "betterenderchests:netherite_ender_chest" } },