From 8cc17a622de58c500b6554f9128da39d8136b059 Mon Sep 17 00:00:00 2001 From: QiuShui1012 Date: Fri, 12 Jun 2026 22:46:39 +0800 Subject: [PATCH 1/7] feat(storage): implement storage system with various storage types and UI integration --- .../assets/anvilcraft/lang/en_ud.json | 4 + .../assets/anvilcraft/lang/en_us.json | 4 + .../TypeLimitItemStacksResourceHandler.java | 109 +++++- .../block/container/CrateBlock.java | 9 - .../block/container/storage/CrateBlock.java | 70 ++++ .../HyperdimensionStorageStationBlock.java | 52 ++- .../{ => storage}/LargeCrateBlock.java | 52 ++- .../{ => storage}/ShulkerContainerBlock.java | 51 ++- .../block/container/storage/package-info.java | 4 + ...perdimensionStorageStationBlockEntity.java | 12 - .../container/LargeCrateBlockEntity.java | 12 - .../ShulkerContainerBlockEntity.java | 12 - .../entity/storage/CrateBlockEntity.java | 12 + ...perdimensionStorageStationBlockEntity.java | 12 + .../entity/storage/LargeCrateBlockEntity.java | 12 + .../storage/ShulkerContainerBlockEntity.java | 12 + .../entity/storage/StorageBlockEntity.java | 107 ++++++ .../block/entity/storage/package-info.java | 4 + .../client/event/ClientEventListener.java | 2 + .../client/gui/screen/StorageScreen.java | 62 +++ .../config/AnvilCraftServerConfig.java | 3 + .../data/lang/ToolPropertyLang.java | 2 + .../MultiBlockConversionRecipeLoader.java | 2 +- .../event/ServerLifecycleEventListener.java | 2 + .../event/TooltipEventListener.java | 1 + .../anvilcraft/init/ModDataAttachments.java | 8 + .../dubhe/anvilcraft/init/ModMenuTypes.java | 13 +- .../init/block/ModBlockEntities.java | 12 +- .../anvilcraft/init/block/ModBlocks.java | 33 +- .../anvilcraft/init/item/ModComponents.java | 6 + .../anvilcraft/inventory/StorageMenu.java | 47 +++ .../item/block/ShulkerContainerBlockItem.java | 4 +- .../item/property/component/StorageRef.java | 72 ++++ .../network/multiple/StoragePackets.java | 361 ++++++++++++++++++ .../anvilcraft/saved/storage/BaseStorage.java | 16 +- .../saved/storage/CrateStorage.java | 29 ++ .../saved/storage/HyperdimensionStorage.java | 29 ++ .../saved/storage/LargeCrateStorage.java | 19 + .../storage/ShulkerContainerStorage.java | 29 ++ .../anvilcraft/saved/storage/StorageType.java | 74 ++++ .../anvilcraft/saved/storage/Storages.java | 85 +++++ .../saved/storage/category/Categories.java | 15 + .../saved/storage/network/MenuState.java | 59 +++ .../saved/storage/network/ServerState.java | 31 ++ 44 files changed, 1491 insertions(+), 75 deletions(-) delete mode 100644 src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java rename src/main/java/dev/dubhe/anvilcraft/block/container/{ => storage}/HyperdimensionStorageStationBlock.java (72%) rename src/main/java/dev/dubhe/anvilcraft/block/container/{ => storage}/LargeCrateBlock.java (73%) rename src/main/java/dev/dubhe/anvilcraft/block/container/{ => storage}/ShulkerContainerBlock.java (82%) create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java delete mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java delete mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java delete mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/item/property/component/StorageRef.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java diff --git a/src/generated/resources/assets/anvilcraft/lang/en_ud.json b/src/generated/resources/assets/anvilcraft/lang/en_ud.json index 342a858faf..db13e87d06 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_ud.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_ud.json @@ -219,6 +219,8 @@ "anvilcraft.configuration.show_multiphase_stored_id.tooltip": "ᗡI pǝɹoʇs ǝsɐɥdᴉʇꞁnɯ sʍoɥs ʇɐɥʇ ǝuᴉꞁ dᴉʇꞁooʇ ɐ ppⱯ", "anvilcraft.configuration.sliding_rail_stick_to_each_other": "ɹǝɥʇO ɥɔɐƎ o⟘ ʞɔᴉʇS ꞁᴉɐᴚ ᵷuᴉpᴉꞁS", "anvilcraft.configuration.sliding_rail_stick_to_each_other.tooltip": "sꞁᴉɐɹ ɹǝɥʇo oʇ uᴉɐɥɔ ꞁꞁᴉʍ ꞁᴉɐɹ ᵷuᴉpᴉꞁs ɐ ᵷuᴉꞁꞁnd ɹo ᵷuᴉɥsnԀ", + "anvilcraft.configuration.storage_recover_max_size": "ǝzᴉS xɐW ɹǝʌoɔǝᴚ ǝᵷɐɹoʇS", + "anvilcraft.configuration.storage_recover_max_size.tooltip": "uoᴉʇɐʇs ɹǝʌoɔǝɹ ,sǝᵷɐɹoʇs uᴉ sǝᴉɹʇuǝ ǝɥʇ ɟo ǝzᴉs xɐɯ ǝɥ⟘", "anvilcraft.configuration.title": "uoᴉʇɐɹnᵷᴉɟuoƆ ʇɟɐɹɔꞁᴉʌuⱯ", "anvilcraft.configuration.transcendence_anvil_beyond_max_level": "ꞁǝʌǝꞀ xɐW puoʎǝᗺ ꞁᴉʌuⱯ ǝɔuǝpuǝɔsuɐɹ⟘", "anvilcraft.configuration.transcendence_anvil_beyond_max_level.tooltip": "ꞁᴉʌuⱯ ǝɔuǝpuǝɔsuɐɹ⟘ uᴉ ꞁǝʌǝꞁ xɐɯ puoʎǝq sʞooᗺ pǝʇuɐɥɔuƎ ɥʇᴉʍ sɯǝʇᴉ ᵷuᴉuᴉqɯoƆ", @@ -1755,6 +1757,8 @@ "tooltip.anvilcraft.property.multiphase.suffix.3": "δ-", "tooltip.anvilcraft.property.providence": "sǝɯᴉʇ ǝꞁdᴉʇꞁnɯ sʇuǝɯʇuɐɥɔuǝ [%s pꞁoH] ɹǝᵷᵷᴉɹʇ oʇ ǝɔuɐɥɔ sɐɥ :ǝɔuǝpᴉʌoɹԀ", "tooltip.anvilcraft.property.providence.shifting": "sǝɯᴉʇ ǝꞁdᴉʇꞁnɯ sʇuǝɯʇuɐɥɔuǝ (%s) ɹǝᵷᵷᴉɹʇ oʇ ǝɔuɐɥɔ sɐɥ :ǝɔuǝpᴉʌoɹԀ", + "tooltip.anvilcraft.property.storage.id": "%s :ᗡI ǝᵷɐɹoʇS", + "tooltip.anvilcraft.property.storage.id.null": "ᵷuᴉɔɐꞁd uǝɥʍ ǝʇɐǝɹɔ ꞁꞁᴉʍ 'ʇǝʎ ǝuoN", "tooltip.anvilcraft.property.stored_energy": "%s :ʎᵷɹǝuƎ ᵷuᴉuᴉɐɯǝᴚ", "tooltip.anvilcraft.redstone.output_mode": "%s :ǝpoW ʇndʇnO ", "tooltip.anvilcraft.redstone.output_mode.compare": "ǝɹɐdɯoƆ", diff --git a/src/generated/resources/assets/anvilcraft/lang/en_us.json b/src/generated/resources/assets/anvilcraft/lang/en_us.json index 623a6e391b..1746cb1686 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_us.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_us.json @@ -219,6 +219,8 @@ "anvilcraft.configuration.show_multiphase_stored_id.tooltip": "Add a tooltip line that shows multiphase stored ID", "anvilcraft.configuration.sliding_rail_stick_to_each_other": "Sliding Rail Stick To Each Other", "anvilcraft.configuration.sliding_rail_stick_to_each_other.tooltip": "Pushing or pulling a sliding rail will chain to other rails", + "anvilcraft.configuration.storage_recover_max_size": "Storage Recover Max Size", + "anvilcraft.configuration.storage_recover_max_size.tooltip": "The max size of the entries in storages' recover station", "anvilcraft.configuration.title": "Anvilcraft Configuration", "anvilcraft.configuration.transcendence_anvil_beyond_max_level": "Transcendence Anvil Beyond Max Level", "anvilcraft.configuration.transcendence_anvil_beyond_max_level.tooltip": "Combining items with Enchanted Books beyond max level in Transcendence Anvil", @@ -1755,6 +1757,8 @@ "tooltip.anvilcraft.property.multiphase.suffix.3": "-δ", "tooltip.anvilcraft.property.providence": "Providence: has chance to trigger [Hold %s] enchantments multiple times", "tooltip.anvilcraft.property.providence.shifting": "Providence: has chance to trigger (%s) enchantments multiple times", + "tooltip.anvilcraft.property.storage.id": "Storage ID: %s", + "tooltip.anvilcraft.property.storage.id.null": "None yet, will create when placing", "tooltip.anvilcraft.property.stored_energy": "Remaining Energy: %s", "tooltip.anvilcraft.redstone.output_mode": " Output Mode: %s", "tooltip.anvilcraft.redstone.output_mode.compare": "Compare", diff --git a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java index 4e503d2151..01aba84511 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java @@ -1,9 +1,16 @@ package dev.dubhe.anvilcraft.api.itemhandler; import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; import dev.anvilcraft.lib.v2.network.util.BoolAndInt; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; +import lombok.AccessLevel; +import lombok.Getter; import net.minecraft.core.NonNullList; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.item.ItemInstance; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; @@ -19,14 +26,43 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; +import java.util.function.IntUnaryOperator; +@Getter(AccessLevel.PRIVATE) public class TypeLimitItemStacksResourceHandler implements ResourceHandler, ValueIOSerializable { - public static final String VALUE_IO_KEY = "stacks"; + public static final String STACKS_KEY = "stacks"; + public static final String TYPE_LIMIT_KEY = "type_limit"; + public static final String SPACE_SIZE_KEY = "space_size"; public static final Codec> STACKS_CODEC = UnlimitedItemStack.CODEC .listOf() .xmap(TypeLimitItemStacksResourceHandler::constructStackList, Function.identity()); - private final int typeLimit; - private final int spaceSize; + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.STACKS_CODEC + .fieldOf(TypeLimitItemStacksResourceHandler.STACKS_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getStacks), + Codec.INT + .fieldOf(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getTypeLimit), + Codec.INT + .fieldOf(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY) + .forGetter(TypeLimitItemStacksResourceHandler::getSpaceSize), + TypeLimitItemStacksResourceHandler::new + ); + public static final StreamCodec> STACKS_STREAM_CODEC = UnlimitedItemStack + .STREAM_CODEC + .apply(ByteBufCodecs.list()) + .map(TypeLimitItemStacksResourceHandler::constructStackList, Function.identity()); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STACKS_STREAM_CODEC, + TypeLimitItemStacksResourceHandler::getStacks, + ByteBufCodecs.VAR_INT, + TypeLimitItemStacksResourceHandler::getTypeLimit, + ByteBufCodecs.VAR_INT, + TypeLimitItemStacksResourceHandler::getSpaceSize, + TypeLimitItemStacksResourceHandler::new + ); + private int typeLimit; + private int spaceSize; private final NonNullList stacks = TypeLimitItemStacksResourceHandler.constructStackList(); private int space = 0; @@ -41,6 +77,53 @@ public TypeLimitItemStacksResourceHandler(int typeLimit, int spaceSize) { this.spaceSize = spaceSize; } + private TypeLimitItemStacksResourceHandler(NonNullList stacks, int typeLimit, int spaceSize) { + this.typeLimit = typeLimit; + this.spaceSize = spaceSize; + this.initStacks(stacks); + } + + private void initStacks(NonNullList stacks) { + this.space = 0; + MAIN: + for (UnlimitedItemStack stack : stacks) { + // 剩余空间连一个都塞不下,直接下一个 + if (this.computeEmptySize(stack) < 1) { + continue; + } + + int space = TypeLimitItemStacksResourceHandler.computeSpace(stack, stack.count()); + + // 塞得下整个栈,塞完下一个 + if (this.space + space <= this.spaceSize) { + this.space += space; + this.stacks.add(stack); + continue; + } + + // 塞不下整个栈,尝试找到能塞下的数量 + + for (int i = stack.count() - 1; i >= 0; i--) { + space = TypeLimitItemStacksResourceHandler.computeSpace(stack, i); + // 找到了,塞完下一个 + if (this.space + space <= this.spaceSize) { + this.space += space; + this.stacks.add(stack); + continue MAIN; + } + } + // 找不到不塞,下一个 + } + } + + public void addSpaceSize(IntUnaryOperator adder) { + int spaceSize = adder.applyAsInt(this.spaceSize); + if (spaceSize < this.spaceSize) { + return; + } + this.spaceSize = spaceSize; + } + private static NonNullList constructStackList() { return new NonNullList<>(new ArrayList<>(), UnlimitedItemStack.EMPTY); } @@ -68,7 +151,7 @@ public long getAmountAsLong(int index) { @Override public long getCapacityAsLong(int index, ItemResource resource) { - return Integer.MAX_VALUE; + return Long.MAX_VALUE; } @Override @@ -80,6 +163,10 @@ protected int computeEmptySize(ItemResource resource) { return TypeLimitItemStacksResourceHandler.computeCount(resource, this.spaceSize - this.space); } + protected int computeEmptySize(ItemInstance instance) { + return TypeLimitItemStacksResourceHandler.computeCount(instance, this.spaceSize - this.space); + } + protected int findEmptySlot() { for (int i = 0; i < this.stacks.size(); i++) { if (this.stacks.get(i).isEmpty()) { @@ -178,6 +265,12 @@ private void updateStacksSize() { } } + public void sync(TypeLimitItemStacksResourceHandler items) { + this.typeLimit = items.typeLimit; + this.spaceSize = items.spaceSize; + this.initStacks(items.stacks); + } + @Override public void serialize(ValueOutput output) { NonNullList saving = TypeLimitItemStacksResourceHandler.constructStackList(); @@ -187,13 +280,17 @@ public void serialize(ValueOutput output) { } saving.add(stack); } - output.store(TypeLimitItemStacksResourceHandler.VALUE_IO_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC, saving); + output.store(TypeLimitItemStacksResourceHandler.STACKS_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC, saving); + output.putInt(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY, this.typeLimit); + output.putInt(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY, this.spaceSize); } @Override public void deserialize(ValueInput input) { + this.typeLimit = input.getIntOr(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY, Integer.MAX_VALUE); + input.getInt(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY).ifPresent(size -> this.spaceSize = size); Optional> stacksOp = input.read( - TypeLimitItemStacksResourceHandler.VALUE_IO_KEY, + TypeLimitItemStacksResourceHandler.STACKS_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC ); if (stacksOp.isEmpty()) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java deleted file mode 100644 index 5996721da3..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/CrateBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.dubhe.anvilcraft.block.container; - -import net.minecraft.world.level.block.Block; - -public class CrateBlock extends Block { - public CrateBlock(Properties properties) { - super(properties); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java new file mode 100644 index 0000000000..e3198f0f59 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java @@ -0,0 +1,70 @@ +package dev.dubhe.anvilcraft.block.container.storage; + +import dev.dubhe.anvilcraft.block.entity.storage.CrateBlockEntity; +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import org.jspecify.annotations.Nullable; + +public class CrateBlock extends Block implements EntityBlock { + public CrateBlock(Properties properties) { + super(properties); + } + + @Override + public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return ModBlockEntities.CRATE.create(pos, state); + } + + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof CrateBlockEntity be) { + if (!level.isClientSide() && player.preventsBlockDrops() && be.getId() != null) { + ItemStack itemStack = new ItemStack(state.getBlock()); + itemStack.applyComponents(blockEntity.collectComponents()); + ItemEntity entity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, itemStack); + entity.setDefaultPickUpDelay(); + level.addFreshEntity(entity); + } + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof CrateBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java similarity index 72% rename from src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java index bd048a193d..d349454a89 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/HyperdimensionStorageStationBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java @@ -1,14 +1,24 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.HyperdimensionStorageStationBlockEntity; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.multipart.SimpleMultiPartBlock; import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -16,6 +26,7 @@ import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -64,6 +75,45 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.HYPERDIMENSION_STORAGE_STATION.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof HyperdimensionStorageStationBlockEntity be) { + if (!level.isClientSide() && player.preventsBlockDrops() && be.getId() != null) { + ItemStack itemStack = new ItemStack(state.getBlock()); + itemStack.applyComponents(blockEntity.collectComponents()); + ItemEntity entity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, itemStack); + entity.setDefaultPickUpDelay(); + level.addFreshEntity(entity); + } + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof HyperdimensionStorageStationBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java similarity index 73% rename from src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java index 09fb079c86..5f7c797e69 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/LargeCrateBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java @@ -1,14 +1,24 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.LargeCrateBlockEntity; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.multipart.SimpleMultiPartBlock; import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -16,6 +26,7 @@ import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -64,6 +75,45 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.LARGE_CRATE.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof LargeCrateBlockEntity be) { + if (!level.isClientSide() && player.preventsBlockDrops() && be.getId() != null) { + ItemStack itemStack = new ItemStack(state.getBlock()); + itemStack.applyComponents(blockEntity.collectComponents()); + ItemEntity entity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, itemStack); + entity.setDefaultPickUpDelay(); + level.addFreshEntity(entity); + } + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof LargeCrateBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java similarity index 82% rename from src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java rename to src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java index cdca495d9f..83ac169996 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/ShulkerContainerBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java @@ -1,17 +1,26 @@ -package dev.dubhe.anvilcraft.block.container; +package dev.dubhe.anvilcraft.block.container.storage; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import dev.anvilcraft.lib.v2.util.ShapeUtil; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; +import dev.dubhe.anvilcraft.block.entity.storage.ShulkerContainerBlockEntity; import dev.dubhe.anvilcraft.block.multipart.FlexibleMultiPartBlock; import dev.dubhe.anvilcraft.block.multipart.MultiPartBlockEntity; import dev.dubhe.anvilcraft.block.state.OpenedCube3x3PartHalf; +import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Mirror; @@ -23,6 +32,7 @@ import net.minecraft.world.level.block.state.properties.Property; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -190,6 +200,45 @@ public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { return ModBlockEntities.SHULKER_CONTAINER.create(pos, state); } + @Override + public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof ShulkerContainerBlockEntity be) { + if (!level.isClientSide() && player.preventsBlockDrops() && be.getId() != null) { + ItemStack itemStack = new ItemStack(state.getBlock()); + itemStack.applyComponents(blockEntity.collectComponents()); + ItemEntity entity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, itemStack); + entity.setDefaultPickUpDelay(); + level.addFreshEntity(entity); + } + } + + return super.playerWillDestroy(level, pos, state, player); + } + + @Override + protected InteractionResult useItemOn( + ItemStack itemStack, + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hitResult + ) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof ShulkerContainerBlockEntity entity) { + if (player instanceof ServerPlayer serverPlayer) { + if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + ModMenuTypes.open(serverPlayer, entity, pos); + return InteractionResult.SUCCESS_SERVER; + } else { + return InteractionResult.SUCCESS; + } + } + return super.useItemOn(itemStack, state, level, pos, player, hand, hitResult); + } + // region VoxelShapes @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java new file mode 100644 index 0000000000..0edfe95ac2 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package dev.dubhe.anvilcraft.block.container.storage; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java deleted file mode 100644 index 83b6e94adf..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/HyperdimensionStorageStationBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class HyperdimensionStorageStationBlockEntity extends BlockEntity { - public HyperdimensionStorageStationBlockEntity(BlockEntityType type, BlockPos worldPosition, BlockState blockState) { - super(type, worldPosition, blockState); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java deleted file mode 100644 index d356a4d400..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/LargeCrateBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class LargeCrateBlockEntity extends BlockEntity { - public LargeCrateBlockEntity(BlockEntityType type, BlockPos worldPosition, BlockState blockState) { - super(type, worldPosition, blockState); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java deleted file mode 100644 index ae2f256735..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/container/ShulkerContainerBlockEntity.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.dubhe.anvilcraft.block.entity.container; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -public class ShulkerContainerBlockEntity extends BlockEntity { // TODO: 实现潜影集装箱功能 - public ShulkerContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); - } -} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java new file mode 100644 index 0000000000..4644f8c60c --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/CrateBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class CrateBlockEntity extends StorageBlockEntity { + public CrateBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.CRATE); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java new file mode 100644 index 0000000000..0423d16688 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/HyperdimensionStorageStationBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class HyperdimensionStorageStationBlockEntity extends StorageBlockEntity { + public HyperdimensionStorageStationBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.HYPERDIMENSION); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java new file mode 100644 index 0000000000..14c86fb438 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/LargeCrateBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class LargeCrateBlockEntity extends StorageBlockEntity { + public LargeCrateBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.LARGE_CRATE); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java new file mode 100644 index 0000000000..61ddac9a23 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/ShulkerContainerBlockEntity.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +public class ShulkerContainerBlockEntity extends StorageBlockEntity { // TODO: 实现潜影集装箱功能 + public ShulkerContainerBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state, StorageType.SHULKER_CONTAINER); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java new file mode 100644 index 0000000000..a82423761f --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/StorageBlockEntity.java @@ -0,0 +1,107 @@ +package dev.dubhe.anvilcraft.block.entity.storage; + +import dev.dubhe.anvilcraft.init.ModMenuTypes; +import dev.dubhe.anvilcraft.init.item.ModComponents; +import dev.dubhe.anvilcraft.inventory.StorageMenu; +import dev.dubhe.anvilcraft.item.property.component.StorageRef; +import dev.dubhe.anvilcraft.saved.storage.StorageType; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.component.DataComponentGetter; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import org.jspecify.annotations.Nullable; + +import java.util.UUID; + +@Getter +public class StorageBlockEntity extends BlockEntity implements MenuProvider { + private final StorageType storageType; + private @Nullable UUID id; + + public StorageBlockEntity(BlockEntityType type, BlockPos pos, BlockState state, StorageType storageType) { + super(type, pos, state); + this.storageType = storageType; + } + + public void setId(UUID id) { + if (this.level != null) { + BlockState state = this.getBlockState(); + this.level.sendBlockUpdated(this.getBlockPos(), state, state, Block.UPDATE_ALL); + } + if (this.id != null) { + return; + } + this.id = id; + } + + @Override + protected void saveAdditional(ValueOutput output) { + if (this.id != null) { + output.store("storage_id", UUIDUtil.CODEC, this.id); + } + } + + @Override + protected void loadAdditional(ValueInput input) { + input.read("storage_id", UUIDUtil.CODEC).ifPresent(id -> this.id = id); + } + + @Override + public void onDataPacket(Connection net, ValueInput input) { + input.read("storage_id", UUIDUtil.CODEC).ifPresent(id -> this.id = id); + } + + @Override + public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + CompoundTag tag = new CompoundTag(); + tag.store("storage_id", UUIDUtil.CODEC, this.id); + return tag; + } + + @Override + public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @Override + protected void applyImplicitComponents(DataComponentGetter components) { + StorageRef ref = components.get(ModComponents.STORAGE); + if (ref.type() != this.storageType) { + return; + } + this.setId(ref.id().orElse(UUID.randomUUID())); + } + + @Override + protected void collectImplicitComponents(DataComponentMap.Builder components) { + components.set(ModComponents.STORAGE, new StorageRef(this.storageType, this.id)); + } + + @Override + public Component getDisplayName() { + return this.getBlockState().getBlock().getName(); + } + + @Override + public @Nullable AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) { + return new StorageMenu(ModMenuTypes.STORAGE.get(), containerId, inventory, this); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java new file mode 100644 index 0000000000..f3e611dc73 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/storage/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package dev.dubhe.anvilcraft.block.entity.storage; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index a29d9b5981..78816d5d1a 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -18,6 +18,7 @@ import dev.dubhe.anvilcraft.item.tool.AnvilHammerItem; import dev.dubhe.anvilcraft.network.UsePillBoxPacket; import dev.dubhe.anvilcraft.recipe.sync.RecipesRecord; +import dev.dubhe.anvilcraft.saved.storage.network.MenuState; import dev.dubhe.anvilcraft.util.BlockHighlightUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -93,6 +94,7 @@ public static void onRenderBlockOverlay(RenderBlockScreenEffectEvent event) { public static void onClientPlayerDisconnect(ClientPlayerNetworkEvent.LoggingOut event) { SoundHelper.INSTANCE.clear(); RecipesRecord.CLIENTSIDE = null; + MenuState.clear(); } @SubscribeEvent diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java new file mode 100644 index 0000000000..e164c4462c --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -0,0 +1,62 @@ +package dev.dubhe.anvilcraft.client.gui.screen; + +import dev.dubhe.anvilcraft.constant.Constant; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.inventory.StorageMenu; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; + +public class StorageScreen extends AbstractContainerScreen { + private static final Identifier BACKGROUND = SharedTextures.bg("misc", "storage_station"); + private static final Identifier CAPACITY = SharedTextures.textureGui("misc/storage_station/capacity"); + + public StorageScreen(StorageMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title, 300, 222); + } + + @Override + protected void init() { + super.init(); + this.titleLabelX = (this.getImageWidth() - 106 - this.font.width(this.title)) / 2 + 106; + this.titleLabelY = Constant.SCREEN_TITLE_Y; + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractBackground(graphics, mouseX, mouseY, a); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + StorageScreen.BACKGROUND, + this.leftPos, + this.topPos, + 0, + 0, + this.getImageWidth(), + this.getImageHeight(), + 512, + 256 + ); + + graphics.blit( + RenderPipelines.GUI_TEXTURED, + StorageScreen.CAPACITY, + this.leftPos, + this.topPos, + 0, + 0, + 194 * this.menu.getState().get, + 13, + 194, + 13 + ); + } + + @Override + protected void extractLabels(GuiGraphicsExtractor graphics, int xm, int ym) { + graphics.text(this.font, this.title, this.titleLabelX, this.titleLabelY, 0xFF404040, false); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/config/AnvilCraftServerConfig.java b/src/main/java/dev/dubhe/anvilcraft/config/AnvilCraftServerConfig.java index e4cdec67c9..636564a2ee 100644 --- a/src/main/java/dev/dubhe/anvilcraft/config/AnvilCraftServerConfig.java +++ b/src/main/java/dev/dubhe/anvilcraft/config/AnvilCraftServerConfig.java @@ -166,4 +166,7 @@ public static class PowerConverter { @Comment("The max size of the entries in multiphases' recover station") public int multiphaseRecoverMaxSize = 20; + + @Comment("The max size of the entries in storages' recover station") + public int storageRecoverMaxSize = 20; } diff --git a/src/main/java/dev/dubhe/anvilcraft/data/lang/ToolPropertyLang.java b/src/main/java/dev/dubhe/anvilcraft/data/lang/ToolPropertyLang.java index 7ab6c0cd1c..97379e9ff8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/lang/ToolPropertyLang.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/lang/ToolPropertyLang.java @@ -28,5 +28,7 @@ public static void init(RegistrumLangProvider provider) { provider.add("tooltip.anvilcraft.property.devour_range.range_5", "5x5"); provider.add("tooltip.anvilcraft.property.devour_range.range_7", "7x7"); provider.add("tooltip.anvilcraft.property.devour_range.range_9", "9x9"); + provider.add("tooltip.anvilcraft.property.storage.id", "Storage ID: %s"); + provider.add("tooltip.anvilcraft.property.storage.id.null", "None yet, will create when placing"); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/data/recipe/MultiBlockConversionRecipeLoader.java b/src/main/java/dev/dubhe/anvilcraft/data/recipe/MultiBlockConversionRecipeLoader.java index 6120e3e175..82dc5634e2 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/recipe/MultiBlockConversionRecipeLoader.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/recipe/MultiBlockConversionRecipeLoader.java @@ -3,7 +3,7 @@ import dev.anvilcraft.lib.v2.registrum.providers.generators.RegistrumRecipeProvider; import dev.dubhe.anvilcraft.AnvilCraft; import dev.dubhe.anvilcraft.block.cake.LargeCakeBlock; -import dev.dubhe.anvilcraft.block.container.LargeCrateBlock; +import dev.dubhe.anvilcraft.block.container.storage.LargeCrateBlock; import dev.dubhe.anvilcraft.block.decoration.heavyiron.HeavyIronBeamBlock; import dev.dubhe.anvilcraft.block.power.ring.AccelerationRingBlock; import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; diff --git a/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java index 247a52bc4a..820948bfab 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java @@ -10,6 +10,7 @@ import dev.dubhe.anvilcraft.api.world.load.RandomChuckTickLoadManager; import dev.dubhe.anvilcraft.block.entity.ItemCollectorBlockEntity; import dev.dubhe.anvilcraft.init.ModHammerInits; +import dev.dubhe.anvilcraft.saved.storage.network.MenuState; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.server.ServerStartedEvent; @@ -45,5 +46,6 @@ public static void onServerStopped(ServerStoppedEvent event) { PowerGrid.isServerClosing = false; PowerGrid.clear(); SoundHelper.INSTANCE.clear(); + MenuState.clear(); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/event/TooltipEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/TooltipEventListener.java index a821f45e21..32a8f8cb88 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/TooltipEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/TooltipEventListener.java @@ -68,6 +68,7 @@ public static void onTooltip(AppendCustomHoverTextEvent event) { stack.addToTooltip(ModComponents.AMULET, ctx, display, builder, flag); stack.addToTooltip(ModComponents.BOX_CONTENTS, ctx, display, builder, flag); stack.addToTooltip(ModComponents.OVER_LIMIT_CONTAINER, ctx, display, builder, flag); + stack.addToTooltip(ModComponents.STORAGE, ctx, display, builder, flag); } @SuppressWarnings("SameParameterValue") diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModDataAttachments.java b/src/main/java/dev/dubhe/anvilcraft/init/ModDataAttachments.java index aaa14a2973..e538a29327 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModDataAttachments.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModDataAttachments.java @@ -3,6 +3,7 @@ import com.mojang.serialization.Codec; import dev.dubhe.anvilcraft.AnvilCraft; import dev.dubhe.anvilcraft.api.amulet.AmuletRaffleProbability; +import dev.dubhe.anvilcraft.saved.storage.category.Categories; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.registries.DeferredRegister; @@ -29,6 +30,13 @@ public class ModDataAttachments { .build() ); + public static final Supplier> CATEGORIES = ATTACHMENT_TYPES.register( + "categories", + () -> AttachmentType.builder(Categories::new) + .serialize(Categories.CODEC.fieldOf("categories")) + .build() + ); + public static void register(IEventBus eventBus) { ATTACHMENT_TYPES.register(eventBus); } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java b/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java index 3a74287061..94720f2ef8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModMenuTypes.java @@ -23,6 +23,7 @@ import dev.dubhe.anvilcraft.client.gui.screen.RoyalGrindstoneScreen; import dev.dubhe.anvilcraft.client.gui.screen.RoyalSmithingScreen; import dev.dubhe.anvilcraft.client.gui.screen.SliderScreen; +import dev.dubhe.anvilcraft.client.gui.screen.StorageScreen; import dev.dubhe.anvilcraft.client.gui.screen.StructureToolScreen; import dev.dubhe.anvilcraft.client.gui.screen.TeslaTowerScreen; import dev.dubhe.anvilcraft.client.gui.screen.TranscendenceAnvilScreen; @@ -48,6 +49,7 @@ import dev.dubhe.anvilcraft.inventory.RoyalGrindstoneMenu; import dev.dubhe.anvilcraft.inventory.RoyalSmithingMenu; import dev.dubhe.anvilcraft.inventory.SliderMenu; +import dev.dubhe.anvilcraft.inventory.StorageMenu; import dev.dubhe.anvilcraft.inventory.StructureToolMenu; import dev.dubhe.anvilcraft.inventory.TeslaTowerMenu; import dev.dubhe.anvilcraft.inventory.TranscendenceAnvilMenu; @@ -65,8 +67,9 @@ public class ModMenuTypes { .menu("batch_cutter", BatchCutterMenu::new, () -> BatchCutterScreen::new) .register(); - public static final MenuEntry CHUTE = - REGISTRUM.menu("chute", ChuteMenu::new, () -> ChuteScreen::new).register(); + public static final MenuEntry CHUTE = REGISTRUM + .menu("chute", ChuteMenu::new, () -> ChuteScreen::new) + .register(); public static final MenuEntry MAGNETIC_CHUTE = REGISTRUM .menu("magnetic_chute", MagneticChuteMenu::new, () -> MagneticChuteScreen::new) @@ -162,7 +165,11 @@ public class ModMenuTypes { () -> FrostSmithingScreen::new) .register(); public static final MenuEntry ENERGY_WEAPON_MAKE = REGISTRUM - .menu("energy_weapon_make", EnergyWeaponMakeMenu::new, () -> EnergyWeaponMakeScreen::new).register(); + .menu("energy_weapon_make", EnergyWeaponMakeMenu::new, () -> EnergyWeaponMakeScreen::new) + .register(); + public static final MenuEntry STORAGE = REGISTRUM + .menu("storage", StorageMenu::new, () -> StorageScreen::new) + .register(); public static void register() { } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java index f081c7bd60..14e06aba73 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlockEntities.java @@ -51,9 +51,6 @@ import dev.dubhe.anvilcraft.block.entity.WhiteHoleBlockEntity; import dev.dubhe.anvilcraft.block.entity.batch.BatchCrafterBlockEntity; import dev.dubhe.anvilcraft.block.entity.batch.BatchCutterBlockEntity; -import dev.dubhe.anvilcraft.block.entity.container.HyperdimensionStorageStationBlockEntity; -import dev.dubhe.anvilcraft.block.entity.container.LargeCrateBlockEntity; -import dev.dubhe.anvilcraft.block.entity.container.ShulkerContainerBlockEntity; import dev.dubhe.anvilcraft.block.entity.heatable.GlowingBlockEntity; import dev.dubhe.anvilcraft.block.entity.heatable.HeatedBlockEntity; import dev.dubhe.anvilcraft.block.entity.heatable.IncandescentBlockEntity; @@ -63,6 +60,10 @@ import dev.dubhe.anvilcraft.block.entity.nesting.OverNestingShulkerBoxBlockEntity; import dev.dubhe.anvilcraft.block.entity.nesting.SupercriticalNestingShulkerBoxBlockEntity; import dev.dubhe.anvilcraft.block.entity.plate.TimeCountedPressurePlateBlockEntity; +import dev.dubhe.anvilcraft.block.entity.storage.CrateBlockEntity; +import dev.dubhe.anvilcraft.block.entity.storage.HyperdimensionStorageStationBlockEntity; +import dev.dubhe.anvilcraft.block.entity.storage.LargeCrateBlockEntity; +import dev.dubhe.anvilcraft.block.entity.storage.ShulkerContainerBlockEntity; import dev.dubhe.anvilcraft.client.renderer.blockentity.AdvancedComparatorRenderer; import dev.dubhe.anvilcraft.client.renderer.blockentity.BatchCraftingRenderer; import dev.dubhe.anvilcraft.client.renderer.blockentity.CFARenderer; @@ -364,6 +365,11 @@ public class ModBlockEntities { .validBlocks(ModBlocks.NEUTRON_IRRADIATOR) .register(); + public static final BlockEntityEntry CRATE = REGISTRUM + .blockEntity("crate", CrateBlockEntity::new) + .validBlocks(ModBlocks.CRATE) + .register(); + public static final BlockEntityEntry LARGE_CRATE = REGISTRUM .blockEntity("large_crate", LargeCrateBlockEntity::new) .validBlocks(ModBlocks.LARGE_CRATE) diff --git a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java index 925e96b1e1..18c58ac2bd 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/block/ModBlocks.java @@ -38,12 +38,12 @@ import dev.dubhe.anvilcraft.block.cfa.interfaces.CelestialForgingAnvilLogisticsInterfaceBlock; import dev.dubhe.anvilcraft.block.cfa.item.CelestialForgingAnvilAmplifierBlockItem; import dev.dubhe.anvilcraft.block.cfa.item.CelestialForgingAnvilInterfaceBlockItem; -import dev.dubhe.anvilcraft.block.container.CrateBlock; import dev.dubhe.anvilcraft.block.container.FluidTankBlock; -import dev.dubhe.anvilcraft.block.container.HyperdimensionStorageStationBlock; -import dev.dubhe.anvilcraft.block.container.LargeCrateBlock; import dev.dubhe.anvilcraft.block.container.LargeFluidTankBlock; -import dev.dubhe.anvilcraft.block.container.ShulkerContainerBlock; +import dev.dubhe.anvilcraft.block.container.storage.CrateBlock; +import dev.dubhe.anvilcraft.block.container.storage.HyperdimensionStorageStationBlock; +import dev.dubhe.anvilcraft.block.container.storage.LargeCrateBlock; +import dev.dubhe.anvilcraft.block.container.storage.ShulkerContainerBlock; import dev.dubhe.anvilcraft.block.decoration.ReinforcedConcreteBlock; import dev.dubhe.anvilcraft.block.decoration.ember.EmberMetalBlock; import dev.dubhe.anvilcraft.block.decoration.ember.EmberMetalPillarBlock; @@ -199,12 +199,12 @@ import dev.dubhe.anvilcraft.item.block.PlaceInWaterBlockItem; import dev.dubhe.anvilcraft.item.block.RadiationBlockItem; import dev.dubhe.anvilcraft.item.block.ResinBlockItem; -import dev.dubhe.anvilcraft.item.block.ShulkerContainerBlockItem; import dev.dubhe.anvilcraft.item.block.SimpleMultiPartBlockItem; import dev.dubhe.anvilcraft.item.block.SuperHeavyBlockItem; import dev.dubhe.anvilcraft.item.block.TeslaTowerItem; import dev.dubhe.anvilcraft.item.block.UncontainableBlockItem; import dev.dubhe.anvilcraft.item.property.component.OverLimitItemContainerContents; +import dev.dubhe.anvilcraft.item.property.component.StorageRef; import dev.dubhe.anvilcraft.util.registrater.DataGenUtil; import dev.dubhe.anvilcraft.util.registrater.ModelProviderUtil; import dev.dubhe.anvilcraft.util.registrater.PropertiesProviderUtil; @@ -1022,7 +1022,11 @@ public void accept(DataGenContext ctx, RegistrumItemModelGenera .noOcclusion() .isValidSpawn(ModBlocks::never) ) - .simpleItem() + .item() + .properties(properties -> properties + .component(ModComponents.STORAGE, StorageRef.crate()) + ) + .build() .recipe(RegistrumBlockRecipeLoader::crate) .blockstate(DataGenUtil::onlyState) .tag(BlockTags.MINEABLE_WITH_AXE) @@ -1036,7 +1040,10 @@ public void accept(DataGenContext ctx, RegistrumItemModelGenera .isValidSpawn(ModBlocks::never) ) .item(SimpleMultiPartBlockItem::new) - .properties(properties -> properties.stacksTo(16)) + .properties(properties -> properties + .stacksTo(16) + .component(ModComponents.STORAGE, StorageRef.largeCrate()) + ) .model(DataGenUtil::oversizedItem) .build() .blockstate(DataGenUtil::noExtraModelOrState) @@ -1051,8 +1058,11 @@ public void accept(DataGenContext ctx, RegistrumItemModelGenera .isValidSpawn(ModBlocks::never) .requiresCorrectToolForDrops() ) - .item(ShulkerContainerBlockItem::new) - .properties(properties -> properties.stacksTo(16)) + .item(FlexibleMultiPartBlockItem::new) + .properties(properties -> properties + .stacksTo(16) + .component(ModComponents.STORAGE, StorageRef.shulkerContainer()) + ) .model(DataGenUtil::oversizedItem) .tag(ModItemTags.EXPLOSION_PROOF) .build() @@ -1069,7 +1079,10 @@ public void accept(DataGenContext ctx, RegistrumItemModelGenera .requiresCorrectToolForDrops() ) .item(SimpleMultiPartBlockItem::new) - .properties(properties -> properties.stacksTo(16)) + .properties(properties -> properties + .stacksTo(16) + .component(ModComponents.STORAGE, StorageRef.hyperdimension()) + ) .model(DataGenUtil::oversizedItem) .tag(ModItemTags.EXPLOSION_PROOF) .build() diff --git a/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java b/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java index 632e3de616..4bd9f1f8be 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java @@ -18,6 +18,7 @@ import dev.dubhe.anvilcraft.item.property.component.PillBoxContents; import net.minecraft.world.item.component.ItemContainerContents; import dev.dubhe.anvilcraft.item.property.component.SavedEntity; +import dev.dubhe.anvilcraft.item.property.component.StorageRef; import dev.dubhe.anvilcraft.item.property.component.StoredEnergy; import dev.dubhe.anvilcraft.item.property.component.StoredItem; import dev.dubhe.anvilcraft.item.property.component.StructureData; @@ -164,6 +165,11 @@ public class ModComponents { b -> b.persistent(IAmulet.CODEC).networkSynchronized(IAmulet.STREAM_CODEC) ); + public static final DataComponentType STORAGE = register( + "storage", + b -> b.persistent(StorageRef.CODEC.codec()).networkSynchronized(StorageRef.STREAM_CODEC) + ); + private static DataComponentType register(String name, Consumer> customizer) { var builder = DataComponentType.builder(); customizer.accept(builder); diff --git a/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java new file mode 100644 index 0000000000..71d59cc96b --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java @@ -0,0 +1,47 @@ +package dev.dubhe.anvilcraft.inventory; + +import dev.anvilcraft.lib.v2.util.Util; +import dev.dubhe.anvilcraft.block.entity.storage.StorageBlockEntity; +import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import lombok.Getter; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jspecify.annotations.Nullable; + +@Getter +public class StorageMenu extends AbstractContainerMenu { + private final StorageBlockEntity be; + private final MenuState state; + private final Player player; + + public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory inv, @Nullable RegistryFriendlyByteBuf buf) { + this(menuType, containerId, inv, inv.player.level().getBlockEntity(buf.readBlockPos())); + } + + public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory inv, BlockEntity be) { + super(menuType, containerId); + this.be = Util.cast(be); + this.state = MenuState.get(this.be.getId()); + this.player = inv.player; + } + + @Override + public ItemStack quickMoveStack(Player player, int slotIndex) { + return null; + } + + @Override + public boolean stillValid(Player player) { + return AbstractContainerMenu.stillValid( + ContainerLevelAccess.create(player.level(), this.be.getBlockPos()), + player, + this.be.getBlockState().getBlock() + ); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/item/block/ShulkerContainerBlockItem.java b/src/main/java/dev/dubhe/anvilcraft/item/block/ShulkerContainerBlockItem.java index 76ae89d967..bbf98d289e 100644 --- a/src/main/java/dev/dubhe/anvilcraft/item/block/ShulkerContainerBlockItem.java +++ b/src/main/java/dev/dubhe/anvilcraft/item/block/ShulkerContainerBlockItem.java @@ -1,6 +1,6 @@ package dev.dubhe.anvilcraft.item.block; -import dev.dubhe.anvilcraft.block.container.ShulkerContainerBlock; +import dev.dubhe.anvilcraft.block.container.storage.ShulkerContainerBlock; import dev.dubhe.anvilcraft.block.state.OpenedCube3x3PartHalf; import net.minecraft.world.level.block.state.properties.BooleanProperty; @@ -8,4 +8,4 @@ public class ShulkerContainerBlockItem extends FlexibleMultiPartBlockItem id) implements TooltipProvider { + public static final MapCodec CODEC = CodecUtil.mapCodec( + StorageType.CODEC + .fieldOf("type") + .forGetter(StorageRef::type), + UUIDUtil.CODEC + .optionalFieldOf("id") + .forGetter(StorageRef::id), + StorageRef::new + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + StorageType.STREAM_CODEC, + StorageRef::type, + ByteBufCodecs.optional(UUIDUtil.STREAM_CODEC), + StorageRef::id, + StorageRef::new + ); + + public StorageRef(StorageType type) { + this(type, Optional.empty()); + } + + public StorageRef(StorageType type, @Nullable UUID id) { + this(type, Optional.ofNullable(id)); + } + + public static StorageRef crate() { + return new StorageRef(StorageType.CRATE); + } + + public static StorageRef largeCrate() { + return new StorageRef(StorageType.LARGE_CRATE); + } + + public static StorageRef shulkerContainer() { + return new StorageRef(StorageType.SHULKER_CONTAINER); + } + + public static StorageRef hyperdimension() { + return new StorageRef(StorageType.HYPERDIMENSION); + } + + @Override + public void addToTooltip(Item.TooltipContext context, Consumer builder, TooltipFlag flag, DataComponentGetter components) { + builder.accept(Component.translatable( + "tooltip.anvilcraft.property.storage.id", + this.id.map(id -> Component.literal(id.toString())) + .orElseGet(() -> Component.translatable("tooltip.anvilcraft.property.storage.id.null")) + ).withStyle(ChatFormatting.GRAY)); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java new file mode 100644 index 0000000000..3b67d61c78 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java @@ -0,0 +1,361 @@ +package dev.dubhe.anvilcraft.network.multiple; + +import dev.anvilcraft.lib.v2.network.packet.IClientboundPacket; +import dev.anvilcraft.lib.v2.network.packet.IPacket; +import dev.anvilcraft.lib.v2.network.packet.IServerboundPacket; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; +import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import dev.dubhe.anvilcraft.saved.storage.network.ServerState; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.UUIDUtil; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.PacketDistributor; +import net.neoforged.neoforge.network.connection.ConnectionType; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +public class StoragePackets { + private static CustomPacketPayload.Type of(String id) { + return IPacket.type(AnvilCraft.of("storage_" + id)); + } + + public record Sync2CFull( + UUID id, + int head, + @Nullable List stacks, + int encodedCount, + @Nullable RegistryFriendlyByteBuf buf + ) implements IClientboundPacket { + public static final Type TYPE = StoragePackets.of("sync2c_full"); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + Sync2CFull::encode, + Sync2CFull::decode + ); + public static final StreamCodec> STACKS_STREAM_CODEC = UnlimitedItemStack + .STREAM_CODEC + .apply(ByteBufCodecs.list()); + + /// 服务器侧 + private Sync2CFull(UUID id, int head, int encodedCount, RegistryFriendlyByteBuf buf) { + this(id, head, null, encodedCount, buf); + } + + /// 客户端侧 + private Sync2CFull(UUID id, int head, List stacks) { + this(id, head, stacks, stacks.size(), null); + } + + private static Sync2CFull decode(RegistryFriendlyByteBuf buf) { + return new Sync2CFull( + buf.readUUID(), + buf.readVarInt(), + Sync2CFull.STACKS_STREAM_CODEC.decode(buf) + ); + } + + private static void encode(RegistryFriendlyByteBuf buf, Sync2CFull data) { + buf.writeUUID(data.id); + buf.writeVarInt(data.head); + buf.writeVarInt(data.encodedCount); + if (data.buf == null) { + throw new UnsupportedOperationException("Use the FullSyncer"); + } + buf.ensureWritable(data.buf.readableBytes()); + buf.setBytes(buf.writerIndex(), data.buf, data.buf.readableBytes()); + } + + @Override + public Type type() { + return TYPE; + } + + @Override + public void handleOnClient(Player player) { + MenuState.get(this.id).sync(this.head, this.stacks); + } + } + + public static class FullSyncer { + /// 单个包的最大容量 + private static final int UNCOMPRESSED_PACKET_LIMIT = 512 * 1024; + /// 包的初始缓冲区大小 + private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; + private final ServerPlayer player; + private final ServerState state; + private final RegistryAccess registries; + + private final List packets = new ArrayList<>(); + + private int head = -1; + private int encodedCount = 0; + @Nullable + private RegistryFriendlyByteBuf buf; + + private FullSyncer(ServerPlayer player, ServerState state, RegistryAccess registries) { + this.player = player; + this.state = state; + this.registries = registries; + } + + public static void sync(ServerPlayer player, ServerState state, RegistryAccess registries) { + new FullSyncer(player, state, registries).sync(); + } + + private void sync() { + Map changes = this.state.getChanges(); + + // 找到最大值 + int max = -1; + for (int index : changes.keySet()) { + if (index > max) { + max = index; + } + } + if (max == -1) { + return; + } + + // 遍历 + for (int i = 0; i < max; i++) { + RegistryFriendlyByteBuf data = this.ensureBuf(); + + UnlimitedItemStack stack = changes.get(i); + // 物品栈为空 + if (stack == null || stack.isEmpty()) { + // 已写入数据则尝试刷入下一个包 + if (data.writerIndex() > 0) { + this.flush(); + } + if (stack.isEmpty()) { + changes.remove(i); + } + continue; + } + + // 只有当包内存超过约 2 兆字节时才会报错, + // 如果任何物品栈在其共享标签中写入了这么多垃圾数据,崩溃是可以接受的。 + // 我们通常会更早(32k数据)刷入下一个包 + UnlimitedItemStack.STREAM_CODEC.encode(data, stack); + this.head = i; + this.encodedCount++; + + if (data.writerIndex() >= UNCOMPRESSED_PACKET_LIMIT || this.encodedCount >= Short.MAX_VALUE) { + this.flush(); + } + } + + // 发包 + if (!this.packets.isEmpty()) { + Sync2CFull[] array = this.packets.toArray(new Sync2CFull[0]); + int length = array.length - 1; + Sync2CFull[] dest = new Sync2CFull[length]; + System.arraycopy(array, 0, dest, 1, length); + PacketDistributor.sendToPlayer(this.player, this.packets.getFirst(), dest); + } + } + + private void flush() { + if (this.buf != null) { + // Build a packet and queue it + var packet = new Sync2CFull(this.state.getId(), this.head, this.encodedCount, this.buf); + this.packets.add(packet); + + // Reset + this.encodedCount = 0; + this.buf = null; + } + } + + private RegistryFriendlyByteBuf ensureBuf() { + if (this.buf == null) { + this.buf = new RegistryFriendlyByteBuf( + Unpooled.buffer(INITIAL_BUFFER_CAPACITY), + this.registries, + ConnectionType.NEOFORGE + ); + } + return this.buf; + } + } + + public record Sync2CIncremental( + UUID id, + @Nullable Map stacks, + int encodedCount, + @Nullable RegistryFriendlyByteBuf buf + ) implements IClientboundPacket { + public static final Type TYPE = StoragePackets.of("sync2c_incremental"); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + Sync2CIncremental::encode, + Sync2CIncremental::decode + ); + public static final StreamCodec> STACKS_STREAM_CODEC = ByteBufCodecs.map( + HashMap::new, + ByteBufCodecs.VAR_INT, + UnlimitedItemStack.STREAM_CODEC + ); + + /// 服务器侧 + private Sync2CIncremental(UUID id, int encodedCount, RegistryFriendlyByteBuf buf) { + this(id, null, encodedCount, buf); + } + + /// 客户端侧 + private Sync2CIncremental(UUID id, Map stacks) { + this(id, stacks, stacks.size(), null); + } + + private static Sync2CIncremental decode(RegistryFriendlyByteBuf buf) { + return new Sync2CIncremental( + buf.readUUID(), + Sync2CIncremental.STACKS_STREAM_CODEC.decode(buf) + ); + } + + private static void encode(RegistryFriendlyByteBuf buf, Sync2CIncremental data) { + buf.writeUUID(data.id); + buf.writeVarInt(data.encodedCount); + if (data.buf == null) { + throw new UnsupportedOperationException("Use the IncrementalSyncer"); + } + buf.ensureWritable(data.buf.readableBytes()); + buf.setBytes(buf.writerIndex(), data.buf, data.buf.readableBytes()); + } + + @Override + public Type type() { + return TYPE; + } + + @Override + public void handleOnClient(Player player) { + MenuState.get(this.id).sync(this.stacks); + } + } + + public static class IncrementalSyncer { + /// 单个包的最大容量 + private static final int UNCOMPRESSED_PACKET_LIMIT = 512 * 1024; + /// 包的初始缓冲区大小 + private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; + private final ServerPlayer player; + private final ServerState state; + private final RegistryAccess registries; + + private final List packets = new ArrayList<>(); + + private int encodedCount = 0; + @Nullable + private RegistryFriendlyByteBuf buf; + + private IncrementalSyncer(ServerPlayer player, ServerState state, RegistryAccess registries) { + this.player = player; + this.state = state; + this.registries = registries; + } + + public static void sync(ServerPlayer player, ServerState state, RegistryAccess registries) { + new IncrementalSyncer(player, state, registries).sync(); + } + + private void sync() { + Map changes = this.state.getChanges(); + + // 遍历 + for (Iterator iterator = changes.keySet().iterator(); iterator.hasNext(); ) { + int i = iterator.next(); + RegistryFriendlyByteBuf data = this.ensureBuf(); + + UnlimitedItemStack stack = changes.get(i); + // 物品栈为空则直接继续 + if (stack == null || stack.isEmpty()) { + iterator.remove(); + continue; + } + + // 只有当包内存超过约 2 兆字节时才会报错, + // 如果任何物品栈在其共享标签中写入了这么多垃圾数据,崩溃是可以接受的。 + // 我们通常会更早(32k数据)刷入下一个包 + data.writeVarInt(i); + UnlimitedItemStack.STREAM_CODEC.encode(data, stack); + this.encodedCount++; + iterator.remove(); + + if (data.writerIndex() >= UNCOMPRESSED_PACKET_LIMIT || this.encodedCount >= Short.MAX_VALUE) { + this.flush(); + } + } + + // 发包 + if (!this.packets.isEmpty()) { + Sync2CIncremental[] array = this.packets.toArray(new Sync2CIncremental[0]); + int length = array.length - 1; + Sync2CIncremental[] dest = new Sync2CIncremental[length]; + System.arraycopy(array, 0, dest, 1, length); + PacketDistributor.sendToPlayer(this.player, this.packets.getFirst(), dest); + } + } + + private void flush() { + if (this.buf != null) { + // Build a packet and queue it + var packet = new Sync2CIncremental(this.state.getId(), this.encodedCount, this.buf); + this.packets.add(packet); + + // Reset + this.encodedCount = 0; + this.buf = null; + } + } + + private RegistryFriendlyByteBuf ensureBuf() { + if (this.buf == null) { + this.buf = new RegistryFriendlyByteBuf( + Unpooled.buffer(INITIAL_BUFFER_CAPACITY), + this.registries, + ConnectionType.NEOFORGE + ); + } + return this.buf; + } + } + + public record SyncSlots(UUID id, IntList slots) implements IServerboundPacket { + public static final Type TYPE = StoragePackets.of("sync_slots"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + SyncSlots::id, + ByteBufCodecs.VAR_INT.apply(ByteBufCodecs.list()) + .map(IntArrayList::new, Function.identity()), + SyncSlots::slots, + SyncSlots::new + ); + + @Override + public Type type() { + return SyncSlots.TYPE; + } + + @Override + public void handleOnServer(Player player) { + MenuState.get(this.id).sync(this.slots); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java index 8c681dcad0..6c420d86ed 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java @@ -1,19 +1,31 @@ package dev.dubhe.anvilcraft.saved.storage; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.util.Util; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; import dev.dubhe.anvilcraft.saved.BetterSavedData; -import dev.dubhe.anvilcraft.saved.storage.category.Categories; import lombok.Getter; import net.minecraft.core.RegistryAccess; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; @Getter public abstract class BaseStorage extends BetterSavedData { + public static final MapCodec CODEC = StorageType.CODEC + .dispatchMap(StorageType::find, StorageType::codec); + public static final StreamCodec STREAM_CODEC = StorageType.STREAM_CODEC + .cast() + .dispatch(StorageType::find, StorageType::streamCodec); private final TypeLimitItemStacksResourceHandler items = this.constructItemHandler(); - private final Categories categories = new Categories(); protected abstract TypeLimitItemStacksResourceHandler constructItemHandler(); + protected T sync(TypeLimitItemStacksResourceHandler items) { + this.items.sync(items); + return Util.cast(this); + } + @Override protected void registerDataFixers() { } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java new file mode 100644 index 0000000000..52bb302265 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java @@ -0,0 +1,29 @@ +package dev.dubhe.anvilcraft.saved.storage; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; + +public class CrateStorage extends BaseStorage { + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.CODEC + .forGetter(CrateStorage::getItems), + CrateStorage::of + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STREAM_CODEC, + CrateStorage::getItems, + CrateStorage::of + ); + + private static CrateStorage of(TypeLimitItemStacksResourceHandler items) { + return new CrateStorage().sync(items); + } + + @Override + protected TypeLimitItemStacksResourceHandler constructItemHandler() { + return new TypeLimitItemStacksResourceHandler(2048); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java new file mode 100644 index 0000000000..7ed4eaec61 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java @@ -0,0 +1,29 @@ +package dev.dubhe.anvilcraft.saved.storage; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; + +public class HyperdimensionStorage extends BaseStorage { + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.CODEC + .forGetter(HyperdimensionStorage::getItems), + HyperdimensionStorage::of + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STREAM_CODEC, + HyperdimensionStorage::getItems, + HyperdimensionStorage::of + ); + + private static HyperdimensionStorage of(TypeLimitItemStacksResourceHandler items) { + return new HyperdimensionStorage().sync(items); + } + + @Override + protected TypeLimitItemStacksResourceHandler constructItemHandler() { + return new TypeLimitItemStacksResourceHandler(65536, 65536); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java index d9ffd1508d..e73a53a004 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java @@ -1,8 +1,27 @@ package dev.dubhe.anvilcraft.saved.storage; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; public class LargeCrateStorage extends BaseStorage { + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.CODEC + .forGetter(LargeCrateStorage::getItems), + LargeCrateStorage::of + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STREAM_CODEC, + LargeCrateStorage::getItems, + LargeCrateStorage::of + ); + + private static LargeCrateStorage of(TypeLimitItemStacksResourceHandler items) { + return new LargeCrateStorage().sync(items); + } + @Override protected TypeLimitItemStacksResourceHandler constructItemHandler() { return new TypeLimitItemStacksResourceHandler(65536); diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java new file mode 100644 index 0000000000..a3213b9195 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java @@ -0,0 +1,29 @@ +package dev.dubhe.anvilcraft.saved.storage; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; + +public class ShulkerContainerStorage extends BaseStorage { + public static final MapCodec CODEC = CodecUtil.mapCodec( + TypeLimitItemStacksResourceHandler.CODEC + .forGetter(ShulkerContainerStorage::getItems), + ShulkerContainerStorage::of + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + TypeLimitItemStacksResourceHandler.STREAM_CODEC, + ShulkerContainerStorage::getItems, + ShulkerContainerStorage::of + ); + + private static ShulkerContainerStorage of(TypeLimitItemStacksResourceHandler items) { + return new ShulkerContainerStorage().sync(items); + } + + @Override + protected TypeLimitItemStacksResourceHandler constructItemHandler() { + return new TypeLimitItemStacksResourceHandler(65536, 65536); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java new file mode 100644 index 0000000000..0ee2583707 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java @@ -0,0 +1,74 @@ +package dev.dubhe.anvilcraft.saved.storage; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.StreamCodecUtil; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.util.StringRepresentable; + +import java.util.Locale; + +public enum StorageType implements StringRepresentable { + CRATE, + LARGE_CRATE, + SHULKER_CONTAINER, + HYPERDIMENSION, + ; + + public static final Codec CODEC = StringRepresentable.fromEnum(StorageType::values); + public static final StreamCodec STREAM_CODEC = StreamCodecUtil.enumStreamCodec(StorageType.class); + + public static StorageType find(Class clazz) { + return switch (clazz.getSimpleName()) { + case "CrateStorage" -> StorageType.CRATE; + case "LargeCrateStorage" -> StorageType.LARGE_CRATE; + case "ShulkerContainerStorage" -> StorageType.SHULKER_CONTAINER; + case "HyperdimensionStorage" -> StorageType.HYPERDIMENSION; + default -> throw new IllegalStateException("Unexpected storage class: " + clazz.getTypeName()); + }; + } + + public static StorageType find(BaseStorage storage) { + return switch (storage) { + case CrateStorage _ -> StorageType.CRATE; + case LargeCrateStorage _ -> StorageType.LARGE_CRATE; + case ShulkerContainerStorage _ -> StorageType.SHULKER_CONTAINER; + case HyperdimensionStorage _ -> StorageType.HYPERDIMENSION; + default -> throw new IllegalStateException("Unexpected storage: " + storage); + }; + } + + public MapCodec codec() { + return switch (this) { + case CRATE -> CrateStorage.CODEC; + case LARGE_CRATE -> LargeCrateStorage.CODEC; + case SHULKER_CONTAINER -> ShulkerContainerStorage.CODEC; + case HYPERDIMENSION -> HyperdimensionStorage.CODEC; + }; + } + + public StreamCodec streamCodec() { + return switch (this) { + case CRATE -> CrateStorage.STREAM_CODEC; + case LARGE_CRATE -> LargeCrateStorage.STREAM_CODEC; + case SHULKER_CONTAINER -> ShulkerContainerStorage.STREAM_CODEC; + case HYPERDIMENSION -> HyperdimensionStorage.STREAM_CODEC; + }; + } + + public BaseStorage newInstance() { + return switch (this) { + case CRATE -> new CrateStorage(); + case LARGE_CRATE -> new LargeCrateStorage(); + case SHULKER_CONTAINER -> new ShulkerContainerStorage(); + case HYPERDIMENSION -> new HyperdimensionStorage(); + }; + } + + @Override + public String getSerializedName() { + return this.name().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java new file mode 100644 index 0000000000..96e59c5399 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java @@ -0,0 +1,85 @@ +package dev.dubhe.anvilcraft.saved.storage; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.anvilcraft.lib.v2.util.Util; +import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.util.recover.RecoverStation; +import lombok.Getter; +import net.minecraft.core.UUIDUtil; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.world.level.saveddata.SavedDataType; +import net.minecraft.world.level.storage.SavedDataStorage; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.jspecify.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +@Getter +public class Storages extends SavedData { + public static final Identifier ID = AnvilCraft.of("storages"); + public static final MapCodec CODEC = CodecUtil.mapCodec( + Codec.unboundedMap(UUIDUtil.CODEC, BaseStorage.CODEC.codec()) + .fieldOf("storages") + .forGetter(Storages::getStorages), + RecoverStation.codec(BaseStorage.CODEC) + .fieldOf("recover") + .forGetter(Storages::getRecover), + Storages::new + ); + public static final SavedDataType TYPE = new SavedDataType<>(Storages.ID, Storages::new, Storages.CODEC.codec()); + public static final Storages CLIENT_COPY = new Storages(); + private final Map storages; + private final RecoverStation recover; + + private Storages() { + this(new HashMap<>(), RecoverStation.create(AnvilCraft.CONFIG.storageRecoverMaxSize)); + } + + private Storages(Map storages, RecoverStation recover) { + this.storages = storages; + this.recover = recover; + } + + public static Storages get() { + if (Util.isServer()) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server != null) { + ServerLevel overworld = server.overworld(); + SavedDataStorage storage = overworld.getDataStorage(); + return storage.computeIfAbsent(Storages.TYPE); + } + } else { + throw new IllegalStateException("Cannot access Storages on client"); + } + } + + public Optional get(UUID id) { + return Optional.ofNullable(this.storages.get(id)); + } + + public Optional get(UUID id, Class clazz) { + return Util.castSafely(this.storages.get(id), clazz); + } + + public T getOrCreate(UUID id, Class clazz) { + AtomicReference<@Nullable BaseStorage> storage = new AtomicReference<>(this.storages.get(id)); + if (storage.get() == null) { + storage.set(StorageType.find(clazz).newInstance()); + this.storages.put(id, storage.get()); + + } + return Util.castSafely(storage, clazz).orElseThrow(() -> new IllegalArgumentException( + "Storage with id '%s' cannot be cast to expected type '%s'. Actual type: %s" + .formatted(id, clazz.getName(), storage.get().getClass().getName()) + )); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/category/Categories.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/category/Categories.java index 3b71a48a28..001c7af7a0 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/category/Categories.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/category/Categories.java @@ -5,6 +5,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; @@ -28,6 +31,13 @@ public class Categories implements ValueIOSerializable { .forGetter(Categories::getEnabled), Categories::new ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ICategory.STREAM_CODEC.apply(ByteBufCodecs.list()), + Categories::getCustom, + ICategory.STREAM_CODEC.apply(ByteBufCodecs.list()), + Categories::getEnabled, + Categories::new + ); private List custom; private List enabled; @@ -51,6 +61,11 @@ public void addCustom(ItemStack filter) { this.custom.add(FilterCategory.from(filter)); } + public void sync(Categories categories) { + this.custom = categories.custom; + this.enabled = categories.enabled; + } + @Override public void serialize(ValueOutput output) { output.store("categories", Categories.CODEC.codec(), this); diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java new file mode 100644 index 0000000000..804300ce9e --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java @@ -0,0 +1,59 @@ +package dev.dubhe.anvilcraft.saved.storage.network; + +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/// 双端界面状态,用于同步双端界面数据 +public class MenuState { + private static final Map STATES = new HashMap<>(); + + public static MenuState get(UUID id) { + return MenuState.STATES.computeIfAbsent(id, MenuState::new); + } + + public static void clear() { + MenuState.STATES.clear(); + } + + private final UUID id; + private final IntList slots; + private final ArrayList mapping; + + public MenuState(UUID id) { + this.id = id; + this.slots = new IntArrayList(); + this.mapping = new ArrayList<>(); + } + + public void sync(IntList slots) { + this.slots.clear(); + this.slots.addAll(slots); + } + + public void sync(int head, List stacks) { + this.mapping.ensureCapacity(head); + + int size = stacks.size(); + for (int i = head; i < head + size; i++) { + if (this.mapping.get(i) == null) { + this.mapping.add(i, stacks.get(i)); + } else { + this.mapping.set(i, stacks.get(i)); + } + } + } + + public void sync(Map stacks) { + for (int index : stacks.keySet()) { + this.mapping.ensureCapacity(index); + this.mapping.set(index, stacks.get(index)); + } + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java new file mode 100644 index 0000000000..a8a6f838ad --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java @@ -0,0 +1,31 @@ +package dev.dubhe.anvilcraft.saved.storage.network; + +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; +import dev.dubhe.anvilcraft.saved.storage.BaseStorage; +import dev.dubhe.anvilcraft.saved.storage.Storages; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Getter +public class ServerState { + private static final Map STATES = new HashMap<>(); + + public static ServerState get(UUID id) { + return ServerState.STATES.computeIfAbsent(id, ServerState::new); + } + + private final UUID id; + private final Map changes; + + public ServerState(UUID id) { + this.id = id; + this.changes = new HashMap<>(); + } + + public BaseStorage getStorage() { + return Storages.get().get(this.id).orElseThrow(() -> new IllegalStateException("Trying to create state with an unbounded id")); + } +} From 263449000c922e887f6c57a1e17fdec3b48b73b2 Mon Sep 17 00:00:00 2001 From: QiuShui1012 Date: Sun, 21 Jun 2026 03:14:40 +0800 Subject: [PATCH 2/7] feat(storage): enhance storage system with UUID support and synchronization improvements --- .../api/energy/IEnergyHandlerHolder.java | 10 +++ .../api/fluid/IFluidHandlerHolder.java | 2 +- .../TypeLimitItemStacksResourceHandler.java | 71 +++++++++++++------ .../block/container/storage/CrateBlock.java | 2 + .../HyperdimensionStorageStationBlock.java | 2 + .../container/storage/LargeCrateBlock.java | 2 + .../storage/ShulkerContainerBlock.java | 2 + .../block/entity/FeCollectorBlockEntity.java | 6 +- .../entity/PowerConverterBlockEntity.java | 6 +- .../client/gui/screen/StorageScreen.java | 5 +- .../anvilcraft/init/ModCapabilities.java | 65 ++++++++++------- .../anvilcraft/inventory/StorageMenu.java | 2 +- .../utility/EnergyWeaponPlatformItem.java | 2 + .../network/multiple/StoragePackets.java | 68 +++++++++++------- .../anvilcraft/saved/storage/BaseStorage.java | 23 +++++- .../saved/storage/CrateStorage.java | 29 ++++++-- .../saved/storage/HyperdimensionStorage.java | 29 ++++++-- .../saved/storage/LargeCrateStorage.java | 29 ++++++-- .../storage/ShulkerContainerStorage.java | 29 ++++++-- .../anvilcraft/saved/storage/StorageType.java | 20 ++++-- .../anvilcraft/saved/storage/Storages.java | 36 +++++----- .../saved/storage/network/MenuState.java | 20 ++++++ .../saved/storage/network/ServerState.java | 31 -------- 23 files changed, 340 insertions(+), 151 deletions(-) create mode 100644 src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java delete mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java diff --git a/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java b/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java new file mode 100644 index 0000000000..ea48d3c0eb --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/energy/IEnergyHandlerHolder.java @@ -0,0 +1,10 @@ +package dev.dubhe.anvilcraft.api.energy; + +import net.minecraft.core.Direction; +import net.neoforged.neoforge.transfer.energy.EnergyHandler; +import org.jspecify.annotations.Nullable; + +/// 持有 EnergyHandler 的 +public interface IEnergyHandlerHolder { + @Nullable EnergyHandler getEnergyHandler(@Nullable Direction direction); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java b/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java index 8bb1560e7a..84ce373a7b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/fluid/IFluidHandlerHolder.java @@ -3,7 +3,7 @@ import net.neoforged.neoforge.transfer.ResourceHandler; import net.neoforged.neoforge.transfer.fluid.FluidResource; -/// 持有FluidTank的 +/// 持有 FluidTank 的 public interface IFluidHandlerHolder { ResourceHandler getFluidHandler(); } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java index 01aba84511..13bd9f02da 100644 --- a/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java +++ b/src/main/java/dev/dubhe/anvilcraft/api/itemhandler/TypeLimitItemStacksResourceHandler.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Function; import java.util.function.IntUnaryOperator; @Getter(AccessLevel.PRIVATE) @@ -33,9 +32,9 @@ public class TypeLimitItemStacksResourceHandler implements ResourceHandler> STACKS_CODEC = UnlimitedItemStack.CODEC + public static final Codec> STACKS_CODEC = UnlimitedItemStack.OPTIONAL_CODEC .listOf() - .xmap(TypeLimitItemStacksResourceHandler::constructStackList, Function.identity()); + .xmap(TypeLimitItemStacksResourceHandler::constructStackList, TypeLimitItemStacksResourceHandler::trim); public static final MapCodec CODEC = CodecUtil.mapCodec( TypeLimitItemStacksResourceHandler.STACKS_CODEC .fieldOf(TypeLimitItemStacksResourceHandler.STACKS_KEY) @@ -51,7 +50,7 @@ public class TypeLimitItemStacksResourceHandler implements ResourceHandler> STACKS_STREAM_CODEC = UnlimitedItemStack .STREAM_CODEC .apply(ByteBufCodecs.list()) - .map(TypeLimitItemStacksResourceHandler::constructStackList, Function.identity()); + .map(TypeLimitItemStacksResourceHandler::constructStackList, TypeLimitItemStacksResourceHandler::trim); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( TypeLimitItemStacksResourceHandler.STACKS_STREAM_CODEC, TypeLimitItemStacksResourceHandler::getStacks, @@ -95,19 +94,36 @@ private void initStacks(NonNullList stacks) { int space = TypeLimitItemStacksResourceHandler.computeSpace(stack, stack.count()); // 塞得下整个栈,塞完下一个 - if (this.space + space <= this.spaceSize) { + if (this.spaceSize == Integer.MAX_VALUE || this.space + space <= this.spaceSize) { this.space += space; + for (int i = 0; i < this.stacks.size(); i++) { + UnlimitedItemStack exist = this.stacks.get(i); + if (exist.isSameItemSameComponents(stack)) { + UnlimitedItemStack original = exist.copy(); + exist.setCount(stack.count()); + this.onContentsChanged(i, original); + continue MAIN; + } + } this.stacks.add(stack); continue; } // 塞不下整个栈,尝试找到能塞下的数量 - for (int i = stack.count() - 1; i >= 0; i--) { space = TypeLimitItemStacksResourceHandler.computeSpace(stack, i); // 找到了,塞完下一个 if (this.space + space <= this.spaceSize) { this.space += space; + for (int index = 0; index < this.stacks.size(); index++) { + UnlimitedItemStack exist = this.stacks.get(index); + if (exist.isSameItemSameComponents(stack)) { + UnlimitedItemStack original = exist.copy(); + exist.setCount(stack.count()); + this.onContentsChanged(index, original); + continue MAIN; + } + } this.stacks.add(stack); continue MAIN; } @@ -149,9 +165,13 @@ public long getAmountAsLong(int index) { return this.stacks.get(index).getCount(); } + public double getFullness() { + return (double) this.space / this.spaceSize; + } + @Override public long getCapacityAsLong(int index, ItemResource resource) { - return Long.MAX_VALUE; + return TypeLimitItemStacksResourceHandler.computeSpace(resource, this.spaceSize); } @Override @@ -160,19 +180,21 @@ public boolean isValid(int index, ItemResource resource) { } protected int computeEmptySize(ItemResource resource) { + if (this.spaceSize == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } return TypeLimitItemStacksResourceHandler.computeCount(resource, this.spaceSize - this.space); } protected int computeEmptySize(ItemInstance instance) { + if (this.spaceSize == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } return TypeLimitItemStacksResourceHandler.computeCount(instance, this.spaceSize - this.space); } - protected int findEmptySlot() { - for (int i = 0; i < this.stacks.size(); i++) { - if (this.stacks.get(i).isEmpty()) { - return i; - } - } + protected int findNewSlot() { + // 虽然种类限制为 Integer.MAX_VALUE 时视为无上限,但 Java 底层限制不允许 ArrayList 的大小超过 Integer.MAX_VALUE return this.stacks.size() >= this.typeLimit ? -1 : this.stacks.size(); } @@ -188,7 +210,7 @@ public int insert(ItemResource resource, int amount, TransactionContext transact } } - int index = this.findEmptySlot(); + int index = this.findNewSlot(); if (index < 0) { return 0; } @@ -214,7 +236,7 @@ private BoolAndInt insertInternal(int index, ItemResource resource, int amount, TransferPreconditions.checkNonEmptyNonNegative(resource, amount); UnlimitedItemStack stack = this.stacks.get(index); - if (!stack.isSameItemSameComponents(resource)) { + if (!stack.isEmpty() && !stack.isSameItemSameComponents(resource)) { return new BoolAndInt(false, 0); } @@ -236,11 +258,11 @@ public int extract(int index, ItemResource resource, int amount, TransactionCont TransferPreconditions.checkNonEmptyNonNegative(resource, amount); UnlimitedItemStack stack = this.stacks.get(index); - int count = stack.count(); if (!stack.isSameItemSameComponents(resource)) { return 0; } + int count = stack.count(); int extracted = Math.min(amount, count); if (extracted <= 0) { return 0; @@ -249,7 +271,7 @@ public int extract(int index, ItemResource resource, int amount, TransactionCont this.snapshotJournals.get(index).updateSnapshots(transaction); this.stacks.set(index, new UnlimitedItemStack(resource, count - extracted)); this.space -= TypeLimitItemStacksResourceHandler.computeSpace(resource, extracted); - return amount; + return extracted; } private void updateStacksSize() { @@ -271,15 +293,20 @@ public void sync(TypeLimitItemStacksResourceHandler items) { this.initStacks(items.stacks); } - @Override - public void serialize(ValueOutput output) { - NonNullList saving = TypeLimitItemStacksResourceHandler.constructStackList(); - for (UnlimitedItemStack stack : this.stacks) { + private static NonNullList trim(NonNullList list) { + NonNullList result = TypeLimitItemStacksResourceHandler.constructStackList(); + for (UnlimitedItemStack stack : list) { if (stack.isEmpty()) { continue; } - saving.add(stack); + result.add(stack); } + return result; + } + + @Override + public void serialize(ValueOutput output) { + NonNullList saving = TypeLimitItemStacksResourceHandler.trim(this.stacks); output.store(TypeLimitItemStacksResourceHandler.STACKS_KEY, TypeLimitItemStacksResourceHandler.STACKS_CODEC, saving); output.putInt(TypeLimitItemStacksResourceHandler.TYPE_LIMIT_KEY, this.typeLimit); output.putInt(TypeLimitItemStacksResourceHandler.SPACE_SIZE_KEY, this.spaceSize); diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java index e3198f0f59..0c7cd3d237 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/CrateBlock.java @@ -3,6 +3,7 @@ import dev.dubhe.anvilcraft.block.entity.storage.CrateBlockEntity; import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; @@ -59,6 +60,7 @@ protected InteractionResult useItemOn( if (blockEntity instanceof CrateBlockEntity entity) { if (player instanceof ServerPlayer serverPlayer) { if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); ModMenuTypes.open(serverPlayer, entity, pos); return InteractionResult.SUCCESS_SERVER; } else { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java index d349454a89..bc7a3044c2 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/HyperdimensionStorageStationBlock.java @@ -8,6 +8,7 @@ import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerPlayer; @@ -105,6 +106,7 @@ protected InteractionResult useItemOn( if (blockEntity instanceof HyperdimensionStorageStationBlockEntity entity) { if (player instanceof ServerPlayer serverPlayer) { if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); ModMenuTypes.open(serverPlayer, entity, pos); return InteractionResult.SUCCESS_SERVER; } else { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java index 5f7c797e69..9f2647c986 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/LargeCrateBlock.java @@ -8,6 +8,7 @@ import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.server.level.ServerPlayer; @@ -105,6 +106,7 @@ protected InteractionResult useItemOn( if (blockEntity instanceof LargeCrateBlockEntity entity) { if (player instanceof ServerPlayer serverPlayer) { if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); ModMenuTypes.open(serverPlayer, entity, pos); return InteractionResult.SUCCESS_SERVER; } else { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java index 83ac169996..f71e47a6d3 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/container/storage/ShulkerContainerBlock.java @@ -10,6 +10,7 @@ import dev.dubhe.anvilcraft.block.state.OpenedCube3x3PartHalf; import dev.dubhe.anvilcraft.init.ModMenuTypes; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; +import dev.dubhe.anvilcraft.network.multiple.StoragePackets; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; @@ -230,6 +231,7 @@ protected InteractionResult useItemOn( if (blockEntity instanceof ShulkerContainerBlockEntity entity) { if (player instanceof ServerPlayer serverPlayer) { if (serverPlayer.gameMode.getGameModeForPlayer() == GameType.SPECTATOR) return InteractionResult.PASS; + StoragePackets.sync(serverPlayer, entity.getId(), serverPlayer.registryAccess()); ModMenuTypes.open(serverPlayer, entity, pos); return InteractionResult.SUCCESS_SERVER; } else { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java index aa0ad07c37..8128969e75 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/FeCollectorBlockEntity.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.block.entity; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.energy.IEnergyHandlerHolder; import dev.dubhe.anvilcraft.api.power.IPowerProducer; import dev.dubhe.anvilcraft.api.power.PowerGrid; import dev.dubhe.anvilcraft.api.tooltip.providers.IHasAffectRange; @@ -30,7 +31,7 @@ import net.neoforged.neoforge.transfer.transaction.TransactionContext; import org.jspecify.annotations.Nullable; -public class FeCollectorBlockEntity extends BlockEntity implements IPowerProducer, IHasAffectRange { +public class FeCollectorBlockEntity extends BlockEntity implements IPowerProducer, IHasAffectRange, IEnergyHandlerHolder { public static final int MAX_ENERGY = 1_000_000; static final int FE_PER_TICK = 10_000; public static final int PRODUCE_THRESHOLD = 400_000; @@ -115,8 +116,9 @@ Direction[] getConnectedSides() { : new Direction[]{Direction.NORTH, Direction.SOUTH}; } + @Override @Nullable - public EnergyHandler getEnergyStorage(@Nullable Direction side) { + public EnergyHandler getEnergyHandler(@Nullable Direction side) { if (side == null) return new FeEnergyStore(null); for (Direction d : this.getConnectedSides()) { if (d == side) return new FeEnergyStore(side); diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java index 9562ebbec4..04c4d01f93 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/PowerConverterBlockEntity.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.block.entity; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.energy.IEnergyHandlerHolder; import dev.dubhe.anvilcraft.api.power.IPowerConsumer; import dev.dubhe.anvilcraft.api.power.PowerGrid; import dev.dubhe.anvilcraft.block.power.converter.BasePowerConverterBlock; @@ -27,7 +28,7 @@ import net.neoforged.neoforge.transfer.transaction.TransactionContext; import org.jspecify.annotations.Nullable; -public class PowerConverterBlockEntity extends BlockEntity implements IPowerConsumer { +public class PowerConverterBlockEntity extends BlockEntity implements IPowerConsumer, IEnergyHandlerHolder { @Getter @Setter private @Nullable PowerGrid grid = null; @@ -54,7 +55,8 @@ int getMaxEnergy() { return this.inputPower * 10000; } - public @Nullable EnergyHandler getEnergyStorage(@Nullable Direction side) { + @Override + public @Nullable EnergyHandler getEnergyHandler(@Nullable Direction side) { if (side == null) return new PowerConverterEnergyStore(); if (side == getBlockState().getValue(BasePowerConverterBlock.FACING)) return new PowerConverterEnergyStore(); return null; diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java index e164c4462c..6522b7c7c3 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/screen/StorageScreen.java @@ -8,6 +8,7 @@ import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; +import net.minecraft.util.Mth; import net.minecraft.world.entity.player.Inventory; public class StorageScreen extends AbstractContainerScreen { @@ -44,11 +45,11 @@ public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mou graphics.blit( RenderPipelines.GUI_TEXTURED, StorageScreen.CAPACITY, - this.leftPos, + this.leftPos + 106, this.topPos, 0, 0, - 194 * this.menu.getState().get, + Mth.ceil(194 * this.menu.getState().getFullness()), 13, 194, 13 diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java b/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java index fd862830b9..295d5a4909 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModCapabilities.java @@ -1,28 +1,38 @@ package dev.dubhe.anvilcraft.init; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.energy.IEnergyHandlerHolder; import dev.dubhe.anvilcraft.api.energy.ItemFEStorage; import dev.dubhe.anvilcraft.api.fluid.IFluidHandlerHolder; import dev.dubhe.anvilcraft.api.itemhandler.IItemResourceHandlerHolder; import dev.dubhe.anvilcraft.api.itemhandler.SolidCauldronExtractor; import dev.dubhe.anvilcraft.block.cauldron.HoneyCauldronBlock; import dev.dubhe.anvilcraft.block.cauldron.ObsidianCauldronBlock; +import dev.dubhe.anvilcraft.block.entity.storage.StorageBlockEntity; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.item.ModItems; import dev.dubhe.anvilcraft.item.armor.IonoCraftBackpackItem; +import dev.dubhe.anvilcraft.item.utility.EnergyWeaponPlatformItem; import dev.dubhe.anvilcraft.item.weapon.AnvilRailgunItem; import dev.dubhe.anvilcraft.item.weapon.SpectralWeaponLauncherItem; +import dev.dubhe.anvilcraft.saved.storage.Storages; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.entity.BlockEntity; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.capabilities.Capabilities; +import net.neoforged.neoforge.capabilities.ICapabilityProvider; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.transfer.ResourceHandler; +import net.neoforged.neoforge.transfer.access.ItemAccess; +import net.neoforged.neoforge.transfer.energy.EnergyHandler; import net.neoforged.neoforge.transfer.fluid.BucketResourceHandler; import net.neoforged.neoforge.transfer.fluid.FluidResource; import net.neoforged.neoforge.transfer.item.ItemResource; +import org.jspecify.annotations.Nullable; @EventBusSubscriber(modid = AnvilCraft.MOD_ID) public class ModCapabilities { @@ -43,22 +53,27 @@ public static void registerCapabilities(final RegisterCapabilitiesEvent event) { event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.BURNING_HEATER.get(), ModCapabilities::item); event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.FISH_TANK.get(), ModCapabilities::item); + event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.CRATE.get(), ModCapabilities::item); + event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.LARGE_CRATE.get(), ModCapabilities::item); + event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.SHULKER_CONTAINER.get(), ModCapabilities::item); + event.registerBlockEntity(Capabilities.Item.BLOCK, ModBlockEntities.HYPERDIMENSION_STORAGE_STATION.get(), ModCapabilities::item); + event.registerBlock( Capabilities.Item.BLOCK, - ((level, pos, _, _, _) -> SolidCauldronExtractor.get( + (level, pos, _, _, _) -> SolidCauldronExtractor.get( level, pos, state -> state.getBlock() instanceof HoneyCauldronBlock && state.getValue(HoneyCauldronBlock.LEVEL) == 4 - )), + ), ModBlocks.HONEY_CAULDRON.get() ); event.registerBlock( Capabilities.Item.BLOCK, - ((level, pos, _, _, _) -> SolidCauldronExtractor.get( + (level, pos, _, _, _) -> SolidCauldronExtractor.get( level, pos, state -> state.getBlock() instanceof ObsidianCauldronBlock - )), + ), ModBlocks.OBSIDIAN_CAULDRON.get() ); @@ -68,44 +83,29 @@ public static void registerCapabilities(final RegisterCapabilitiesEvent event) { event.registerItem(Capabilities.Fluid.ITEM, (_, ctx) -> new BucketResourceHandler(ctx), Items.POWDER_SNOW_BUCKET); - // 武器物品注册 FE ITEM capability event.registerItem( Capabilities.Energy.ITEM, - (stack, ctx) -> ItemFEStorage.create(stack, ctx, AnvilRailgunItem.MAX_ENERGY), + ModCapabilities.energy(AnvilRailgunItem.MAX_ENERGY), ModItems.ANVIL_RAILGUN.get() ); event.registerItem( Capabilities.Energy.ITEM, - (stack, ctx) -> ItemFEStorage.create(stack, ctx, SpectralWeaponLauncherItem.MAX_ENERGY), + ModCapabilities.energy(SpectralWeaponLauncherItem.MAX_ENERGY), ModItems.SPECTRAL_WEAPON_LAUNCHER.get() ); - // 能量武器平台 event.registerItem( Capabilities.Energy.ITEM, - (stack, ctx) -> ItemFEStorage.create(stack, ctx, 640000000), + ModCapabilities.energy(EnergyWeaponPlatformItem.STORED_ENERGY), ModItems.ENERGY_WEAPON_PLATFORM.get() ); - - // 飘升机背包 FE capability event.registerItem( Capabilities.Energy.ITEM, - (stack, ctx) -> ItemFEStorage.create(stack, ctx, IonoCraftBackpackItem.MAX_ENERGY), + ModCapabilities.energy(IonoCraftBackpackItem.MAX_ENERGY), ModItems.IONOCRAFT_BACKPACK.get() ); - // 能量转换器 FE capability - event.registerBlockEntity( - Capabilities.Energy.BLOCK, - ModBlockEntities.POWER_CONVERTER.get(), - (be, direction) -> be.getEnergyStorage(direction) - ); - - // FE收集器 FE capability - event.registerBlockEntity( - Capabilities.Energy.BLOCK, - ModBlockEntities.FE_COLLECTOR.get(), - (be, direction) -> be.getEnergyStorage(direction) - ); + event.registerBlockEntity(Capabilities.Energy.BLOCK, ModBlockEntities.POWER_CONVERTER.get(), ModCapabilities::energy); + event.registerBlockEntity(Capabilities.Energy.BLOCK, ModBlockEntities.FE_COLLECTOR.get(), ModCapabilities::energy); } /// 物品 @@ -113,8 +113,23 @@ private static ResourceH return be.getItemHandler(); } + /// 存储容器的物品 + private static ResourceHandler item(T be, S ignored) { + return Storages.get().getOrCreate(be.getId(), be.getStorageType().clazz()).getItems(); + } + /// 流体 private static ResourceHandler fluid(T be, S ignored) { return be.getFluidHandler(); } + + /// 能量物品 + private static ICapabilityProvider energy(int capacity) { + return (stack, access) -> ItemFEStorage.create(stack, access, capacity); + } + + /// 能量 + private static EnergyHandler energy(T be, @Nullable Direction dir) { + return be.getEnergyHandler(dir); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java index 71d59cc96b..8f8655bd8d 100644 --- a/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java @@ -33,7 +33,7 @@ public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory in @Override public ItemStack quickMoveStack(Player player, int slotIndex) { - return null; + return this.getSlot(slotIndex).getItem(); } @Override diff --git a/src/main/java/dev/dubhe/anvilcraft/item/utility/EnergyWeaponPlatformItem.java b/src/main/java/dev/dubhe/anvilcraft/item/utility/EnergyWeaponPlatformItem.java index 5353cbd38f..55a23b6328 100644 --- a/src/main/java/dev/dubhe/anvilcraft/item/utility/EnergyWeaponPlatformItem.java +++ b/src/main/java/dev/dubhe/anvilcraft/item/utility/EnergyWeaponPlatformItem.java @@ -12,6 +12,8 @@ import net.minecraft.world.level.Level; public class EnergyWeaponPlatformItem extends Item { + public static final int STORED_ENERGY = 640_000_000; + public EnergyWeaponPlatformItem(Properties properties) { super(properties); } diff --git a/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java index 3b67d61c78..bdf928f035 100644 --- a/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java +++ b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java @@ -6,7 +6,6 @@ import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.AnvilCraft; import dev.dubhe.anvilcraft.saved.storage.network.MenuState; -import dev.dubhe.anvilcraft.saved.storage.network.ServerState; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -36,6 +35,28 @@ private static CustomPacketPayload.Type of(String id) { return IPacket.type(AnvilCraft.of("storage_" + id)); } + public record SyncSlots(UUID id, IntList slots) implements IServerboundPacket { + public static final Type TYPE = StoragePackets.of("sync_slots"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + SyncSlots::id, + ByteBufCodecs.VAR_INT.apply(ByteBufCodecs.list()) + .map(IntArrayList::new, Function.identity()), + SyncSlots::slots, + SyncSlots::new + ); + + @Override + public Type type() { + return SyncSlots.TYPE; + } + + @Override + public void handleOnServer(Player player) { + MenuState.get(this.id).sync(this.slots); + } + } + public record Sync2CFull( UUID id, int head, @@ -92,13 +113,17 @@ public void handleOnClient(Player player) { } } + public static void sync(ServerPlayer player, UUID id, RegistryAccess registries) { + new FullSyncer(player, MenuState.get(id), registries).sync(); + } + public static class FullSyncer { /// 单个包的最大容量 private static final int UNCOMPRESSED_PACKET_LIMIT = 512 * 1024; /// 包的初始缓冲区大小 private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; private final ServerPlayer player; - private final ServerState state; + private final MenuState state; private final RegistryAccess registries; private final List packets = new ArrayList<>(); @@ -108,16 +133,12 @@ public static class FullSyncer { @Nullable private RegistryFriendlyByteBuf buf; - private FullSyncer(ServerPlayer player, ServerState state, RegistryAccess registries) { + private FullSyncer(ServerPlayer player, MenuState state, RegistryAccess registries) { this.player = player; this.state = state; this.registries = registries; } - public static void sync(ServerPlayer player, ServerState state, RegistryAccess registries) { - new FullSyncer(player, state, registries).sync(); - } - private void sync() { Map changes = this.state.getChanges(); @@ -250,13 +271,17 @@ public void handleOnClient(Player player) { } } + public static void syncIncremental(ServerPlayer player, UUID id, RegistryAccess registries) { + new IncrementalSyncer(player, MenuState.get(id), registries).sync(); + } + public static class IncrementalSyncer { /// 单个包的最大容量 private static final int UNCOMPRESSED_PACKET_LIMIT = 512 * 1024; /// 包的初始缓冲区大小 private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; private final ServerPlayer player; - private final ServerState state; + private final MenuState state; private final RegistryAccess registries; private final List packets = new ArrayList<>(); @@ -265,16 +290,12 @@ public static class IncrementalSyncer { @Nullable private RegistryFriendlyByteBuf buf; - private IncrementalSyncer(ServerPlayer player, ServerState state, RegistryAccess registries) { + private IncrementalSyncer(ServerPlayer player, MenuState state, RegistryAccess registries) { this.player = player; this.state = state; this.registries = registries; } - public static void sync(ServerPlayer player, ServerState state, RegistryAccess registries) { - new IncrementalSyncer(player, state, registries).sync(); - } - private void sync() { Map changes = this.state.getChanges(); @@ -337,25 +358,24 @@ private RegistryFriendlyByteBuf ensureBuf() { } } - public record SyncSlots(UUID id, IntList slots) implements IServerboundPacket { - public static final Type TYPE = StoragePackets.of("sync_slots"); - public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + public record SyncFullness(UUID id, double fullness) implements IServerboundPacket { + public static final Type TYPE = StoragePackets.of("sync_fullness"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( UUIDUtil.STREAM_CODEC, - SyncSlots::id, - ByteBufCodecs.VAR_INT.apply(ByteBufCodecs.list()) - .map(IntArrayList::new, Function.identity()), - SyncSlots::slots, - SyncSlots::new + SyncFullness::id, + ByteBufCodecs.DOUBLE, + SyncFullness::fullness, + SyncFullness::new ); @Override - public Type type() { - return SyncSlots.TYPE; + public Type type() { + return SyncFullness.TYPE; } @Override public void handleOnServer(Player player) { - MenuState.get(this.id).sync(this.slots); + MenuState.get(this.id).setFullness(this.fullness); } } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java index 6c420d86ed..35305e7cdf 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java @@ -2,14 +2,19 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.util.Util; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; import dev.dubhe.anvilcraft.saved.BetterSavedData; +import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; import lombok.Getter; import net.minecraft.core.RegistryAccess; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import java.util.UUID; + @Getter public abstract class BaseStorage extends BetterSavedData { public static final MapCodec CODEC = StorageType.CODEC @@ -17,9 +22,23 @@ public abstract class BaseStorage extends BetterSavedData { public static final StreamCodec STREAM_CODEC = StorageType.STREAM_CODEC .cast() .dispatch(StorageType::find, StorageType::streamCodec); - private final TypeLimitItemStacksResourceHandler items = this.constructItemHandler(); + private final UUID id; + private final TypeLimitItemStacksResourceHandler items = this.constructItemHandler(this::onContentsChanged); + + protected BaseStorage(UUID id) { + this.id = id; + } - protected abstract TypeLimitItemStacksResourceHandler constructItemHandler(); + protected abstract TypeLimitItemStacksResourceHandler constructItemHandler( + IntObjectBiConsumer onContentsChanged + ); + + protected void onContentsChanged(int index, UnlimitedItemStack original) { + MenuState state = MenuState.get(this.id); + state.getChanges().put(index, original); + state.setFullness(this.items.getFullness()); + Storages.get().setDirty(); + } protected T sync(TypeLimitItemStacksResourceHandler items) { this.items.sync(items); diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java index 52bb302265..4bdc1b9bfe 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/CrateStorage.java @@ -2,28 +2,49 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; +import net.minecraft.core.UUIDUtil; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; +import java.util.UUID; + public class CrateStorage extends BaseStorage { public static final MapCodec CODEC = CodecUtil.mapCodec( + UUIDUtil.CODEC + .fieldOf("storage_id") + .forGetter(CrateStorage::getId), TypeLimitItemStacksResourceHandler.CODEC .forGetter(CrateStorage::getItems), CrateStorage::of ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + CrateStorage::getId, TypeLimitItemStacksResourceHandler.STREAM_CODEC, CrateStorage::getItems, CrateStorage::of ); - private static CrateStorage of(TypeLimitItemStacksResourceHandler items) { - return new CrateStorage().sync(items); + public CrateStorage(UUID id) { + super(id); + } + + private static CrateStorage of(UUID id, TypeLimitItemStacksResourceHandler items) { + CrateStorage storage = new CrateStorage(id); + storage.getItems().sync(items); + return storage; } @Override - protected TypeLimitItemStacksResourceHandler constructItemHandler() { - return new TypeLimitItemStacksResourceHandler(2048); + protected TypeLimitItemStacksResourceHandler constructItemHandler(IntObjectBiConsumer onContentsChanged) { + return new TypeLimitItemStacksResourceHandler(2048) { + @Override + protected void onContentsChanged(int index, UnlimitedItemStack original) { + onContentsChanged.accept(index, original); + } + }; } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java index 7ed4eaec61..9d028aedeb 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/HyperdimensionStorage.java @@ -2,28 +2,49 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; +import net.minecraft.core.UUIDUtil; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; +import java.util.UUID; + public class HyperdimensionStorage extends BaseStorage { public static final MapCodec CODEC = CodecUtil.mapCodec( + UUIDUtil.CODEC + .fieldOf("storage_id") + .forGetter(HyperdimensionStorage::getId), TypeLimitItemStacksResourceHandler.CODEC .forGetter(HyperdimensionStorage::getItems), HyperdimensionStorage::of ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + HyperdimensionStorage::getId, TypeLimitItemStacksResourceHandler.STREAM_CODEC, HyperdimensionStorage::getItems, HyperdimensionStorage::of ); - private static HyperdimensionStorage of(TypeLimitItemStacksResourceHandler items) { - return new HyperdimensionStorage().sync(items); + public HyperdimensionStorage(UUID id) { + super(id); + } + + private static HyperdimensionStorage of(UUID id, TypeLimitItemStacksResourceHandler items) { + HyperdimensionStorage storage = new HyperdimensionStorage(id); + storage.getItems().sync(items); + return storage; } @Override - protected TypeLimitItemStacksResourceHandler constructItemHandler() { - return new TypeLimitItemStacksResourceHandler(65536, 65536); + protected TypeLimitItemStacksResourceHandler constructItemHandler(IntObjectBiConsumer onContentsChanged) { + return new TypeLimitItemStacksResourceHandler(65536) { + @Override + protected void onContentsChanged(int index, UnlimitedItemStack original) { + onContentsChanged.accept(index, original); + } + }; } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java index e73a53a004..bab6c7e306 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/LargeCrateStorage.java @@ -2,28 +2,49 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; +import net.minecraft.core.UUIDUtil; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; +import java.util.UUID; + public class LargeCrateStorage extends BaseStorage { public static final MapCodec CODEC = CodecUtil.mapCodec( + UUIDUtil.CODEC + .fieldOf("storage_id") + .forGetter(LargeCrateStorage::getId), TypeLimitItemStacksResourceHandler.CODEC .forGetter(LargeCrateStorage::getItems), LargeCrateStorage::of ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + LargeCrateStorage::getId, TypeLimitItemStacksResourceHandler.STREAM_CODEC, LargeCrateStorage::getItems, LargeCrateStorage::of ); - private static LargeCrateStorage of(TypeLimitItemStacksResourceHandler items) { - return new LargeCrateStorage().sync(items); + public LargeCrateStorage(UUID id) { + super(id); + } + + private static LargeCrateStorage of(UUID id, TypeLimitItemStacksResourceHandler items) { + LargeCrateStorage storage = new LargeCrateStorage(id); + storage.getItems().sync(items); + return storage; } @Override - protected TypeLimitItemStacksResourceHandler constructItemHandler() { - return new TypeLimitItemStacksResourceHandler(65536); + protected TypeLimitItemStacksResourceHandler constructItemHandler(IntObjectBiConsumer onContentsChanged) { + return new TypeLimitItemStacksResourceHandler(65536) { + @Override + protected void onContentsChanged(int index, UnlimitedItemStack original) { + onContentsChanged.accept(index, original); + } + }; } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java index a3213b9195..2618c1ae69 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/ShulkerContainerStorage.java @@ -2,28 +2,49 @@ import com.mojang.serialization.MapCodec; import dev.anvilcraft.lib.v2.codec.CodecUtil; +import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; +import net.minecraft.core.UUIDUtil; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; +import java.util.UUID; + public class ShulkerContainerStorage extends BaseStorage { public static final MapCodec CODEC = CodecUtil.mapCodec( + UUIDUtil.CODEC + .fieldOf("storage_id") + .forGetter(ShulkerContainerStorage::getId), TypeLimitItemStacksResourceHandler.CODEC .forGetter(ShulkerContainerStorage::getItems), ShulkerContainerStorage::of ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + ShulkerContainerStorage::getId, TypeLimitItemStacksResourceHandler.STREAM_CODEC, ShulkerContainerStorage::getItems, ShulkerContainerStorage::of ); - private static ShulkerContainerStorage of(TypeLimitItemStacksResourceHandler items) { - return new ShulkerContainerStorage().sync(items); + public ShulkerContainerStorage(UUID id) { + super(id); + } + + private static ShulkerContainerStorage of(UUID id, TypeLimitItemStacksResourceHandler items) { + ShulkerContainerStorage storage = new ShulkerContainerStorage(id); + storage.getItems().sync(items); + return storage; } @Override - protected TypeLimitItemStacksResourceHandler constructItemHandler() { - return new TypeLimitItemStacksResourceHandler(65536, 65536); + protected TypeLimitItemStacksResourceHandler constructItemHandler(IntObjectBiConsumer onContentsChanged) { + return new TypeLimitItemStacksResourceHandler(65536, 65536) { + @Override + protected void onContentsChanged(int index, UnlimitedItemStack original) { + onContentsChanged.accept(index, original); + } + }; } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java index 0ee2583707..f9151683e7 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/StorageType.java @@ -9,6 +9,7 @@ import net.minecraft.util.StringRepresentable; import java.util.Locale; +import java.util.UUID; public enum StorageType implements StringRepresentable { CRATE, @@ -58,12 +59,21 @@ public MapCodec codec() { }; } - public BaseStorage newInstance() { + public BaseStorage newInstance(UUID id) { return switch (this) { - case CRATE -> new CrateStorage(); - case LARGE_CRATE -> new LargeCrateStorage(); - case SHULKER_CONTAINER -> new ShulkerContainerStorage(); - case HYPERDIMENSION -> new HyperdimensionStorage(); + case CRATE -> new CrateStorage(id); + case LARGE_CRATE -> new LargeCrateStorage(id); + case SHULKER_CONTAINER -> new ShulkerContainerStorage(id); + case HYPERDIMENSION -> new HyperdimensionStorage(id); + }; + } + + public Class clazz() { + return switch (this) { + case CRATE -> CrateStorage.class; + case LARGE_CRATE -> LargeCrateStorage.class; + case SHULKER_CONTAINER -> ShulkerContainerStorage.class; + case HYPERDIMENSION -> HyperdimensionStorage.class; }; } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java index 96e59c5399..3f607e8868 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/Storages.java @@ -15,19 +15,17 @@ import net.minecraft.world.level.saveddata.SavedDataType; import net.minecraft.world.level.storage.SavedDataStorage; import net.neoforged.neoforge.server.ServerLifecycleHooks; -import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; @Getter public class Storages extends SavedData { public static final Identifier ID = AnvilCraft.of("storages"); public static final MapCodec CODEC = CodecUtil.mapCodec( - Codec.unboundedMap(UUIDUtil.CODEC, BaseStorage.CODEC.codec()) + Codec.unboundedMap(UUIDUtil.STRING_CODEC, BaseStorage.CODEC.codec()) .fieldOf("storages") .forGetter(Storages::getStorages), RecoverStation.codec(BaseStorage.CODEC) @@ -50,16 +48,18 @@ private Storages(Map storages, RecoverStation re } public static Storages get() { - if (Util.isServer()) { - MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); - if (server != null) { - ServerLevel overworld = server.overworld(); - SavedDataStorage storage = overworld.getDataStorage(); - return storage.computeIfAbsent(Storages.TYPE); - } - } else { - throw new IllegalStateException("Cannot access Storages on client"); + if (!Util.isServer()) { + return Storages.CLIENT_COPY; } + + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server == null) { + throw new IllegalStateException("Cannot access Storages when the server was not initialized"); + } + + ServerLevel overworld = server.overworld(); + SavedDataStorage storage = overworld.getDataStorage(); + return storage.computeIfAbsent(Storages.TYPE); } public Optional get(UUID id) { @@ -71,15 +71,15 @@ public Optional get(UUID id, Class clazz) { } public T getOrCreate(UUID id, Class clazz) { - AtomicReference<@Nullable BaseStorage> storage = new AtomicReference<>(this.storages.get(id)); - if (storage.get() == null) { - storage.set(StorageType.find(clazz).newInstance()); - this.storages.put(id, storage.get()); - + BaseStorage storage = this.storages.get(id); + if (storage == null) { + T empty = Util.cast(StorageType.find(clazz).newInstance(id)); + this.storages.put(id, empty); + return empty; } return Util.castSafely(storage, clazz).orElseThrow(() -> new IllegalArgumentException( "Storage with id '%s' cannot be cast to expected type '%s'. Actual type: %s" - .formatted(id, clazz.getName(), storage.get().getClass().getName()) + .formatted(id, clazz.getName(), storage.getClass().getName()) )); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java index 804300ce9e..08ae0af356 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java @@ -1,8 +1,11 @@ package dev.dubhe.anvilcraft.saved.storage.network; +import dev.anvilcraft.lib.v2.util.Util; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import lombok.Getter; +import lombok.Setter; import java.util.ArrayList; import java.util.HashMap; @@ -11,6 +14,8 @@ import java.util.UUID; /// 双端界面状态,用于同步双端界面数据 +@Getter +@Setter public class MenuState { private static final Map STATES = new HashMap<>(); @@ -25,11 +30,26 @@ public static void clear() { private final UUID id; private final IntList slots; private final ArrayList mapping; + private final Map changes; + private double fullness; public MenuState(UUID id) { this.id = id; this.slots = new IntArrayList(); this.mapping = new ArrayList<>(); + this.changes = new HashMap<>(); + } + + /// 获取服务端需要更新的列表 + /// + /// **注意:不允许在客户端调用该方法** + /// + /// @return 服务端需要更新的列表 + public Map getChanges() { + if (!Util.isServer()) { + throw new IllegalStateException("Cannot invoke MenuState#getChanges in clientside."); + } + return this.changes; } public void sync(IntList slots) { diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java deleted file mode 100644 index a8a6f838ad..0000000000 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/ServerState.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.dubhe.anvilcraft.saved.storage.network; - -import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; -import dev.dubhe.anvilcraft.saved.storage.BaseStorage; -import dev.dubhe.anvilcraft.saved.storage.Storages; -import lombok.Getter; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@Getter -public class ServerState { - private static final Map STATES = new HashMap<>(); - - public static ServerState get(UUID id) { - return ServerState.STATES.computeIfAbsent(id, ServerState::new); - } - - private final UUID id; - private final Map changes; - - public ServerState(UUID id) { - this.id = id; - this.changes = new HashMap<>(); - } - - public BaseStorage getStorage() { - return Storages.get().get(this.id).orElseThrow(() -> new IllegalStateException("Trying to create state with an unbounded id")); - } -} From ae2292b04ef6fbbd881ad279597b1357fd1a7344 Mon Sep 17 00:00:00 2001 From: QiuShui1012 Date: Sun, 21 Jun 2026 03:49:41 +0800 Subject: [PATCH 3/7] refactor(storage): rename MenuState to StorageMenuState for clarity and consistency --- .../client/event/ClientEventListener.java | 4 ++-- .../event/ServerLifecycleEventListener.java | 4 ++-- .../anvilcraft/init/item/ModComponents.java | 2 +- .../anvilcraft/inventory/StorageMenu.java | 6 ++--- .../state/StorageMenuState.java} | 16 +++++++------- .../network/multiple/StoragePackets.java | 22 +++++++++---------- .../anvilcraft/saved/storage/BaseStorage.java | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) rename src/main/java/dev/dubhe/anvilcraft/{saved/storage/network/MenuState.java => inventory/state/StorageMenuState.java} (79%) diff --git a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java index 78816d5d1a..01a0c1a2c9 100644 --- a/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/client/event/ClientEventListener.java @@ -15,10 +15,10 @@ import dev.dubhe.anvilcraft.client.support.SeismicBounceManager; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.item.ModItems; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import dev.dubhe.anvilcraft.item.tool.AnvilHammerItem; import dev.dubhe.anvilcraft.network.UsePillBoxPacket; import dev.dubhe.anvilcraft.recipe.sync.RecipesRecord; -import dev.dubhe.anvilcraft.saved.storage.network.MenuState; import dev.dubhe.anvilcraft.util.BlockHighlightUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; @@ -94,7 +94,7 @@ public static void onRenderBlockOverlay(RenderBlockScreenEffectEvent event) { public static void onClientPlayerDisconnect(ClientPlayerNetworkEvent.LoggingOut event) { SoundHelper.INSTANCE.clear(); RecipesRecord.CLIENTSIDE = null; - MenuState.clear(); + StorageMenuState.clear(); } @SubscribeEvent diff --git a/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java index 820948bfab..e4f6411a4f 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/ServerLifecycleEventListener.java @@ -10,7 +10,7 @@ import dev.dubhe.anvilcraft.api.world.load.RandomChuckTickLoadManager; import dev.dubhe.anvilcraft.block.entity.ItemCollectorBlockEntity; import dev.dubhe.anvilcraft.init.ModHammerInits; -import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.server.ServerStartedEvent; @@ -46,6 +46,6 @@ public static void onServerStopped(ServerStoppedEvent event) { PowerGrid.isServerClosing = false; PowerGrid.clear(); SoundHelper.INSTANCE.clear(); - MenuState.clear(); + StorageMenuState.clear(); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java b/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java index 4bd9f1f8be..b7bc602304 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/item/ModComponents.java @@ -16,7 +16,6 @@ import dev.dubhe.anvilcraft.item.property.component.MultiphaseRef; import dev.dubhe.anvilcraft.item.property.component.OverLimitItemContainerContents; import dev.dubhe.anvilcraft.item.property.component.PillBoxContents; -import net.minecraft.world.item.component.ItemContainerContents; import dev.dubhe.anvilcraft.item.property.component.SavedEntity; import dev.dubhe.anvilcraft.item.property.component.StorageRef; import dev.dubhe.anvilcraft.item.property.component.StoredEnergy; @@ -30,6 +29,7 @@ import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.Unit; +import net.minecraft.world.item.component.ItemContainerContents; import net.minecraft.world.item.enchantment.ItemEnchantments; import net.neoforged.bus.api.IEventBus; import net.neoforged.neoforge.registries.DeferredRegister; diff --git a/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java index 8f8655bd8d..b17fdd4365 100644 --- a/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/StorageMenu.java @@ -2,7 +2,7 @@ import dev.anvilcraft.lib.v2.util.Util; import dev.dubhe.anvilcraft.block.entity.storage.StorageBlockEntity; -import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import lombok.Getter; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.entity.player.Inventory; @@ -17,7 +17,7 @@ @Getter public class StorageMenu extends AbstractContainerMenu { private final StorageBlockEntity be; - private final MenuState state; + private final StorageMenuState state; private final Player player; public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory inv, @Nullable RegistryFriendlyByteBuf buf) { @@ -27,7 +27,7 @@ public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory in public StorageMenu(@Nullable MenuType menuType, int containerId, Inventory inv, BlockEntity be) { super(menuType, containerId); this.be = Util.cast(be); - this.state = MenuState.get(this.be.getId()); + this.state = StorageMenuState.get(this.be.getId()); this.player = inv.player; } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java b/src/main/java/dev/dubhe/anvilcraft/inventory/state/StorageMenuState.java similarity index 79% rename from src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java rename to src/main/java/dev/dubhe/anvilcraft/inventory/state/StorageMenuState.java index 08ae0af356..f2c0b2e348 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/network/MenuState.java +++ b/src/main/java/dev/dubhe/anvilcraft/inventory/state/StorageMenuState.java @@ -1,4 +1,4 @@ -package dev.dubhe.anvilcraft.saved.storage.network; +package dev.dubhe.anvilcraft.inventory.state; import dev.anvilcraft.lib.v2.util.Util; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; @@ -16,15 +16,15 @@ /// 双端界面状态,用于同步双端界面数据 @Getter @Setter -public class MenuState { - private static final Map STATES = new HashMap<>(); +public class StorageMenuState { + private static final Map STATES = new HashMap<>(); - public static MenuState get(UUID id) { - return MenuState.STATES.computeIfAbsent(id, MenuState::new); + public static StorageMenuState get(UUID id) { + return StorageMenuState.STATES.computeIfAbsent(id, StorageMenuState::new); } public static void clear() { - MenuState.STATES.clear(); + StorageMenuState.STATES.clear(); } private final UUID id; @@ -33,7 +33,7 @@ public static void clear() { private final Map changes; private double fullness; - public MenuState(UUID id) { + public StorageMenuState(UUID id) { this.id = id; this.slots = new IntArrayList(); this.mapping = new ArrayList<>(); @@ -47,7 +47,7 @@ public MenuState(UUID id) { /// @return 服务端需要更新的列表 public Map getChanges() { if (!Util.isServer()) { - throw new IllegalStateException("Cannot invoke MenuState#getChanges in clientside."); + throw new IllegalStateException("Cannot invoke StorageMenuState#getChanges in clientside."); } return this.changes; } diff --git a/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java index bdf928f035..20f67d02cd 100644 --- a/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java +++ b/src/main/java/dev/dubhe/anvilcraft/network/multiple/StoragePackets.java @@ -5,7 +5,7 @@ import dev.anvilcraft.lib.v2.network.packet.IServerboundPacket; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.AnvilCraft; -import dev.dubhe.anvilcraft.saved.storage.network.MenuState; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -53,7 +53,7 @@ public Type type() { @Override public void handleOnServer(Player player) { - MenuState.get(this.id).sync(this.slots); + StorageMenuState.get(this.id).sync(this.slots); } } @@ -109,12 +109,12 @@ public Type type() { @Override public void handleOnClient(Player player) { - MenuState.get(this.id).sync(this.head, this.stacks); + StorageMenuState.get(this.id).sync(this.head, this.stacks); } } public static void sync(ServerPlayer player, UUID id, RegistryAccess registries) { - new FullSyncer(player, MenuState.get(id), registries).sync(); + new FullSyncer(player, StorageMenuState.get(id), registries).sync(); } public static class FullSyncer { @@ -123,7 +123,7 @@ public static class FullSyncer { /// 包的初始缓冲区大小 private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; private final ServerPlayer player; - private final MenuState state; + private final StorageMenuState state; private final RegistryAccess registries; private final List packets = new ArrayList<>(); @@ -133,7 +133,7 @@ public static class FullSyncer { @Nullable private RegistryFriendlyByteBuf buf; - private FullSyncer(ServerPlayer player, MenuState state, RegistryAccess registries) { + private FullSyncer(ServerPlayer player, StorageMenuState state, RegistryAccess registries) { this.player = player; this.state = state; this.registries = registries; @@ -267,12 +267,12 @@ public Type type() { @Override public void handleOnClient(Player player) { - MenuState.get(this.id).sync(this.stacks); + StorageMenuState.get(this.id).sync(this.stacks); } } public static void syncIncremental(ServerPlayer player, UUID id, RegistryAccess registries) { - new IncrementalSyncer(player, MenuState.get(id), registries).sync(); + new IncrementalSyncer(player, StorageMenuState.get(id), registries).sync(); } public static class IncrementalSyncer { @@ -281,7 +281,7 @@ public static class IncrementalSyncer { /// 包的初始缓冲区大小 private static final int INITIAL_BUFFER_CAPACITY = 2 * 1024; private final ServerPlayer player; - private final MenuState state; + private final StorageMenuState state; private final RegistryAccess registries; private final List packets = new ArrayList<>(); @@ -290,7 +290,7 @@ public static class IncrementalSyncer { @Nullable private RegistryFriendlyByteBuf buf; - private IncrementalSyncer(ServerPlayer player, MenuState state, RegistryAccess registries) { + private IncrementalSyncer(ServerPlayer player, StorageMenuState state, RegistryAccess registries) { this.player = player; this.state = state; this.registries = registries; @@ -375,7 +375,7 @@ public Type type() { @Override public void handleOnServer(Player player) { - MenuState.get(this.id).setFullness(this.fullness); + StorageMenuState.get(this.id).setFullness(this.fullness); } } } diff --git a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java index 35305e7cdf..c0c4633eec 100644 --- a/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java +++ b/src/main/java/dev/dubhe/anvilcraft/saved/storage/BaseStorage.java @@ -4,8 +4,8 @@ import dev.anvilcraft.lib.v2.util.Util; import dev.anvilcraft.lib.v2.util1.stack.UnlimitedItemStack; import dev.dubhe.anvilcraft.api.itemhandler.TypeLimitItemStacksResourceHandler; +import dev.dubhe.anvilcraft.inventory.state.StorageMenuState; import dev.dubhe.anvilcraft.saved.BetterSavedData; -import dev.dubhe.anvilcraft.saved.storage.network.MenuState; import it.unimi.dsi.fastutil.ints.IntObjectBiConsumer; import lombok.Getter; import net.minecraft.core.RegistryAccess; @@ -34,7 +34,7 @@ protected abstract TypeLimitItemStacksResourceHandler constructItemHandler( ); protected void onContentsChanged(int index, UnlimitedItemStack original) { - MenuState state = MenuState.get(this.id); + StorageMenuState state = StorageMenuState.get(this.id); state.getChanges().put(index, original); state.setFullness(this.items.getFullness()); Storages.get().setDirty(); From 202d6c8f4b8bf2dac92880d7c3dc1c138dc03999 Mon Sep 17 00:00:00 2001 From: QiuShui1012 Date: Tue, 23 Jun 2026 01:46:54 +0800 Subject: [PATCH 4/7] feat(storage): implement category management with customizable settings and display modes --- .../assets/anvilcraft/lang/en_ud.json | 15 +- .../assets/anvilcraft/lang/en_us.json | 15 +- .../anvilcraft/category/enchanted.json | 4 +- .../anvilcraft/category/foods_and_drinks.json | 15 - .../tags/block/hammer_removable.json | 2 +- .../java/dev/dubhe/anvilcraft/AnvilCraft.java | 3 + .../gui/component/RenderableWidgetAdder.java | 9 + .../component/category/CategoryButton.java | 103 ++++ .../gui/component/category/CategoryList.java | 186 ++++++ .../component/category/CategorySetting.java | 548 ++++++++++++++++++ .../gui/component/category/package-info.java | 4 + .../gui/screen/ActiveSilencerScreen.java | 2 +- .../gui/screen/EmberGrindstoneScreen.java | 7 +- .../client/gui/screen/StorageScreen.java | 41 ++ .../client/gui/screen/TeslaTowerScreen.java | 4 +- .../client/support/GuiRenderSupport.java | 38 ++ .../anvilcraft/constant/SharedTextures.java | 6 +- .../anvilcraft/data/lang/CategoryLang.java | 6 +- .../anvilcraft/data/lang/ScreenLang.java | 10 + .../event/PlayerTickEventHandler.java | 10 + .../anvilcraft/init/ModDataAttachments.java | 8 - .../init/item/ModDataComponentPredicates.java | 17 + .../init/storage/ModCategories.java | 29 +- .../init/storage/ModCategoryTypes.java | 11 +- .../predicate/ExtraEnchantmentsPredicate.java | 73 +++ .../mixin/EnchantmentsPredicateMixin.java | 30 + .../invoker/BaseMappedRegistryInvoker.java | 12 + .../network/PlayerSettingsSyncPacket.java | 31 + .../saved/setting/PlayerSetting.java | 69 +++ .../saved/setting/PlayerSettings.java | 61 ++ .../saved/setting/StorageSetting.java | 47 ++ .../saved/setting/mode/NbtDisplayMode.java | 38 ++ .../saved/setting/mode/OrderMode.java | 28 + .../saved/setting/mode/SearchMode.java | 38 ++ .../saved/setting/mode/SortMode.java | 29 + .../saved/setting/mode/package-info.java | 4 + .../saved/setting/package-info.java | 4 + .../saved/storage/category/BlockCategory.java | 51 ++ .../saved/storage/category/Categories.java | 85 --- .../storage/category/FilterCategory.java | 23 +- .../category/HasComponentCategory.java | 13 +- .../saved/storage/category/ICategory.java | 95 ++- .../storage/category/UnstackableCategory.java | 51 ++ .../storage/category/store/CategoryEntry.java | 42 ++ .../storage/category/store/CategoryMode.java | 44 ++ .../storage/category/store/package-info.java | 4 + src/main/resources/anvilcraft.mixins.json | 2 + 47 files changed, 1820 insertions(+), 147 deletions(-) delete mode 100644 src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategorySetting.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/package-info.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/client/support/GuiRenderSupport.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/item/property/predicate/ExtraEnchantmentsPredicate.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/mixin/EnchantmentsPredicateMixin.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/mixin/invoker/BaseMappedRegistryInvoker.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/network/PlayerSettingsSyncPacket.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/PlayerSetting.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/PlayerSettings.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/StorageSetting.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/mode/NbtDisplayMode.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/mode/OrderMode.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/mode/SearchMode.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/mode/SortMode.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/mode/package-info.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/setting/package-info.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/BlockCategory.java delete mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/Categories.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/UnstackableCategory.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/store/CategoryEntry.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/store/CategoryMode.java create mode 100644 src/main/java/dev/dubhe/anvilcraft/saved/storage/category/store/package-info.java diff --git a/src/generated/resources/assets/anvilcraft/lang/en_ud.json b/src/generated/resources/assets/anvilcraft/lang/en_ud.json index db13e87d06..7c48cceeee 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_ud.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_ud.json @@ -564,12 +564,16 @@ "block.anvilcraft.yellow_reinforced_concrete_wall": "ꞁꞁɐM ǝʇǝɹɔuoƆ pǝɔɹoɟuᴉǝᴚ ʍoꞁꞁǝ⅄", "block.anvilcraft.zinc_block": "ɔuᴉZ ɟo ʞɔoꞁᗺ", "block.anvilcraft.zinc_pressure_plate": "ǝʇɐꞁԀ ǝɹnssǝɹԀ ɔuᴉZ", + "category.anvilcraft.block": "sɯǝʇI ʞɔoꞁᗺ", "category.anvilcraft.enchanted": "sɯǝʇI pǝʇuɐɥɔuƎ", - "category.anvilcraft.foods_and_drinks": "sʞuᴉɹᗡ puɐ spooℲ", + "category.anvilcraft.filter": "ʎɹoᵷǝʇɐƆ ʍǝN", + "category.anvilcraft.food_and_drink": "sʞuᴉɹᗡ puɐ spooℲ", "category.anvilcraft.namespace": "sɯǝʇI %s", "category.anvilcraft.namespace.anvilcraft": "ʇɟɐɹƆꞁᴉʌuⱯ", + "category.anvilcraft.namespace.minecraft": "ʇɟɐɹɔǝuᴉW", "category.anvilcraft.redstone": "sɯǝʇI ǝuoʇspǝᴚ", "category.anvilcraft.unknown_namespace": "<%s> uʍouʞu∩", + "category.anvilcraft.unstackable": "sɯǝʇI ǝꞁqɐʞɔɐʇsu∩", "command.anvilcraft.multiBlock.multi_block_pos": " sᴉ sod ʇɹɐd uᴉɐW", "command.anvilcraft.multiBlock.not_multi_block": "ʞɔoꞁq-ᴉʇꞁnɯ ɐ ʇou sᴉ ʞɔoꞁq sᴉɥ⟘", "command.anvilcraft.multiphase.apply.not_player": "ɹǝʎɐꞁd ʇou sᴉ ɹǝuunɹ puɐɯɯoƆ", @@ -1372,6 +1376,15 @@ "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.applies_to": "poᴚ uoᵷɐɹᗡ ɹǝqɯƎ 'ɹǝɯɯɐH ꞁᴉʌuⱯ ɹǝqɯƎ 'ꞁᴉʌuⱯ ꞁɐʇǝW ɹǝqɯƎ", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.base_slot_description": "ɯǝʇᴉ ǝꞁqɐpɐɹᵷdn ʇnԀ", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.upgrade_ingredients": "ʞɔoꞁᗺ ɯnᴉpuǝɔsuɐɹ⟘ ɹo ʇoᵷuI ɯnᴉpuǝɔsuɐɹ⟘", + "screen.anvilcraft.storage.category.add": "ʎɹoᵷǝʇɐɔ ɯoʇsnɔ ppɐ oʇ ɹǝʇꞁᴉℲ ᵷuᴉpꞁoɥ uǝɥʍ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.alternate.removable": "ʎɹoᵷǝʇɐɔ sᴉɥʇ ǝʇǝꞁǝp oʇ ʞɔᴉꞁɔ ʇɥᵷᴉɹ 'ʇɔǝꞁǝs oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.alternate.unremovable": "ʇɔǝꞁǝs oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", + "screen.anvilcraft.storage.category.mode": "%s :ǝpoW", + "screen.anvilcraft.storage.category.mode.allowlist": "ʎɐꞁdsᴉᗡ ʍoꞁꞁⱯ", + "screen.anvilcraft.storage.category.mode.blocklist": "ʎɐꞁdsᴉᗡ ʞɔoꞁᗺ", + "screen.anvilcraft.storage.category.mode.unlimited": "pǝʇᴉɯᴉꞁu∩", + "screen.anvilcraft.storage.category.name": "%s :ǝɯɐN", + "screen.anvilcraft.storage.category.tooltip": "doʇ oʇ uᴉd oʇ ʞɔᴉꞁɔ ʇɥᵷᴉɹ 'sǝʇɐuɹǝʇꞁɐ oʇ ǝʌoɯ oʇ ʞɔᴉꞁɔ ʇɟǝꞀ", "screen.anvilcraft.structure_tool.conversion_output": "ǝdᴉɔǝɹ ɟo ʇndʇno ǝɥʇ ǝq ꞁꞁᴉʍ ʇᴉ ʎq pǝʇɔǝꞁǝs ɐǝɹɐ ǝɥ⟘", "screen.anvilcraft.structure_tool.conversion_recipe": "ǝdᴉɔǝɹ uoᴉsɹǝʌuoɔ ʞɔoꞁqᴉʇꞁnɯ ǝʇɐɹǝuǝᵷ oʇ ꞁooʇ ǝɹnʇɔnɹʇs ɹǝɥʇouɐ ʇnԀ", "screen.anvilcraft.structure_tool.count": "%d :ʇunoƆ", diff --git a/src/generated/resources/assets/anvilcraft/lang/en_us.json b/src/generated/resources/assets/anvilcraft/lang/en_us.json index 1746cb1686..81f45022b2 100644 --- a/src/generated/resources/assets/anvilcraft/lang/en_us.json +++ b/src/generated/resources/assets/anvilcraft/lang/en_us.json @@ -564,12 +564,16 @@ "block.anvilcraft.yellow_reinforced_concrete_wall": "Yellow Reinforced Concrete Wall", "block.anvilcraft.zinc_block": "Block of Zinc", "block.anvilcraft.zinc_pressure_plate": "Zinc Pressure Plate", + "category.anvilcraft.block": "Block Items", "category.anvilcraft.enchanted": "Enchanted Items", - "category.anvilcraft.foods_and_drinks": "Foods and Drinks", + "category.anvilcraft.filter": "New Category", + "category.anvilcraft.food_and_drink": "Foods and Drinks", "category.anvilcraft.namespace": "%s Items", "category.anvilcraft.namespace.anvilcraft": "AnvilCraft", + "category.anvilcraft.namespace.minecraft": "Minecraft", "category.anvilcraft.redstone": "Redstone Items", "category.anvilcraft.unknown_namespace": "Unknown <%s>", + "category.anvilcraft.unstackable": "Unstackable Items", "command.anvilcraft.multiBlock.multi_block_pos": "Main part pos is ", "command.anvilcraft.multiBlock.not_multi_block": "This block is not a multi-block", "command.anvilcraft.multiphase.apply.not_player": "Command runner is not player", @@ -1372,6 +1376,15 @@ "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.applies_to": "Ember Metal Anvil, Ember Anvil Hammer, Ember Dragon Rod", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.base_slot_description": "Put upgradable item", "screen.anvilcraft.smithing_template.transcendium_upgrade_smithing_template.upgrade_ingredients": "Transcendium Ingot or Transcendium Block", + "screen.anvilcraft.storage.category.add": "Left click when holding Filter to add custom category", + "screen.anvilcraft.storage.category.alternate.removable": "Left click to select, right click to delete this category", + "screen.anvilcraft.storage.category.alternate.unremovable": "Left click to select", + "screen.anvilcraft.storage.category.mode": "Mode: %s", + "screen.anvilcraft.storage.category.mode.allowlist": "Allow Display", + "screen.anvilcraft.storage.category.mode.blocklist": "Block Display", + "screen.anvilcraft.storage.category.mode.unlimited": "Unlimited", + "screen.anvilcraft.storage.category.name": "Name: %s", + "screen.anvilcraft.storage.category.tooltip": "Left click to move to alternates, right click to pin to top", "screen.anvilcraft.structure_tool.conversion_output": "The area selected by it will be the output of recipe", "screen.anvilcraft.structure_tool.conversion_recipe": "Put another structure tool to generate multiblock conversion recipe", "screen.anvilcraft.structure_tool.count": "Count: %d", diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json index a4da14b7d1..54caf3d024 100644 --- a/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json +++ b/src/generated/resources/data/anvilcraft/anvilcraft/category/enchanted.json @@ -7,8 +7,8 @@ "translate": "category.anvilcraft.enchanted" }, "predicates": { - "anvilcraft:disabled_enchantments": {}, - "anvilcraft:merciless_enchantments": {}, + "anvilcraft:disabled_enchantment": [], + "anvilcraft:merciless_enchantment": [], "minecraft:enchantments": [], "minecraft:stored_enchantments": [] } diff --git a/src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json b/src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json deleted file mode 100644 index b95864ec8d..0000000000 --- a/src/generated/resources/data/anvilcraft/anvilcraft/category/foods_and_drinks.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "anvilcraft:has_component", - "icon": { - "id": "minecraft:apple" - }, - "name": { - "translate": "category.anvilcraft.foods_and_drinks" - }, - "predicates": { - "minecraft:food": {}, - "minecraft:potion_contents": { - "type": "neoforge:any" - } - } -} \ No newline at end of file diff --git a/src/generated/resources/data/anvilcraft/tags/block/hammer_removable.json b/src/generated/resources/data/anvilcraft/tags/block/hammer_removable.json index b551e58494..a49f916476 100644 --- a/src/generated/resources/data/anvilcraft/tags/block/hammer_removable.json +++ b/src/generated/resources/data/anvilcraft/tags/block/hammer_removable.json @@ -83,4 +83,4 @@ "anvilcraft:polished_heavy_iron_stairs", "#minecraft:shulker_boxes" ] -} +} \ No newline at end of file diff --git a/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java b/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java index f2af5319a0..aee926003d 100644 --- a/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java +++ b/src/main/java/dev/dubhe/anvilcraft/AnvilCraft.java @@ -48,7 +48,9 @@ import dev.dubhe.anvilcraft.init.recipe.ModRecipeTypes; import dev.dubhe.anvilcraft.init.recipe.ModResultModifierTypes; import dev.dubhe.anvilcraft.init.storage.ModCategoryTypes; +import dev.dubhe.anvilcraft.mixin.invoker.BaseMappedRegistryInvoker; import lombok.Getter; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.Identifier; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; @@ -174,6 +176,7 @@ public static void loadComplete(FMLLoadCompleteEvent event) { CONFIG.emberAnvilBeyondMaxLevel = true; CONFIG.transcendenceAnvilBeyondMaxLevel = true; } + Util.cast(BuiltInRegistries.DATA_COMPONENT_PREDICATE_TYPE).invokeSetSync(true); }); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java new file mode 100644 index 0000000000..3519a18059 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/RenderableWidgetAdder.java @@ -0,0 +1,9 @@ +package dev.dubhe.anvilcraft.client.gui.component; + +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; + +public interface RenderableWidgetAdder { + T addRenderableWidget(T widget); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java new file mode 100644 index 0000000000..a9c450096f --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryButton.java @@ -0,0 +1,103 @@ +package dev.dubhe.anvilcraft.client.gui.component.category; + +import dev.anvilcraft.lib.v2.util.component.MultilineComponentHelper; +import dev.dubhe.anvilcraft.client.support.GuiRenderSupport; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.saved.setting.PlayerSetting; +import dev.dubhe.anvilcraft.saved.storage.category.ICategory; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryEntry; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryMode; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix3x2fStack; + +@Getter +@Setter +public class CategoryButton extends Button { + public static final Identifier BACKGROUND = SharedTextures.textureGui("misc/storage_station/category"); + private final Font font = Minecraft.getInstance().font; + private final PlayerSetting setting; + private final int index; + private CategoryMode mode; + + public CategoryButton(int x, PlayerSetting setting, int index, CategoryMode mode, OnPress onPress) { + super( + x, + 0, + 86, + 20, + Component.empty(), + button -> { + if (!(button instanceof CategoryButton category)) return; + category.mode = category.entry().changeMode(); + onPress.onPress(button); + }, + DEFAULT_NARRATION + ); + this.setting = setting; + this.index = index; + this.mode = mode; + } + + protected CategoryEntry entry() { + return this.setting.listed().get(this.index); + } + + @Override + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + this.isHovered = this.isMouseOver(mouseX, mouseY); + int offsetV = this.mode.getTexYDiff(); + if (this.isHovered) { + offsetV = 20; // Hovered part + } + + graphics.blit( + RenderPipelines.GUI_TEXTURED, + CategoryButton.BACKGROUND, + this.getX(), + this.getY(), + 0, + offsetV, + this.width, + this.height, + 86, + 80 + ); + + Matrix3x2fStack pose = graphics.pose(); + pose.pushMatrix(); + pose.translate(this.getX(), this.getY()); + pose.scale(0.75f, 0.75f); + + ICategory category = this.entry().getCategory(); + + ItemStack icon = category.icon().create(); + int x = 4; + int y = 4; + graphics.fakeItem(icon, x, y); + graphics.itemDecorations(this.font, icon, x, y); + + pose.popMatrix(); + + Component name = category.name(); + this.setTooltip(Tooltip.create( + MultilineComponentHelper.create() + .addln("screen.anvilcraft.storage.category.name", name) + .addln("screen.anvilcraft.storage.category.mode", this.mode.getModeName()) + .build() + )); + + int left = this.getX() + 17; + int top = this.getY() + 5; + GuiRenderSupport.centeredEllipsisText(graphics, this.font, name, left, top, 65); + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java new file mode 100644 index 0000000000..637157c1a7 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/client/gui/component/category/CategoryList.java @@ -0,0 +1,186 @@ +package dev.dubhe.anvilcraft.client.gui.component.category; + +import dev.anvilcraft.lib.v2.util.ListUtil; +import dev.anvilcraft.lib.v2.util.Scrollable; +import dev.dubhe.anvilcraft.client.gui.component.TexturedButton; +import dev.dubhe.anvilcraft.constant.SharedTextures; +import dev.dubhe.anvilcraft.saved.setting.PlayerSetting; +import dev.dubhe.anvilcraft.saved.storage.category.store.CategoryEntry; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractContainerWidget; +import net.minecraft.client.gui.components.AbstractScrollArea; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; + +import java.util.ArrayList; +import java.util.List; + +public class CategoryList extends AbstractContainerWidget { + private static final Identifier SETTING_BUTTON_BACKGROUND = SharedTextures.textureGui("misc/storage_station/category_setting"); + public static final Identifier SMALL_SLIDER = SharedTextures.textureGui("misc/storage_station/slider_small"); + private final List categoryButtons; + private final Button.OnPress categoryOnPress; + private final TexturedButton settingButton; + + private final Scrollable scrollable = new Scrollable() { + @Override + public int row() { + return 8; + } + + @Override + public int column() { + return 1; + } + + @Override + public int size() { + return CategoryList.this.children().size() + 1; + } + + @Override + public void setHead(int head) { + CategoryList.this.head = head; + } + }; + private int head = 0; + + public CategoryList(int x, int y, PlayerSetting setting, Button.OnPress categoryOnPress, Button.OnPress openSetting) { + super(x, y, 92, 160, Component.empty(), AbstractScrollArea.defaultSettings(1)); + + this.categoryButtons = new ArrayList<>(); + this.categoryOnPress = categoryOnPress; + + this.settingButton = new TexturedButton( + x, + 0, + 86, + 20, + CategoryList.SETTING_BUTTON_BACKGROUND, + 20, + 86, + 40, + openSetting + ); + + this.rebuild(setting); + } + + public void rebuild(PlayerSetting setting) { + this.categoryButtons.clear(); + List listed = setting.listed(); + for (int i = 0; i < listed.size(); i++) { + CategoryEntry entry = listed.get(i); + this.categoryButtons.add(new CategoryButton(this.getX(), setting, i, entry.getMode(), this.categoryOnPress)); + } + this.scrollable.scrollTo(); + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (event.button() == 0 && this.insideScrollbar(event.x(), event.y())) { + this.scrollable.scrolling(); + return true; + } + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + if (event.button() == 0 && this.scrollable.isScrolling()) { + this.scrollable.notScrolling(); + return true; + } + + return super.mouseReleased(event); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + if (this.scrollable.isScrolling()) { + int top = this.getY() + 18; + this.scrollable.scrollOnDrag(10, event.y(), top, top + 112); + return true; + } + return super.mouseDragged(event, dx, dy); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (!this.scrollable.canScroll()) { + return false; + } else { + this.scrollable.scrollOnScroll(scrollY / 1.2); + return true; + } + } + + @Override + protected int contentHeight() { + return 0; + } + + @Override + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + int areaSize = this.scrollable.column() * this.scrollable.row(); + int end = this.head + Math.min(this.scrollable.size() - this.head, areaSize); + for (int i = this.head; i < end; i++) { + Button button = ListUtil.safelyGet(this.children(), i).orElse(null); + if (button == null) continue; + button.active = true; + button.setPosition(this.getX(), this.getY() + i * 20); + button.extractRenderState(graphics, mouseX, mouseY, a); + } + } + + @Override + protected void extractScrollbar(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + if (this.scrollable.canScroll()) { + int top = this.getY(); + int bottom = top + this.getHeight(); + graphics.blit( + RenderPipelines.GUI_TEXTURED, + CategoryList.SMALL_SLIDER, + this.getX() + 88, + top + (int) ((float) (bottom - top - 10) * this.scrollable.getScrollOffs()), + 0, + 0, + 4, + 10, + 4, + 10 + ); + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + } + + private final List