This commit is contained in:
2026-03-10 03:04:20 +08:00
parent 740527fc41
commit bd3e1a8d82
18 changed files with 468 additions and 89 deletions

View File

@@ -12,18 +12,13 @@ A Minecraft mod that adds tiered Ender Chests with massive storage capacity!
| Gold | 81 | 3 | | Gold | 81 | 3 |
| Diamond | 108 | 4 | | Diamond | 108 | 4 |
| Netherite | 135 | 5 | | Netherite | 135 | 5 |
| Dragon | 2000 | ~222 | | Dragon | 1998 | ~222 |
### Portable Versions ### Portable Versions
All tiers have handheld versions crafted with 2 sticks + the chest block. All tiers have handheld versions crafted with 2 sticks + the chest block.
## Building ## Building
### Using Docker (Recommended)
```bash
docker build -t betterenderchests .
docker run --rm -v $(pwd)/build:/app/build betterenderchests
```
### Using Gradle ### Using Gradle
```bash ```bash

View File

@@ -56,9 +56,10 @@ repositories {
} }
dependencies { dependencies {
implementation "net.neoforged:neoforge:${neo_version}" implementation 'net.neoforged:neoforge:' + neo_version
} }
tasks.withType(ProcessResources).configureEach { tasks.withType(ProcessResources).configureEach {
var replaceProperties = [ var replaceProperties = [
minecraft_version : minecraft_version, minecraft_version : minecraft_version,

View File

@@ -35,7 +35,7 @@ public class ModBlocks {
// Dragon Breath Ender Chest - Massive storage // Dragon Breath Ender Chest - Massive storage
public static final DeferredBlock<Block> DRAGON_ENDER_CHEST = BLOCKS.registerBlock("dragon_ender_chest", public static final DeferredBlock<Block> 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)); BlockBehaviour.Properties.ofFullCopy(Blocks.OBSIDIAN).strength(50f, 1200f).sound(SoundType.STONE));
public static void register(IEventBus eventBus) { public static void register(IEventBus eventBus) {

View File

@@ -1,6 +1,7 @@
package com.betterenderchests.block; package com.betterenderchests.block;
import com.betterenderchests.menu.TieredEnderChestMenu; import com.betterenderchests.menu.TieredEnderChestMenu;
import com.betterenderchests.storage.PlayerSharedEnderStorage;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
@@ -59,7 +60,7 @@ public class TieredEnderChestBlock extends BaseEntityBlock {
@Override @Override
protected RenderShape getRenderShape(BlockState state) { protected RenderShape getRenderShape(BlockState state) {
return RenderShape.ENTITYBLOCK_ANIMATED; return RenderShape.MODEL;
} }
@Override @Override
@@ -71,11 +72,26 @@ public class TieredEnderChestBlock extends BaseEntityBlock {
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) { protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
if (level.isClientSide) { if (level.isClientSide) {
return InteractionResult.SUCCESS; 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 @Nullable

View File

@@ -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);
}
}

View File

@@ -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<TieredEnderChestMenu> {
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);
}
}

View File

@@ -1,11 +1,9 @@
package com.betterenderchests.item; package com.betterenderchests.item;
import com.betterenderchests.BetterEnderChests; import com.betterenderchests.BetterEnderChests;
import com.betterenderchests.block.ModBlocks;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.ItemStack;
import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.DeferredRegister;
@@ -18,23 +16,23 @@ public class ModCreativeTabs {
public static final Supplier<CreativeModeTab> BETTER_ENDER_CHESTS_TAB = CREATIVE_MODE_TABS.register("betterenderchests_tab", public static final Supplier<CreativeModeTab> BETTER_ENDER_CHESTS_TAB = CREATIVE_MODE_TABS.register("betterenderchests_tab",
() -> CreativeModeTab.builder() () -> CreativeModeTab.builder()
.title(Component.translatable("itemGroup.betterenderchests")) .title(Component.translatable("itemGroup.betterenderchests"))
.icon(() -> new ItemStack(ModBlocks.DIAMOND_ENDER_CHEST.get())) .icon(() -> ModItems.DIAMOND_ENDER_CHEST.toStack())
.displayItems((parameters, output) -> { .displayItems((parameters, output) -> {
// Blocks // Placeable chest blocks
output.accept(ModBlocks.COPPER_ENDER_CHEST.get()); output.accept(ModItems.COPPER_ENDER_CHEST);
output.accept(ModBlocks.IRON_ENDER_CHEST.get()); output.accept(ModItems.IRON_ENDER_CHEST);
output.accept(ModBlocks.GOLD_ENDER_CHEST.get()); output.accept(ModItems.GOLD_ENDER_CHEST);
output.accept(ModBlocks.DIAMOND_ENDER_CHEST.get()); output.accept(ModItems.DIAMOND_ENDER_CHEST);
output.accept(ModBlocks.NETHERITE_ENDER_CHEST.get()); output.accept(ModItems.NETHERITE_ENDER_CHEST);
output.accept(ModBlocks.DRAGON_ENDER_CHEST.get()); output.accept(ModItems.DRAGON_ENDER_CHEST);
// Portable Items // Portable chest items
output.accept(ModItems.PORTABLE_COPPER_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_COPPER_ENDER_CHEST);
output.accept(ModItems.PORTABLE_IRON_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_IRON_ENDER_CHEST);
output.accept(ModItems.PORTABLE_GOLD_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_GOLD_ENDER_CHEST);
output.accept(ModItems.PORTABLE_DIAMOND_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_DIAMOND_ENDER_CHEST);
output.accept(ModItems.PORTABLE_NETHERITE_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_NETHERITE_ENDER_CHEST);
output.accept(ModItems.PORTABLE_DRAGON_ENDER_CHEST.get()); output.accept(ModItems.PORTABLE_DRAGON_ENDER_CHEST);
}) })
.build()); .build());

View File

@@ -1,6 +1,8 @@
package com.betterenderchests.item; package com.betterenderchests.item;
import com.betterenderchests.BetterEnderChests; import com.betterenderchests.BetterEnderChests;
import com.betterenderchests.block.ModBlocks;
import net.minecraft.world.item.BlockItem;
import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredItem; import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.DeferredRegister;
@@ -8,6 +10,31 @@ import net.neoforged.neoforge.registries.DeferredRegister;
public class ModItems { public class ModItems {
public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(BetterEnderChests.MOD_ID); public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(BetterEnderChests.MOD_ID);
// Block items for placeable chests
public static final DeferredItem<BlockItem> COPPER_ENDER_CHEST = ITEMS.registerItem(
"copper_ender_chest",
props -> new BlockItem(ModBlocks.COPPER_ENDER_CHEST.get(), props));
public static final DeferredItem<BlockItem> IRON_ENDER_CHEST = ITEMS.registerItem(
"iron_ender_chest",
props -> new BlockItem(ModBlocks.IRON_ENDER_CHEST.get(), props));
public static final DeferredItem<BlockItem> GOLD_ENDER_CHEST = ITEMS.registerItem(
"gold_ender_chest",
props -> new BlockItem(ModBlocks.GOLD_ENDER_CHEST.get(), props));
public static final DeferredItem<BlockItem> DIAMOND_ENDER_CHEST = ITEMS.registerItem(
"diamond_ender_chest",
props -> new BlockItem(ModBlocks.DIAMOND_ENDER_CHEST.get(), props));
public static final DeferredItem<BlockItem> NETHERITE_ENDER_CHEST = ITEMS.registerItem(
"netherite_ender_chest",
props -> new BlockItem(ModBlocks.NETHERITE_ENDER_CHEST.get(), props.fireResistant()));
public static final DeferredItem<BlockItem> DRAGON_ENDER_CHEST = ITEMS.registerItem(
"dragon_ender_chest",
props -> new BlockItem(ModBlocks.DRAGON_ENDER_CHEST.get(), props.fireResistant()));
// Portable Ender Chests (Handheld versions) // Portable Ender Chests (Handheld versions)
public static final DeferredItem<PortableEnderChestItem> PORTABLE_COPPER_ENDER_CHEST = ITEMS.registerItem( public static final DeferredItem<PortableEnderChestItem> PORTABLE_COPPER_ENDER_CHEST = ITEMS.registerItem(
"portable_copper_ender_chest", "portable_copper_ender_chest",
@@ -31,7 +58,7 @@ public class ModItems {
public static final DeferredItem<PortableEnderChestItem> PORTABLE_DRAGON_ENDER_CHEST = ITEMS.registerItem( public static final DeferredItem<PortableEnderChestItem> PORTABLE_DRAGON_ENDER_CHEST = ITEMS.registerItem(
"portable_dragon_ender_chest", "portable_dragon_ender_chest",
props -> new PortableEnderChestItem(props, 2000).fireResistant()); props -> new PortableEnderChestItem(props, 1998).fireResistant());
public static void register(IEventBus eventBus) { public static void register(IEventBus eventBus) {
ITEMS.register(eventBus); ITEMS.register(eventBus);

View File

@@ -1,19 +1,17 @@
package com.betterenderchests.item; package com.betterenderchests.item;
import com.betterenderchests.menu.TieredEnderChestMenu; import com.betterenderchests.menu.TieredEnderChestMenu;
import com.betterenderchests.storage.PlayerSharedEnderStorage;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats; import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerPlayer;
public class PortableEnderChestItem extends Item { public class PortableEnderChestItem extends Item {
private final int inventorySize; private final int inventorySize;
@@ -30,11 +28,19 @@ public class PortableEnderChestItem extends Item {
ItemStack stack = player.getItemInHand(hand); ItemStack stack = player.getItemInHand(hand);
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) { if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) {
player.openMenu(new SimpleMenuProvider( int normalizedSize = TieredEnderChestMenu.normalizeSize(inventorySize);
(containerId, playerInventory, p) -> serverPlayer.openMenu(
new TieredEnderChestMenu(containerId, playerInventory, inventorySize), new SimpleMenuProvider(
(containerId, playerInventory, p) -> new TieredEnderChestMenu(
containerId,
playerInventory,
PlayerSharedEnderStorage.getContainer(serverPlayer),
normalizedSize
),
containerTitle containerTitle
)); ),
buffer -> buffer.writeVarInt(normalizedSize)
);
player.awardStat(Stats.OPEN_ENDERCHEST); player.awardStat(Stats.OPEN_ENDERCHEST);
} }
@@ -48,4 +54,7 @@ public class PortableEnderChestItem extends Item {
public PortableEnderChestItem fireResistant() { public PortableEnderChestItem fireResistant() {
return this; return this;
} }
} }

View File

@@ -16,7 +16,7 @@ public class ModMenus {
public static final Supplier<MenuType<TieredEnderChestMenu>> TIERED_ENDER_CHEST = MENUS.register( public static final Supplier<MenuType<TieredEnderChestMenu>> TIERED_ENDER_CHEST = MENUS.register(
"tiered_ender_chest", "tiered_ender_chest",
() -> IMenuTypeExtension.create((containerId, playerInventory, buf) -> () -> IMenuTypeExtension.create((containerId, playerInventory, buf) ->
new TieredEnderChestMenu(containerId, playerInventory))); new TieredEnderChestMenu(containerId, playerInventory, buf)));
public static void register(IEventBus eventBus) { public static void register(IEventBus eventBus) {
MENUS.register(eventBus); MENUS.register(eventBus);

View File

@@ -1,6 +1,7 @@
package com.betterenderchests.menu; package com.betterenderchests.menu;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.util.Mth;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer; import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
@@ -10,52 +11,103 @@ import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
public class TieredEnderChestMenu extends AbstractContainerMenu { 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 Container container;
private final int containerRows; private final int logicalSize;
private final int containerCols = 9; 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) { public TieredEnderChestMenu(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, 27); // Default 3 rows this(containerId, playerInventory, 27);
} }
public TieredEnderChestMenu(int containerId, RegistryFriendlyByteBuf buffer) { 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) { 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) { public TieredEnderChestMenu(int containerId, Inventory playerInventory, Container container, int size) {
super(ModMenus.TIERED_ENDER_CHEST.get(), containerId); 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.container = container;
this.containerRows = size / 9;
checkContainerSize(container, size); 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); container.startOpen(playerInventory.player);
}
// Container inventory slots
int slotY = 17; 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++) { 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 + this.visibleRows * 18 + 14;
int playerInventoryY = slotY + containerRows * 18 + 14; if (playerInventory != null) {
for (int row = 0; row < 3; row++) { for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) { 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 + row * 9 + 9, 8 + col * 18, playerInventoryY + row * 18));
} }
} }
// Player hotbar slots
int hotbarY = playerInventoryY + 58; int hotbarY = playerInventoryY + 58;
for (int col = 0; col < 9; col++) { for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(playerInventory, col, 8 + col * 18, hotbarY)); 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 @Override
public boolean stillValid(Player player) { public boolean stillValid(Player player) {
@@ -71,11 +123,11 @@ public class TieredEnderChestMenu extends AbstractContainerMenu {
ItemStack slotStack = slot.getItem(); ItemStack slotStack = slot.getItem();
itemstack = slotStack.copy(); itemstack = slotStack.copy();
if (index < this.containerRows * 9) { if (index < this.visibleSlots) {
if (!this.moveItemStackTo(slotStack, this.containerRows * 9, this.slots.size(), true)) { if (!this.moveItemStackTo(slotStack, this.visibleSlots, this.slots.size(), true)) {
return ItemStack.EMPTY; 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; return ItemStack.EMPTY;
} }
@@ -95,11 +147,68 @@ public class TieredEnderChestMenu extends AbstractContainerMenu {
this.container.stopOpen(player); this.container.stopOpen(player);
} }
public Container getContainer() { public int getRowCount() {
return container; return this.visibleRows;
} }
public int getRowCount() { public int getLogicalRows() {
return containerRows; 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();
}
} }
} }

View File

@@ -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<UUID, PersistentContainer> 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<ItemStack> 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<ItemStack> 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);
}
}
}

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:copper_ender_chest" "item": "betterenderchests:copper_ender_chest"
} }
}, },

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:diamond_ender_chest" "item": "betterenderchests:diamond_ender_chest"
} }
}, },

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:dragon_ender_chest" "item": "betterenderchests:dragon_ender_chest"
} }
}, },

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:gold_ender_chest" "item": "betterenderchests:gold_ender_chest"
} }
}, },

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:iron_ender_chest" "item": "betterenderchests:iron_ender_chest"
} }
}, },

View File

@@ -2,14 +2,14 @@
"type": "minecraft:crafting_shaped", "type": "minecraft:crafting_shaped",
"pattern": [ "pattern": [
" S ", " S ",
"SES", "SCS",
" S " " S "
], ],
"key": { "key": {
"S": { "S": {
"item": "minecraft:stick" "item": "minecraft:stick"
}, },
"E": { "C": {
"item": "betterenderchests:netherite_ender_chest" "item": "betterenderchests:netherite_ender_chest"
} }
}, },