From 6daf3a4cff256960e4dfa14a4f1ff378f6aeae62 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 16:15:31 +0800 Subject: [PATCH 01/11] =?UTF-8?q?refactor(item):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=98=BE=E7=A4=BA=E5=92=8C=E9=94=80=E6=AF=81?= =?UTF-8?q?=E7=89=A9=E5=93=81=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除了 ModelDestroyerItem 和 ModelDisplayItem 类, 同时从 ModItems 中移除了 ModelItemRegistrar 和 ModelToolRegistrar 的注册调用。 feat(register): 添加包模型方块实体注册器 新增 PackModelBlockEntityRegistrar 并在 ModBlocks 中进行注册, 替换原有的动态模型物品注册机制。 refactor(creative): 调整模型标签常量命名 将 DEFAULT_TAB 常量重命名为 DEFAULT_MODEL_TAB, 统一管理模型相关的创意标签名称。 --- .../block/entity/PackModelBlockEntity.java | 85 ++++++++ .../block/entity/PackModelEntityBlock.java | 50 +++++ .../com/box3lab/item/ModelDestroyerItem.java | 72 ------ .../com/box3lab/item/ModelDisplayItem.java | 54 ----- .../java/com/box3lab/register/ModBlocks.java | 4 +- .../java/com/box3lab/register/ModItems.java | 5 +- .../box3lab/register/ModelItemRegistrar.java | 132 ----------- .../box3lab/register/ModelToolRegistrar.java | 59 ----- .../creative/CreativeTabRegistrar.java | 6 +- .../PackModelBlockEntityRegistrar.java | 205 ++++++++++++++++++ 10 files changed, 347 insertions(+), 325 deletions(-) create mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java create mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java delete mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDestroyerItem.java delete mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDisplayItem.java delete mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/register/ModelItemRegistrar.java delete mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/register/ModelToolRegistrar.java create mode 100644 Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java new file mode 100644 index 0000000..1f4755f --- /dev/null +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -0,0 +1,85 @@ +package com.box3lab.block.entity; + +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class PackModelBlockEntity extends BlockEntity { + private static final long RESPAWN_INTERVAL_TICKS = 20L; + private static final String DISPLAY_TAG_PREFIX = "box3_pack_model:"; + + public PackModelBlockEntity(BlockPos pos, BlockState state) { + super(PackModelBlockEntityRegistrar.typeFor(state.getBlock()), pos, state); + } + + @Override + public void setRemoved() { + removeDisplaysAt(this.level, this.getBlockPos()); + super.setRemoved(); + } + + public static void serverTick(Level level, BlockPos pos, BlockState state, PackModelBlockEntity blockEntity) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + if (serverLevel.getGameTime() % RESPAWN_INTERVAL_TICKS != 0) { + return; + } + + String tag = displayTag(pos); + var displays = serverLevel.getEntitiesOfClass( + Display.ItemDisplay.class, + new AABB(pos), + display -> display.getTags().contains(tag)); + if (!displays.isEmpty()) { + return; + } + + spawnDisplay(serverLevel, pos, state, tag); + } + + public static void removeDisplaysAt(Level level, BlockPos pos) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + String tag = displayTag(pos); + var displays = serverLevel.getEntitiesOfClass( + Display.ItemDisplay.class, + new AABB(pos).inflate(0.25D), + display -> display.getTags().contains(tag)); + for (Display.ItemDisplay display : displays) { + display.discard(); + } + } + + private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState state, String tag) { + Display.ItemDisplay display = EntityType.ITEM_DISPLAY.create(level, EntitySpawnReason.SPAWN_ITEM_USE); + if (display == null) { + return; + } + + Vec3 center = Vec3.atCenterOf(pos); + display.setPos(center.x, center.y, center.z); + display.setNoGravity(true); + display.setInvulnerable(true); + display.getSlot(0).set(new ItemStack(state.getBlock())); + display.addTag(tag); + + level.addFreshEntity(display); + } + + private static String displayTag(BlockPos pos) { + return DISPLAY_TAG_PREFIX + pos.asLong(); + } +} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java new file mode 100644 index 0000000..a0d0b26 --- /dev/null +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -0,0 +1,50 @@ +package com.box3lab.block.entity; + +import net.minecraft.core.BlockPos; +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.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import org.jspecify.annotations.Nullable; + +public class PackModelEntityBlock extends Block implements EntityBlock { + public PackModelEntityBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new PackModelBlockEntity(pos, state); + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.INVISIBLE; + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType blockEntityType) { + if (level.isClientSide()) { + return null; + } + + BlockEntityType expectedType = com.box3lab.register.modelbe.PackModelBlockEntityRegistrar.typeFor(state.getBlock()); + if (blockEntityType != expectedType) { + return null; + } + + return (lvl, pos, blockState, be) -> PackModelBlockEntity.serverTick( + lvl, + pos, + blockState, + (PackModelBlockEntity) be); + } +} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDestroyerItem.java b/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDestroyerItem.java deleted file mode 100644 index b70e5de..0000000 --- a/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDestroyerItem.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.box3lab.item; - -import java.util.List; - -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; - -public class ModelDestroyerItem extends Item { - private static final double RANGE = 10.0; - - public ModelDestroyerItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Player player = context.getPlayer(); - if (player == null) { - return InteractionResult.PASS; - } - - boolean deleted = deleteTarget(serverLevel, player); - return deleted ? InteractionResult.SUCCESS : InteractionResult.PASS; - } - - private static boolean deleteTarget(ServerLevel level, Player player) { - Display.ItemDisplay target = findDisplay(level, player, RANGE); - if (target == null) { - return false; - } - target.discard(); - return true; - } - - private static Display.ItemDisplay findDisplay(ServerLevel level, Player player, double range) { - Vec3 start = player.getEyePosition(1.0f); - Vec3 look = player.getViewVector(1.0f); - Vec3 end = start.add(look.scale(range)); - AABB searchBox = player.getBoundingBox().expandTowards(look.scale(range)).inflate(1.5); - - List displays = level.getEntitiesOfClass(Display.ItemDisplay.class, searchBox); - Display.ItemDisplay closest = null; - double bestDist = range * range; - - for (Display.ItemDisplay display : displays) { - AABB box = display.getBoundingBox().inflate(0.8); - var hit = box.clip(start, end); - if (hit.isEmpty()) { - continue; - } - double dist = start.distanceToSqr(hit.get()); - if (dist < bestDist) { - bestDist = dist; - closest = display; - } - } - - return closest; - } -} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDisplayItem.java b/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDisplayItem.java deleted file mode 100644 index 101a34c..0000000 --- a/Fabric-1.21.11/src/main/java/com/box3lab/item/ModelDisplayItem.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.box3lab.item; - -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.EntitySpawnReason; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; - -public class ModelDisplayItem extends Item { - public ModelDisplayItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Display.ItemDisplay display = EntityType.ITEM_DISPLAY.create(serverLevel, EntitySpawnReason.SPAWN_ITEM_USE); - if (display == null) { - return InteractionResult.FAIL; - } - - BlockPos placePos = context.getClickedPos().relative(context.getClickedFace()); - Vec3 pos = Vec3.atCenterOf(placePos); - display.setPos(pos.x, pos.y, pos.z); - - Player player = context.getPlayer(); - if (player != null) { - display.setYRot(player.getYRot()); - } - - ItemStack displayStack = context.getItemInHand().copyWithCount(1); - display.getSlot(0).set(displayStack); - - display.setNoGravity(true); - serverLevel.addFreshEntity(display); - - if (player == null || !player.getAbilities().instabuild) { - context.getItemInHand().shrink(1); - } - - return InteractionResult.SUCCESS; - } -} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModBlocks.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModBlocks.java index f815bbb..2853bc1 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModBlocks.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModBlocks.java @@ -6,6 +6,7 @@ import com.box3lab.Box3; import com.box3lab.register.core.BlockRegistrar; import com.box3lab.register.creative.CreativeTabRegistrar; +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; import com.box3lab.register.sound.CategorySoundTypes; import com.box3lab.register.voxel.VoxelBlockFactories; import com.box3lab.register.voxel.VoxelBlockPropertiesFactory; @@ -62,6 +63,7 @@ public static void initialize() { } CreativeTabRegistrar.registerCreativeTabs(Box3.MOD_ID, BLOCKS, data); + PackModelBlockEntityRegistrar.registerAll(); } -} \ No newline at end of file +} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModItems.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModItems.java index 798e513..f8f2634 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModItems.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModItems.java @@ -6,9 +6,6 @@ private ModItems() { } public static void initialize() { - // Register all model-based display items discovered from resource packs - ModelItemRegistrar.registerAll(); - - ModelToolRegistrar.registerAll(); + // Reserved for future item registrations. } } diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelItemRegistrar.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelItemRegistrar.java deleted file mode 100644 index facc38c..0000000 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelItemRegistrar.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.box3lab.register; - -import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDisplayItem; -import com.box3lab.register.creative.CreativeTabExtras; -import com.box3lab.register.creative.CreativeTabRegistrar; - -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.Identifier; -import net.minecraft.resources.ResourceKey; -import net.minecraft.world.item.Item; - -public final class ModelItemRegistrar { - private static final String ITEMS_DIR_PREFIX = "assets/" + Box3.MOD_ID + "/items/"; - public static final String DEFAULT_TAB = "models"; - - private ModelItemRegistrar() { - } - - public static void registerAll() { - Set itemPaths = discoverModelItemPaths(); - if (itemPaths.isEmpty()) { - return; - } - - for (String path : itemPaths) { - Identifier id = Identifier.tryBuild(Box3.MOD_ID, path); - if (id == null) { - continue; - } - - ResourceKey key = ResourceKey.create(Registries.ITEM, id); - if (BuiltInRegistries.ITEM.containsKey(key)) { - continue; - } - - Item item = new ModelDisplayItem(new Item.Properties().setId(key)); - Registry.register(BuiltInRegistries.ITEM, key, item); - CreativeTabExtras.add(DEFAULT_TAB, item); - } - - CreativeTabRegistrar.registerModelTab(Box3.MOD_ID); - } - - private static Set discoverModelItemPaths() { - Set results = new LinkedHashSet<>(); - Path resourcepacksDir = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); - if (!Files.isDirectory(resourcepacksDir)) { - return results; - } - - try (DirectoryStream stream = Files.newDirectoryStream(resourcepacksDir)) { - for (Path entry : stream) { - if (Files.isDirectory(entry)) { - scanDirectoryPack(entry, results); - } else if (isArchive(entry)) { - scanZipPack(entry, results); - } - } - } catch (IOException ignored) { - } - - return results; - } - - private static boolean isArchive(Path path) { - String name = path.getFileName().toString().toLowerCase(Locale.ROOT); - return name.endsWith(".zip") || name.endsWith(".jar"); - } - - private static void scanDirectoryPack(Path packDir, Set out) { - Path itemsDir = packDir.resolve("assets").resolve(Box3.MOD_ID).resolve("items"); - if (!Files.isDirectory(itemsDir)) { - return; - } - - try (var paths = Files.walk(itemsDir)) { - paths.filter(Files::isRegularFile) - .forEach(file -> { - String name = file.getFileName().toString(); - if (!name.endsWith(".json")) { - return; - } - - String rel = itemsDir.relativize(file).toString().replace(File.separatorChar, '/'); - if (rel.endsWith(".json")) { - rel = rel.substring(0, rel.length() - 5); - } - if (!rel.isBlank()) { - out.add(rel); - } - }); - } catch (IOException ignored) { - } - } - - private static void scanZipPack(Path zipPath, Set out) { - try (ZipFile zip = new ZipFile(zipPath.toFile())) { - Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (entry.isDirectory()) { - continue; - } - String name = entry.getName(); - if (!name.startsWith(ITEMS_DIR_PREFIX) || !name.endsWith(".json")) { - continue; - } - String rel = name.substring(ITEMS_DIR_PREFIX.length(), name.length() - 5); - if (!rel.isBlank()) { - out.add(rel); - } - } - } catch (IOException ignored) { - } - } -} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelToolRegistrar.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelToolRegistrar.java deleted file mode 100644 index b7dc95c..0000000 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/ModelToolRegistrar.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.box3lab.register; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDestroyerItem; -import com.box3lab.register.creative.CreativeTabExtras; - -import net.fabricmc.fabric.api.event.player.UseEntityCallback; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.Identifier; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; - -public final class ModelToolRegistrar { - private ModelToolRegistrar() { - } - - public static void registerAll() { - registerDestroyer(); - registerDestroyerHandler(); - } - - private static void registerDestroyer() { - Identifier id = Identifier.fromNamespaceAndPath(Box3.MOD_ID, "model_destroyer"); - ResourceKey key = ResourceKey.create(Registries.ITEM, id); - if (BuiltInRegistries.ITEM.containsKey(key)) { - return; - } - - Item item = new ModelDestroyerItem(new Item.Properties().setId(key).stacksTo(1)); - Registry.register(BuiltInRegistries.ITEM, key, item); - CreativeTabExtras.add(ModelItemRegistrar.DEFAULT_TAB, item); - } - - private static void registerDestroyerHandler() { - UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { - ItemStack stack = player.getItemInHand(hand); - if (!(stack.getItem() instanceof ModelDestroyerItem)) { - return InteractionResult.PASS; - } - - if (!(entity instanceof Display.ItemDisplay display)) { - return InteractionResult.PASS; - } - - if (!(world instanceof ServerLevel)) { - return InteractionResult.SUCCESS; - } - - display.discard(); - return InteractionResult.SUCCESS; - }); - } -} diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java index 89d5645..4449772 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java @@ -21,9 +21,9 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.ItemLike; -import static com.box3lab.register.ModelItemRegistrar.DEFAULT_TAB; - public final class CreativeTabRegistrar { + public static final String DEFAULT_MODEL_TAB = "models"; + private CreativeTabRegistrar() { } @@ -96,7 +96,7 @@ public static void registerCreativeTabs(String modId, Map blocks, } public static void registerModelTab(String modId) { - String categoryPath = sanitizeCategoryPath(DEFAULT_TAB); + String categoryPath = sanitizeCategoryPath(DEFAULT_MODEL_TAB); if (categoryPath.isBlank()) { return; } diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java new file mode 100644 index 0000000..6c8db79 --- /dev/null +++ b/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -0,0 +1,205 @@ +package com.box3lab.register.modelbe; + +import static com.box3lab.Box3.MOD_ID; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.box3lab.block.entity.PackModelEntityBlock; +import com.box3lab.register.core.BlockRegistrar; +import com.box3lab.register.creative.CreativeTabExtras; +import com.box3lab.register.creative.CreativeTabRegistrar; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; + +public final class PackModelBlockEntityRegistrar { + private static final String ASSET_PREFIX = "assets/" + MOD_ID + "/"; + + private static final Map> TYPES_BY_BLOCK = new LinkedHashMap<>(); + + private PackModelBlockEntityRegistrar() { + } + + public static void registerAll() { + Set modelNames = discoverPairedModelNames(); + if (modelNames.isEmpty()) { + return; + } + + for (String name : modelNames) { + Identifier id = Identifier.tryBuild(MOD_ID, name); + if (id == null) { + continue; + } + + ResourceKey blockKey = ResourceKey.create(Registries.BLOCK, id); + if (BuiltInRegistries.BLOCK.containsKey(blockKey)) { + continue; + } + + Block block = BlockRegistrar.register( + MOD_ID, + name, + PackModelEntityBlock::new, + BlockBehaviour.Properties.of() + .mapColor(MapColor.STONE) + .strength(1.5F, 6.0F) + .noOcclusion() + .isViewBlocking((state, level, pos) -> false) + .isSuffocating((state, level, pos) -> false) + .isRedstoneConductor((state, level, pos) -> false), + true); + + ResourceKey> blockEntityKey = ResourceKey.create(Registries.BLOCK_ENTITY_TYPE, id); + if (BuiltInRegistries.BLOCK_ENTITY_TYPE.containsKey(blockEntityKey)) { + continue; + } + + BlockEntityType type = FabricBlockEntityTypeBuilder + .create(com.box3lab.block.entity.PackModelBlockEntity::new, block) + .build(); + + Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, blockEntityKey, type); + TYPES_BY_BLOCK.put(block, type); + CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, block.asItem()); + } + + CreativeTabRegistrar.registerModelTab(MOD_ID); + } + + public static BlockEntityType typeFor(Block block) { + BlockEntityType type = TYPES_BY_BLOCK.get(block); + if (type == null) { + throw new IllegalStateException("No block entity type bound for block: " + block); + } + return type; + } + + private static Set discoverPairedModelNames() { + Set result = new LinkedHashSet<>(); + Path packsRoot = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); + if (!Files.isDirectory(packsRoot)) { + return result; + } + + try (var entries = Files.list(packsRoot)) { + entries.forEach(entry -> { + if (Files.isDirectory(entry)) { + collectFromDirectory(entry, result); + } else if (isArchive(entry)) { + collectFromArchive(entry, result); + } + }); + } catch (IOException ignored) { + } + + return result; + } + + private static void collectFromDirectory(Path packDir, Set out) { + Path assetsRoot = packDir.resolve("assets").resolve(MOD_ID); + if (!Files.isDirectory(assetsRoot)) { + return; + } + + Set models = collectBaseNamesFromDirectory(assetsRoot, ".json"); + if (models.isEmpty()) { + return; + } + + Set textures = collectBaseNamesFromDirectory(assetsRoot, ".png"); + if (textures.isEmpty()) { + return; + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } + + private static Set collectBaseNamesFromDirectory(Path root, String suffix) { + Set names = new LinkedHashSet<>(); + try (var files = Files.walk(root)) { + files.filter(Files::isRegularFile).forEach(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.toLowerCase(Locale.ROOT).endsWith(suffix)) { + return; + } + + String base = fileName.substring(0, fileName.length() - suffix.length()).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + names.add(base); + } + }); + } catch (IOException ignored) { + } + return names; + } + + private static void collectFromArchive(Path archive, Set out) { + try (ZipFile zip = new ZipFile(archive.toFile())) { + Set models = new LinkedHashSet<>(); + Set textures = new LinkedHashSet<>(); + + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + continue; + } + + String name = entry.getName(); + if (!name.startsWith(ASSET_PREFIX)) { + continue; + } + + String fileName = name.substring(name.lastIndexOf('/') + 1); + String lower = fileName.toLowerCase(Locale.ROOT); + if (lower.endsWith(".json")) { + String base = fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + models.add(base); + } + } else if (lower.endsWith(".png")) { + String base = fileName.substring(0, fileName.length() - 4).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + textures.add(base); + } + } + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } catch (IOException ignored) { + } + } + + private static boolean isArchive(Path path) { + String name = path.getFileName().toString().toLowerCase(Locale.ROOT); + return name.endsWith(".zip") || name.endsWith(".jar"); + } +} From 0ac4b6f58e3b167ff34ddd2c84588b4a30b51ed3 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 16:18:25 +0800 Subject: [PATCH 02/11] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=8E=A9=E5=AE=B6=E5=8F=82=E6=95=B0=E5=B9=B6=E9=87=8D=E5=86=99?= =?UTF-8?q?=E7=A0=B4=E5=9D=8F=E7=B2=92=E5=AD=90=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在PackModelEntityBlock类中添加Player导入 - 重写spawnDestroyParticles方法以处理物品展示框的破坏粒子效果 - 避免因方块模型不可见而导致的纹理缺失问题 --- .../com/box3lab/block/entity/PackModelEntityBlock.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java index a0d0b26..b709c32 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -1,6 +1,7 @@ package com.box3lab.block.entity; import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; @@ -28,6 +29,12 @@ protected RenderShape getRenderShape(BlockState state) { return RenderShape.INVISIBLE; } + @Override + protected void spawnDestroyParticles(Level level, Player player, BlockPos pos, BlockState state) { + // This block is rendered by ItemDisplay instead of block model, + // so vanilla block-break particles would resolve to missing textures. + } + @Nullable @Override @SuppressWarnings("unchecked") From d8b1a3c3fbce0b25f81061519c2a73f0700fc199 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 16:28:00 +0800 Subject: [PATCH 03/11] =?UTF-8?q?refactor(register):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=B3=A8=E5=86=8C=E9=80=BB=E8=BE=91=E4=BB=A5?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BD=BF=E7=94=A8=E5=8E=9F=E7=94=9F=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除对BlockRegistrar的依赖,改为直接使用Registry.register进行方块和物品注册, 同时确保物品正确添加到创造模式标签页中。 --- .../PackModelBlockEntityRegistrar.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java index 6c8db79..04e3625 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -15,7 +15,6 @@ import java.util.zip.ZipFile; import com.box3lab.block.entity.PackModelEntityBlock; -import com.box3lab.register.core.BlockRegistrar; import com.box3lab.register.creative.CreativeTabExtras; import com.box3lab.register.creative.CreativeTabRegistrar; @@ -26,6 +25,8 @@ import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; @@ -56,18 +57,25 @@ public static void registerAll() { continue; } - Block block = BlockRegistrar.register( - MOD_ID, - name, - PackModelEntityBlock::new, + Block block = new PackModelEntityBlock( BlockBehaviour.Properties.of() + .setId(blockKey) .mapColor(MapColor.STONE) .strength(1.5F, 6.0F) .noOcclusion() .isViewBlocking((state, level, pos) -> false) .isSuffocating((state, level, pos) -> false) - .isRedstoneConductor((state, level, pos) -> false), - true); + .isRedstoneConductor((state, level, pos) -> false)); + Registry.register(BuiltInRegistries.BLOCK, blockKey, block); + + ResourceKey itemKey = ResourceKey.create( + Registries.ITEM, + Identifier.fromNamespaceAndPath(MOD_ID, name)); + if (!BuiltInRegistries.ITEM.containsKey(itemKey)) { + Item item = new BlockItem(block, new Item.Properties().setId(itemKey).useItemDescriptionPrefix()); + Registry.register(BuiltInRegistries.ITEM, itemKey, item); + CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, item); + } ResourceKey> blockEntityKey = ResourceKey.create(Registries.BLOCK_ENTITY_TYPE, id); if (BuiltInRegistries.BLOCK_ENTITY_TYPE.containsKey(blockEntityKey)) { @@ -80,7 +88,6 @@ public static void registerAll() { Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, blockEntityKey, type); TYPES_BY_BLOCK.put(block, type); - CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, block.asItem()); } CreativeTabRegistrar.registerModelTab(MOD_ID); From 5503ce2eb9aefe9f9a12ef1c63b86909e3c4d03b Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 17:44:13 +0800 Subject: [PATCH 04/11] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=9C=9D=E5=90=91=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为PackModelEntityBlock添加水平朝向属性支持,使方块能够正确显示朝向并 保持方向一致性。实现了BlockState的旋转、镜像和放置朝向逻辑,并在 PackModelBlockEntity中应用朝向旋转到显示实体。 --- .../block/entity/PackModelBlockEntity.java | 3 ++ .../block/entity/PackModelEntityBlock.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java index 1f4755f..97f5f4a 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -73,6 +73,9 @@ private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState sta display.setPos(center.x, center.y, center.z); display.setNoGravity(true); display.setInvulnerable(true); + if (state.hasProperty(PackModelEntityBlock.HORIZONTAL_FACING)) { + display.setYRot(state.getValue(PackModelEntityBlock.HORIZONTAL_FACING).toYRot()); + } display.getSlot(0).set(new ItemStack(state.getBlock())); display.addTag(tag); diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java index b709c32..8053704 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -1,21 +1,31 @@ package com.box3lab.block.entity; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; 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.Mirror; import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; import org.jspecify.annotations.Nullable; public class PackModelEntityBlock extends Block implements EntityBlock { + public static final EnumProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + public PackModelEntityBlock(BlockBehaviour.Properties properties) { super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(HORIZONTAL_FACING, Direction.NORTH)); } @Nullable @@ -35,6 +45,26 @@ protected void spawnDestroyParticles(Level level, Player player, BlockPos pos, B // so vanilla block-break particles would resolve to missing textures. } + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(HORIZONTAL_FACING); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState().setValue(HORIZONTAL_FACING, context.getHorizontalDirection().getOpposite()); + } + + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return state.setValue(HORIZONTAL_FACING, rotation.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return state.rotate(mirror.getRotation(state.getValue(HORIZONTAL_FACING))); + } + @Nullable @Override @SuppressWarnings("unchecked") From 753db3919e96af626436b6a7502487fd5bfba868 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 18:14:35 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=9D=97=E5=AE=9E=E4=BD=93=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现模型缩放、偏移和旋转调整功能 - 添加配置模式循环切换机制 - 支持使用木棍和烈焰棒进行数值调节 - 实现数据持久化保存和加载 - 添加中英文界面消息支持 --- .../block/entity/PackModelBlockEntity.java | 189 ++++++++++++++++-- .../block/entity/PackModelEntityBlock.java | 45 +++++ .../resources/assets/box3/lang/en_us.json | 8 +- .../resources/assets/box3/lang/zh_cn.json | 8 +- 4 files changed, 227 insertions(+), 23 deletions(-) diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java index 97f5f4a..d16d0ce 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -1,16 +1,25 @@ package com.box3lab.block.entity; +import java.util.Locale; + import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; +import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.permissions.PermissionSet; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Display; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; @@ -18,10 +27,45 @@ public class PackModelBlockEntity extends BlockEntity { private static final long RESPAWN_INTERVAL_TICKS = 20L; private static final String DISPLAY_TAG_PREFIX = "box3_pack_model:"; + private static final float SCALE_STEP = 0.1F; + private static final float SCALE_MIN = 0.1F; + private static final float SCALE_MAX = 4.0F; + private static final float OFFSET_STEP = 0.05F; + private static final float ROTATION_STEP = 15.0F; + + private float scale = 1.0F; + private float offsetX = 0.0F; + private float offsetY = 0.0F; + private float offsetZ = 0.0F; + private float rotationOffset = 0.0F; + private int modeIndex = 0; + public PackModelBlockEntity(BlockPos pos, BlockState state) { super(PackModelBlockEntityRegistrar.typeFor(state.getBlock()), pos, state); } + @Override + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + this.scale = clamp(input.getFloatOr("scale", 1.0F), SCALE_MIN, SCALE_MAX); + this.offsetX = input.getFloatOr("offset_x", 0.0F); + this.offsetY = input.getFloatOr("offset_y", 0.0F); + this.offsetZ = input.getFloatOr("offset_z", 0.0F); + this.rotationOffset = input.getFloatOr("rotation_offset", 0.0F); + this.modeIndex = Math.floorMod(input.getIntOr("config_mode", 0), Mode.values().length); + } + + @Override + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + output.putFloat("scale", this.scale); + output.putFloat("offset_x", this.offsetX); + output.putFloat("offset_y", this.offsetY); + output.putFloat("offset_z", this.offsetZ); + output.putFloat("rotation_offset", this.rotationOffset); + output.putInt("config_mode", this.modeIndex); + } + @Override public void setRemoved() { removeDisplaysAt(this.level, this.getBlockPos()); @@ -32,20 +76,48 @@ public static void serverTick(Level level, BlockPos pos, BlockState state, PackM if (!(level instanceof ServerLevel serverLevel)) { return; } - if (serverLevel.getGameTime() % RESPAWN_INTERVAL_TICKS != 0) { - return; - } String tag = displayTag(pos); - var displays = serverLevel.getEntitiesOfClass( - Display.ItemDisplay.class, - new AABB(pos), - display -> display.getTags().contains(tag)); - if (!displays.isEmpty()) { + var displays = findDisplays(serverLevel, pos, tag); + + if (displays.isEmpty()) { + if (serverLevel.getGameTime() % RESPAWN_INTERVAL_TICKS != 0) { + return; + } + spawnDisplay(serverLevel, pos, state, blockEntity, tag); return; } - spawnDisplay(serverLevel, pos, state, tag); + for (Display.ItemDisplay display : displays) { + blockEntity.applyPose(serverLevel, pos, state, display); + } + } + + public void cycleMode(net.minecraft.world.entity.player.Player player) { + this.modeIndex = (this.modeIndex + 1) % Mode.values().length; + this.setChanged(); + player.displayClientMessage(Component.translatable( + "message.box3.model.config.mode", + Component.translatable(currentMode().translationKey())), true); + } + + public void adjustCurrentMode(ServerLevel level, BlockPos pos, BlockState state, int direction, + net.minecraft.world.entity.player.Player player) { + Mode mode = currentMode(); + switch (mode) { + case SCALE -> this.scale = clamp(this.scale + SCALE_STEP * direction, SCALE_MIN, SCALE_MAX); + case OFFSET_X -> this.offsetX += OFFSET_STEP * direction; + case OFFSET_Y -> this.offsetY += OFFSET_STEP * direction; + case OFFSET_Z -> this.offsetZ += OFFSET_STEP * direction; + case ROTATION -> this.rotationOffset = normalizeDegrees(this.rotationOffset + ROTATION_STEP * direction); + } + + this.setChanged(); + player.displayClientMessage(statusComponent(), true); + + for (Display.ItemDisplay display : findDisplays(level, pos, displayTag(pos))) { + applyPose(level, pos, state, display); + } } public static void removeDisplaysAt(Level level, BlockPos pos) { @@ -54,35 +126,110 @@ public static void removeDisplaysAt(Level level, BlockPos pos) { } String tag = displayTag(pos); - var displays = serverLevel.getEntitiesOfClass( - Display.ItemDisplay.class, - new AABB(pos).inflate(0.25D), - display -> display.getTags().contains(tag)); - for (Display.ItemDisplay display : displays) { + for (Display.ItemDisplay display : findDisplays(serverLevel, pos, tag)) { display.discard(); } } - private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState state, String tag) { + private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState state, PackModelBlockEntity be, String tag) { Display.ItemDisplay display = EntityType.ITEM_DISPLAY.create(level, EntitySpawnReason.SPAWN_ITEM_USE); if (display == null) { return; } - Vec3 center = Vec3.atCenterOf(pos); - display.setPos(center.x, center.y, center.z); display.setNoGravity(true); display.setInvulnerable(true); - if (state.hasProperty(PackModelEntityBlock.HORIZONTAL_FACING)) { - display.setYRot(state.getValue(PackModelEntityBlock.HORIZONTAL_FACING).toYRot()); - } display.getSlot(0).set(new ItemStack(state.getBlock())); display.addTag(tag); + be.applyPose(level, pos, state, display); level.addFreshEntity(display); } + private void applyPose(ServerLevel level, BlockPos pos, BlockState state, Display.ItemDisplay display) { + double x = pos.getX() + 0.5D; + double y = pos.getY() + this.offsetY; + double z = pos.getZ() + 0.5D; + display.setPos(x, y, z); + + float baseYaw = 0.0F; + if (state.hasProperty(PackModelEntityBlock.HORIZONTAL_FACING)) { + Direction facing = state.getValue(PackModelEntityBlock.HORIZONTAL_FACING); + baseYaw = facing.toYRot(); + } + display.setYRot(normalizeDegrees(baseYaw + this.rotationOffset)); + + applyDisplayTransformation(level, display); + } + + private void applyDisplayTransformation(ServerLevel level, Display.ItemDisplay display) { + MinecraftServer server = level.getServer(); + CommandSourceStack source = server.createCommandSourceStack().withSuppressedOutput() + .withPermission(PermissionSet.ALL_PERMISSIONS); + + String cmd = String.format( + Locale.ROOT, + "data merge entity %s {item_display:\"fixed\",transformation:{translation:[%sf,%sf,%sf],left_rotation:[0f,0f,0f,1f],scale:[%sf,%sf,%sf],right_rotation:[0f,0f,0f,1f]}}", + display.getStringUUID(), + fmt(this.offsetX), fmt(0.0F), fmt(this.offsetZ), + fmt(this.scale), fmt(this.scale), fmt(this.scale)); + server.getCommands().performPrefixedCommand(source, cmd); + } + + private static String fmt(float value) { + return String.format(Locale.ROOT, "%.3f", value); + } + + private static java.util.List findDisplays(ServerLevel level, BlockPos pos, String tag) { + return level.getEntitiesOfClass( + Display.ItemDisplay.class, + new AABB(pos).inflate(0.25D), + display -> display.getTags().contains(tag)); + } + + private Mode currentMode() { + return Mode.values()[this.modeIndex]; + } + + private Component statusComponent() { + return Component.translatable( + "message.box3.model.config.status", + Component.translatable(currentMode().translationKey()), + String.format(Locale.ROOT, "%.2f", this.scale), + String.format(Locale.ROOT, "%.2f", this.offsetX), + String.format(Locale.ROOT, "%.2f", this.offsetY), + String.format(Locale.ROOT, "%.2f", this.offsetZ), + String.format(Locale.ROOT, "%.1f", this.rotationOffset)); + } + + private static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } + + private static float normalizeDegrees(float value) { + float v = value % 360.0F; + return v < 0.0F ? v + 360.0F : v; + } + private static String displayTag(BlockPos pos) { return DISPLAY_TAG_PREFIX + pos.asLong(); } + + private enum Mode { + SCALE("scale"), + OFFSET_X("offset_x"), + OFFSET_Y("offset_y"), + OFFSET_Z("offset_z"), + ROTATION("rotation"); + + private final String keyPart; + + Mode(String keyPart) { + this.keyPart = keyPart; + } + + public String translationKey() { + return "message.box3.model.config.mode." + this.keyPart; + } + } } diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java index 8053704..cc9e0ac 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -2,7 +2,11 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -18,6 +22,7 @@ import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.BlockHitResult; import org.jspecify.annotations.Nullable; public class PackModelEntityBlock extends Block implements EntityBlock { @@ -65,6 +70,46 @@ public BlockState mirror(BlockState state, Mirror mirror) { return state.rotate(mirror.getRotation(state.getValue(HORIZONTAL_FACING))); } + @Override + protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, + BlockHitResult hitResult) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe) { + modelBe.cycleMode(player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + @Override + protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, + InteractionHand hand, BlockHitResult hitResult) { + int direction; + if (stack.is(Items.STICK)) { + direction = 1; + } else if (stack.is(Items.BLAZE_ROD)) { + direction = -1; + } else { + return InteractionResult.TRY_WITH_EMPTY_HAND; + } + + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe && level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.adjustCurrentMode(serverLevel, pos, state, direction, player); + return InteractionResult.SUCCESS; + } + + return InteractionResult.PASS; + } + @Nullable @Override @SuppressWarnings("unchecked") diff --git a/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json b/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json index 9b34656..0dc4de0 100644 --- a/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json +++ b/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json @@ -411,6 +411,12 @@ "command.box3.box3barrier.status": "Barrier visible: %s", "command.box3.box3barrier.set": "Barrier visibility set to: %s", "command.box3.box3barrier.toggled": "Barrier visibility toggled to: %s (re-enter the world to fully apply)", - "item.box3.model_destroyer": "Model destruction bucket", + "message.box3.model.config.mode": "Model config mode: %s (Stick: +, Blaze Rod: -)", + "message.box3.model.config.status": "mode=%s scale=%s offset=(%s, %s, %s) rot=%s", + "message.box3.model.config.mode.scale": "Scale", + "message.box3.model.config.mode.offset_x": "Offset X", + "message.box3.model.config.mode.offset_y": "Offset Y", + "message.box3.model.config.mode.offset_z": "Offset Z", + "message.box3.model.config.mode.rotation": "Rotation", "flat_world_preset.box3.box3_plains_world": "Box3 Plains" } diff --git a/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json b/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json index 29bebcc..38e5af4 100644 --- a/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json +++ b/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json @@ -399,6 +399,12 @@ "command.box3.box3barrier.status": "屏障可见状态:%s", "command.box3.box3barrier.set": "屏障可见状态已设置为:%s", "command.box3.box3barrier.toggled": "屏障可见状态已切换为:%s(重新进入世界以完全生效)", - "item.box3.model_destroyer": "模型销毁桶", + "message.box3.model.config.mode": "模型配置模式:%s(木棍:增加,烈焰棒:减少)", + "message.box3.model.config.status": "模式=%s 缩放=%s 偏移=(%s, %s, %s) 旋转=%s", + "message.box3.model.config.mode.scale": "缩放", + "message.box3.model.config.mode.offset_x": "X偏移", + "message.box3.model.config.mode.offset_y": "Y偏移", + "message.box3.model.config.mode.offset_z": "Z偏移", + "message.box3.model.config.mode.rotation": "旋转", "flat_world_preset.box3.box3_plains_world": "神岛平原" } From 4b6a0a73e92582a10c1b8d876c99868790efdd57 Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 18:20:19 +0800 Subject: [PATCH 06/11] =?UTF-8?q?feat(model):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E7=9A=84=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E7=B2=98=E8=B4=B4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在PackModelBlockEntity中添加CONFIG_CLIPBOARD静态变量用于存储玩家的配置快照 - 实现copyConfig方法将当前配置保存到剪贴板 - 实现pasteConfig方法从剪贴板恢复配置 - 新增ConfigSnapshot记录类用于保存配置状态 - 在PackModelEntityBlock中添加对纸张和书籍的支持 - 纸张用于复制当前模型配置,书籍用于粘贴配置 - 更新中英文语言文件,添加相关提示信息 - 修改applyPose逻辑为独立的applyToDisplays方法便于复用 --- .../block/entity/PackModelBlockEntity.java | 38 ++++++++++++++++++- .../block/entity/PackModelEntityBlock.java | 24 ++++++++++++ .../resources/assets/box3/lang/en_us.json | 5 ++- .../resources/assets/box3/lang/zh_cn.json | 5 ++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java index d16d0ce..df361f1 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -1,6 +1,9 @@ package com.box3lab.block.entity; import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; @@ -32,6 +35,7 @@ public class PackModelBlockEntity extends BlockEntity { private static final float SCALE_MAX = 4.0F; private static final float OFFSET_STEP = 0.05F; private static final float ROTATION_STEP = 15.0F; + private static final Map CONFIG_CLIPBOARD = new ConcurrentHashMap<>(); private float scale = 1.0F; private float offsetX = 0.0F; @@ -114,10 +118,31 @@ public void adjustCurrentMode(ServerLevel level, BlockPos pos, BlockState state, this.setChanged(); player.displayClientMessage(statusComponent(), true); + applyToDisplays(level, pos, state); + } - for (Display.ItemDisplay display : findDisplays(level, pos, displayTag(pos))) { - applyPose(level, pos, state, display); + public void copyConfig(net.minecraft.world.entity.player.Player player) { + CONFIG_CLIPBOARD.put(player.getUUID(), new ConfigSnapshot(this.scale, this.offsetX, this.offsetY, this.offsetZ, this.rotationOffset)); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.success"), true); + } + + public void pasteConfig(ServerLevel level, BlockPos pos, BlockState state, net.minecraft.world.entity.player.Player player) { + ConfigSnapshot snapshot = CONFIG_CLIPBOARD.get(player.getUUID()); + if (snapshot == null) { + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.empty"), true); + return; } + + this.scale = clamp(snapshot.scale, SCALE_MIN, SCALE_MAX); + this.offsetX = snapshot.offsetX; + this.offsetY = snapshot.offsetY; + this.offsetZ = snapshot.offsetZ; + this.rotationOffset = normalizeDegrees(snapshot.rotationOffset); + this.setChanged(); + + applyToDisplays(level, pos, state); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.pasted"), true); + player.displayClientMessage(statusComponent(), true); } public static void removeDisplaysAt(Level level, BlockPos pos) { @@ -187,6 +212,12 @@ private static java.util.List findDisplays(ServerLevel leve display -> display.getTags().contains(tag)); } + private void applyToDisplays(ServerLevel level, BlockPos pos, BlockState state) { + for (Display.ItemDisplay display : findDisplays(level, pos, displayTag(pos))) { + applyPose(level, pos, state, display); + } + } + private Mode currentMode() { return Mode.values()[this.modeIndex]; } @@ -232,4 +263,7 @@ public String translationKey() { return "message.box3.model.config.mode." + this.keyPart; } } + + private record ConfigSnapshot(float scale, float offsetX, float offsetY, float offsetZ, float rotationOffset) { + } } diff --git a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java index cc9e0ac..bd449ca 100644 --- a/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java +++ b/Fabric-1.21.11/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -88,6 +88,30 @@ protected InteractionResult useWithoutItem(BlockState state, Level level, BlockP @Override protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult) { + if (stack.is(Items.PAPER)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe) { + modelBe.copyConfig(player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + if (stack.is(Items.BOOK)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe && level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.pasteConfig(serverLevel, pos, state, player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + int direction; if (stack.is(Items.STICK)) { direction = 1; diff --git a/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json b/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json index 0dc4de0..00c4e98 100644 --- a/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json +++ b/Fabric-1.21.11/src/main/resources/assets/box3/lang/en_us.json @@ -411,12 +411,15 @@ "command.box3.box3barrier.status": "Barrier visible: %s", "command.box3.box3barrier.set": "Barrier visibility set to: %s", "command.box3.box3barrier.toggled": "Barrier visibility toggled to: %s (re-enter the world to fully apply)", - "message.box3.model.config.mode": "Model config mode: %s (Stick: +, Blaze Rod: -)", + "message.box3.model.config.mode": "Model config mode: %s (Stick: +, Blaze Rod: -, Paper: copy, Book: paste)", "message.box3.model.config.status": "mode=%s scale=%s offset=(%s, %s, %s) rot=%s", "message.box3.model.config.mode.scale": "Scale", "message.box3.model.config.mode.offset_x": "Offset X", "message.box3.model.config.mode.offset_y": "Offset Y", "message.box3.model.config.mode.offset_z": "Offset Z", "message.box3.model.config.mode.rotation": "Rotation", + "message.box3.model.config.copy.success": "Copied current model config.", + "message.box3.model.config.copy.empty": "No copied config found.", + "message.box3.model.config.copy.pasted": "Pasted copied model config.", "flat_world_preset.box3.box3_plains_world": "Box3 Plains" } diff --git a/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json b/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json index 38e5af4..82b8d1e 100644 --- a/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json +++ b/Fabric-1.21.11/src/main/resources/assets/box3/lang/zh_cn.json @@ -399,12 +399,15 @@ "command.box3.box3barrier.status": "屏障可见状态:%s", "command.box3.box3barrier.set": "屏障可见状态已设置为:%s", "command.box3.box3barrier.toggled": "屏障可见状态已切换为:%s(重新进入世界以完全生效)", - "message.box3.model.config.mode": "模型配置模式:%s(木棍:增加,烈焰棒:减少)", + "message.box3.model.config.mode": "模型配置模式:%s(木棍:增加,烈焰棒:减少,纸:复制,书:粘贴)", "message.box3.model.config.status": "模式=%s 缩放=%s 偏移=(%s, %s, %s) 旋转=%s", "message.box3.model.config.mode.scale": "缩放", "message.box3.model.config.mode.offset_x": "X偏移", "message.box3.model.config.mode.offset_y": "Y偏移", "message.box3.model.config.mode.offset_z": "Z偏移", "message.box3.model.config.mode.rotation": "旋转", + "message.box3.model.config.copy.success": "已复制当前模型参数。", + "message.box3.model.config.copy.empty": "没有可粘贴的已复制参数。", + "message.box3.model.config.copy.pasted": "已粘贴模型参数。", "flat_world_preset.box3.box3_plains_world": "神岛平原" } From 541a8e6b904b7e7431d4bd65db1676f9534a9c2a Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 18:34:29 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat(README):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=93=8D=E4=BD=9C=E8=AF=B4=E6=98=8E=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新README.md中的模型操作说明部分,替换原有的资源包功能描述 为新的模型交互操作指南,包括交互调参和参数复制粘贴功能说明。 --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7a81fc..b691001 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,18 @@ - **资源文件导入**:支持从 `resourcepacks/` 目录文件导入资源包。 - **资源包加载模型**:将模型放入资源包即可自动注册到创造模式。 - **模型物品标签页**:`Box3:模型` 标签页用于管理模型物品。 -- **模型销毁器**:右键模型可删除(道具名:模型销毁器)。 - **生成模型资源包**:访问 https://box3lab.com/mc-resource-pack 获取适用于本模组的资源包文件。 +#### ✨ 操作模型说明 + +- **交互调参**: + - `空手`右键模型:切换模式(缩放 / X偏移 / Y偏移 / Z偏移 / 旋转) + - `木棍`右键模型:当前模式参数增加 + - `烈焰棒`右键模型:当前模式参数减少 +- **参数复制粘贴**: + - `纸`右键模型:复制当前模型参数 + - `书`右键模型:粘贴参数到目标模型模型 + ### 🔍 屏障可见性切换 - **屏障可见性切换 `/box3barrier`**: From 3bb380b0657d57661812dfb7e190e7435f1d43ef Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 18:37:19 +0800 Subject: [PATCH 08/11] =?UTF-8?q?docs(README):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=93=8D=E4=BD=9C=E6=8C=87=E5=8D=97=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AF=A6=E7=BB=86=E5=8F=82=E6=95=B0=E8=B0=83=E8=8A=82?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### ✨ Model Operation Guide - **Interactive parameter tuning**: - Right-click the model with an `empty hand`: switch mode (`Scale / Offset X / Offset Y / Offset Z / Rotation`) - Right-click the model with a `Stick`: increase the current mode value - Right-click the model with a `Blaze Rod`: decrease the current mode value - **Copy & paste parameters**: - Right-click the model with `Paper`: copy current model parameters - Right-click the model with a Book: paste parameters to the target model --- Fabric-1.21.11/gradle.properties | 2 +- README_en.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Fabric-1.21.11/gradle.properties b/Fabric-1.21.11/gradle.properties index 8b57103..1d62dd8 100644 --- a/Fabric-1.21.11/gradle.properties +++ b/Fabric-1.21.11/gradle.properties @@ -12,7 +12,7 @@ loader_version=0.18.4 loom_version=1.15-SNAPSHOT # Mod Properties -mod_version=1.4.1-mc1.21.11 +mod_version=1.4.2-mc1.21.11 maven_group=com.box3lab archives_base_name=box3 diff --git a/README_en.md b/README_en.md index ced4748..df3301e 100644 --- a/README_en.md +++ b/README_en.md @@ -50,6 +50,16 @@ You can also migrate structures from Box3 directly into your Minecraft world, pr - **Model Destroyer**: Right-click a model to delete it (item name: Model Destroyer). - **Generate model resource pack**: Visit https://box3lab.com/mc-resource-pack to get a pack compatible with this mod. +#### ✨ Model Operation Guide + +- **Interactive parameter tuning**: + - Right-click the model with an `empty hand`: switch mode (`Scale / Offset X / Offset Y / Offset Z / Rotation`) + - Right-click the model with a `Stick`: increase the current mode value + - Right-click the model with a `Blaze Rod`: decrease the current mode value +- **Copy & paste parameters**: + - Right-click the model with `Paper`: copy current model parameters + - Right-click the model with a `Book`: paste parameters to the target model + ### 🔍 Barrier Visibility Toggle - **Barrier visibility command `/box3barrier`**: From 06c2db712565c095711f40e1762f110c3840d50a Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 19:34:24 +0800 Subject: [PATCH 09/11] =?UTF-8?q?feat(box3):=20=E7=A7=BB=E9=99=A4=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=A8=A1=E5=9E=8B=E7=89=A9=E5=93=81=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=B9=B6=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E8=87=B31.4.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将mod_version从1.4.1升级到1.4.2-mc1.21.1 - 删除ModelDestroyerItem和ModelDisplayItem类文件 - 移除ModelItemRegistrar和ModelToolRegistrar自动注册机制 - 在ModBlocks中添加PackModelBlockEntityRegistrar注册调用 - 清空ModItems中的模型物品注册代码 - 更新创意标签相关常量名称DEFAULT_TAB为DEFAULT_MODEL_TAB - 添加模型配置模式相关的多语言文本消息 --- Fabric-1.21.1/gradle.properties | 2 +- .../block/entity/PackModelBlockEntity.java | 246 ++++++++++++++++++ .../block/entity/PackModelEntityBlock.java | 160 ++++++++++++ .../com/box3lab/item/ModelDestroyerItem.java | 72 ----- .../com/box3lab/item/ModelDisplayItem.java | 53 ---- .../java/com/box3lab/register/ModBlocks.java | 4 +- .../java/com/box3lab/register/ModItems.java | 5 +- .../box3lab/register/ModelItemRegistrar.java | 131 ---------- .../box3lab/register/ModelToolRegistrar.java | 58 ----- .../creative/CreativeTabRegistrar.java | 10 +- .../PackModelBlockEntityRegistrar.java | 211 +++++++++++++++ .../resources/assets/box3/lang/en_us.json | 11 +- .../resources/assets/box3/lang/zh_cn.json | 11 +- 13 files changed, 647 insertions(+), 327 deletions(-) create mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java create mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java delete mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java delete mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDisplayItem.java delete mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java delete mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java create mode 100644 Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java diff --git a/Fabric-1.21.1/gradle.properties b/Fabric-1.21.1/gradle.properties index 4d1d24b..b5cd0af 100644 --- a/Fabric-1.21.1/gradle.properties +++ b/Fabric-1.21.1/gradle.properties @@ -12,7 +12,7 @@ loader_version=0.18.4 loom_version=1.15-SNAPSHOT # Mod Properties -mod_version=1.4.1-mc1.21.1 +mod_version=1.4.2-mc1.21.1 maven_group=com.box3lab archives_base_name=box3 diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java new file mode 100644 index 0000000..3f4d71a --- /dev/null +++ b/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -0,0 +1,246 @@ +package com.box3lab.block.entity; + +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class PackModelBlockEntity extends BlockEntity { + private static final long RESPAWN_INTERVAL_TICKS = 20L; + private static final String DISPLAY_TAG_PREFIX = "box3_pack_model:"; + + private static final float SCALE_STEP = 0.1F; + private static final float SCALE_MIN = 0.1F; + private static final float SCALE_MAX = 4.0F; + private static final float OFFSET_STEP = 0.05F; + private static final float ROTATION_STEP = 15.0F; + private static final Map CONFIG_CLIPBOARD = new ConcurrentHashMap<>(); + + private float scale = 1.0F; + private float offsetX = 0.0F; + private float offsetY = 0.0F; + private float offsetZ = 0.0F; + private float rotationOffset = 0.0F; + private int modeIndex = 0; + + public PackModelBlockEntity(BlockPos pos, BlockState state) { + super(PackModelBlockEntityRegistrar.typeFor(state.getBlock()), pos, state); + } + + @Override + public void setRemoved() { + removeDisplaysAt(this.level, this.getBlockPos()); + super.setRemoved(); + } + + public static void serverTick(Level level, BlockPos pos, BlockState state, PackModelBlockEntity blockEntity) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + String tag = displayTag(pos); + var displays = findDisplays(serverLevel, pos, tag); + + if (displays.isEmpty()) { + if (serverLevel.getGameTime() % RESPAWN_INTERVAL_TICKS != 0) { + return; + } + spawnDisplay(serverLevel, pos, state, blockEntity, tag); + return; + } + + for (Display.ItemDisplay display : displays) { + blockEntity.applyPose(serverLevel, pos, state, display); + } + } + + public void cycleMode(net.minecraft.world.entity.player.Player player) { + this.modeIndex = (this.modeIndex + 1) % Mode.values().length; + this.setChanged(); + player.displayClientMessage(Component.translatable( + "message.box3.model.config.mode", + Component.translatable(currentMode().translationKey())), true); + } + + public void adjustCurrentMode(ServerLevel level, BlockPos pos, BlockState state, int direction, + net.minecraft.world.entity.player.Player player) { + Mode mode = currentMode(); + switch (mode) { + case SCALE -> this.scale = clamp(this.scale + SCALE_STEP * direction, SCALE_MIN, SCALE_MAX); + case OFFSET_X -> this.offsetX += OFFSET_STEP * direction; + case OFFSET_Y -> this.offsetY += OFFSET_STEP * direction; + case OFFSET_Z -> this.offsetZ += OFFSET_STEP * direction; + case ROTATION -> this.rotationOffset = normalizeDegrees(this.rotationOffset + ROTATION_STEP * direction); + } + + this.setChanged(); + player.displayClientMessage(statusComponent(), true); + applyToDisplays(level, pos, state); + } + + public void copyConfig(net.minecraft.world.entity.player.Player player) { + CONFIG_CLIPBOARD.put(player.getUUID(), + new ConfigSnapshot(this.scale, this.offsetX, this.offsetY, this.offsetZ, this.rotationOffset)); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.success"), true); + } + + public void pasteConfig(ServerLevel level, BlockPos pos, BlockState state, + net.minecraft.world.entity.player.Player player) { + ConfigSnapshot snapshot = CONFIG_CLIPBOARD.get(player.getUUID()); + if (snapshot == null) { + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.empty"), true); + return; + } + + this.scale = clamp(snapshot.scale, SCALE_MIN, SCALE_MAX); + this.offsetX = snapshot.offsetX; + this.offsetY = snapshot.offsetY; + this.offsetZ = snapshot.offsetZ; + this.rotationOffset = normalizeDegrees(snapshot.rotationOffset); + this.setChanged(); + + applyToDisplays(level, pos, state); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.pasted"), true); + player.displayClientMessage(statusComponent(), true); + } + + public static void removeDisplaysAt(Level level, BlockPos pos) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + String tag = displayTag(pos); + for (Display.ItemDisplay display : findDisplays(serverLevel, pos, tag)) { + display.discard(); + } + } + + private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState state, PackModelBlockEntity be, + String tag) { + Display.ItemDisplay display = EntityType.ITEM_DISPLAY.create(level); + if (display == null) { + return; + } + + display.setNoGravity(true); + display.setInvulnerable(true); + display.getSlot(0).set(new ItemStack(state.getBlock())); + display.addTag(tag); + + be.applyPose(level, pos, state, display); + level.addFreshEntity(display); + } + + private void applyPose(ServerLevel level, BlockPos pos, BlockState state, Display.ItemDisplay display) { + double x = pos.getX() + 0.5D; + double y = pos.getY() + this.offsetY; + double z = pos.getZ() + 0.5D; + display.setPos(x, y, z); + + float baseYaw = 0.0F; + if (state.hasProperty(PackModelEntityBlock.HORIZONTAL_FACING)) { + Direction facing = state.getValue(PackModelEntityBlock.HORIZONTAL_FACING); + baseYaw = facing.toYRot(); + } + display.setYRot(normalizeDegrees(baseYaw + this.rotationOffset)); + + applyDisplayTransformation(level, display); + } + + private void applyDisplayTransformation(ServerLevel level, Display.ItemDisplay display) { + MinecraftServer server = level.getServer(); + CommandSourceStack source = server.createCommandSourceStack().withSuppressedOutput() + .withPermission(4); + + String cmd = String.format( + Locale.ROOT, + "data merge entity %s {item_display:\"fixed\",transformation:{translation:[%sf,%sf,%sf],left_rotation:[0f,0f,0f,1f],scale:[%sf,%sf,%sf],right_rotation:[0f,0f,0f,1f]}}", + display.getStringUUID(), + fmt(this.offsetX), fmt(0.0F), fmt(this.offsetZ), + fmt(this.scale), fmt(this.scale), fmt(this.scale)); + server.getCommands().performPrefixedCommand(source, cmd); + } + + private static String fmt(float value) { + return String.format(Locale.ROOT, "%.3f", value); + } + + private static java.util.List findDisplays(ServerLevel level, BlockPos pos, String tag) { + return level.getEntitiesOfClass( + Display.ItemDisplay.class, + new AABB(pos).inflate(0.25D), + display -> display.getTags().contains(tag)); + } + + private void applyToDisplays(ServerLevel level, BlockPos pos, BlockState state) { + for (Display.ItemDisplay display : findDisplays(level, pos, displayTag(pos))) { + applyPose(level, pos, state, display); + } + } + + private Mode currentMode() { + return Mode.values()[this.modeIndex]; + } + + private Component statusComponent() { + return Component.translatable( + "message.box3.model.config.status", + Component.translatable(currentMode().translationKey()), + String.format(Locale.ROOT, "%.2f", this.scale), + String.format(Locale.ROOT, "%.2f", this.offsetX), + String.format(Locale.ROOT, "%.2f", this.offsetY), + String.format(Locale.ROOT, "%.2f", this.offsetZ), + String.format(Locale.ROOT, "%.1f", this.rotationOffset)); + } + + private static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } + + private static float normalizeDegrees(float value) { + float v = value % 360.0F; + return v < 0.0F ? v + 360.0F : v; + } + + private static String displayTag(BlockPos pos) { + return DISPLAY_TAG_PREFIX + pos.asLong(); + } + + private enum Mode { + SCALE("scale"), + OFFSET_X("offset_x"), + OFFSET_Y("offset_y"), + OFFSET_Z("offset_z"), + ROTATION("rotation"); + + private final String keyPart; + + Mode(String keyPart) { + this.keyPart = keyPart; + } + + public String translationKey() { + return "message.box3.model.config.mode." + this.keyPart; + } + } + + private record ConfigSnapshot(float scale, float offsetX, float offsetY, float offsetZ, float rotationOffset) { + } +} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java new file mode 100644 index 0000000..6a9f2ea --- /dev/null +++ b/Fabric-1.21.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -0,0 +1,160 @@ +package com.box3lab.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.ItemInteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.context.BlockPlaceContext; +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.Mirror; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.BlockHitResult; + +public class PackModelEntityBlock extends Block implements EntityBlock { + public static final EnumProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + + public PackModelEntityBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(HORIZONTAL_FACING, Direction.NORTH)); + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new PackModelBlockEntity(pos, state); + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.INVISIBLE; + } + + @Override + protected void spawnDestroyParticles(Level level, Player player, BlockPos pos, BlockState state) { + // This block is rendered by ItemDisplay instead of block model, + // so vanilla block-break particles would resolve to missing textures. + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(HORIZONTAL_FACING); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState().setValue(HORIZONTAL_FACING, context.getHorizontalDirection().getOpposite()); + } + + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return state.setValue(HORIZONTAL_FACING, rotation.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return state.rotate(mirror.getRotation(state.getValue(HORIZONTAL_FACING))); + } + + @Override + protected net.minecraft.world.InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, + Player player, + BlockHitResult hitResult) { + if (level.isClientSide()) { + return net.minecraft.world.InteractionResult.SUCCESS; + } + + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe) { + modelBe.cycleMode(player); + return net.minecraft.world.InteractionResult.SUCCESS; + } + return net.minecraft.world.InteractionResult.PASS; + } + + @Override + protected ItemInteractionResult useItemOn(ItemStack stack, BlockState state, Level level, BlockPos pos, + Player player, + InteractionHand hand, BlockHitResult hitResult) { + if (stack.is(Items.PAPER)) { + if (level.isClientSide()) { + return ItemInteractionResult.SUCCESS; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe) { + modelBe.copyConfig(player); + return ItemInteractionResult.SUCCESS; + } + return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + if (stack.is(Items.BOOK)) { + if (level.isClientSide()) { + return ItemInteractionResult.SUCCESS; + } + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe + && level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.pasteConfig(serverLevel, pos, state, player); + return ItemInteractionResult.SUCCESS; + } + return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + int direction; + if (stack.is(Items.STICK)) { + direction = 1; + } else if (stack.is(Items.BLAZE_ROD)) { + direction = -1; + } else { + return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + if (level.isClientSide()) { + return ItemInteractionResult.SUCCESS; + } + + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof PackModelBlockEntity modelBe + && level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.adjustCurrentMode(serverLevel, pos, state, direction, player); + return ItemInteractionResult.SUCCESS; + } + + return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION; + } + + + @Override + @SuppressWarnings("unchecked") + public BlockEntityTicker getTicker(Level level, BlockState state, + BlockEntityType blockEntityType) { + if (level.isClientSide()) { + return null; + } + + BlockEntityType expectedType = com.box3lab.register.modelbe.PackModelBlockEntityRegistrar + .typeFor(state.getBlock()); + if (blockEntityType != expectedType) { + return null; + } + + return (lvl, pos, blockState, be) -> PackModelBlockEntity.serverTick( + lvl, + pos, + blockState, + (PackModelBlockEntity) be); + } +} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java b/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java deleted file mode 100644 index b70e5de..0000000 --- a/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.box3lab.item; - -import java.util.List; - -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; - -public class ModelDestroyerItem extends Item { - private static final double RANGE = 10.0; - - public ModelDestroyerItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Player player = context.getPlayer(); - if (player == null) { - return InteractionResult.PASS; - } - - boolean deleted = deleteTarget(serverLevel, player); - return deleted ? InteractionResult.SUCCESS : InteractionResult.PASS; - } - - private static boolean deleteTarget(ServerLevel level, Player player) { - Display.ItemDisplay target = findDisplay(level, player, RANGE); - if (target == null) { - return false; - } - target.discard(); - return true; - } - - private static Display.ItemDisplay findDisplay(ServerLevel level, Player player, double range) { - Vec3 start = player.getEyePosition(1.0f); - Vec3 look = player.getViewVector(1.0f); - Vec3 end = start.add(look.scale(range)); - AABB searchBox = player.getBoundingBox().expandTowards(look.scale(range)).inflate(1.5); - - List displays = level.getEntitiesOfClass(Display.ItemDisplay.class, searchBox); - Display.ItemDisplay closest = null; - double bestDist = range * range; - - for (Display.ItemDisplay display : displays) { - AABB box = display.getBoundingBox().inflate(0.8); - var hit = box.clip(start, end); - if (hit.isEmpty()) { - continue; - } - double dist = start.distanceToSqr(hit.get()); - if (dist < bestDist) { - bestDist = dist; - closest = display; - } - } - - return closest; - } -} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDisplayItem.java b/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDisplayItem.java deleted file mode 100644 index 7e2d5ca..0000000 --- a/Fabric-1.21.1/src/main/java/com/box3lab/item/ModelDisplayItem.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.box3lab.item; - -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; - -public class ModelDisplayItem extends Item { - public ModelDisplayItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Display.ItemDisplay display = (Display.ItemDisplay) EntityType.ITEM_DISPLAY.create(serverLevel); - if (display == null) { - return InteractionResult.FAIL; - } - - BlockPos placePos = context.getClickedPos().relative(context.getClickedFace()); - Vec3 pos = Vec3.atCenterOf(placePos); - display.setPos(pos.x, pos.y, pos.z); - - Player player = context.getPlayer(); - if (player != null) { - display.setYRot(player.getYRot()); - } - - ItemStack displayStack = context.getItemInHand().copyWithCount(1); - display.getSlot(0).set(displayStack); - - display.setNoGravity(true); - serverLevel.addFreshEntity(display); - - if (player == null || !player.getAbilities().instabuild) { - context.getItemInHand().shrink(1); - } - - return InteractionResult.SUCCESS; - } -} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModBlocks.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModBlocks.java index f815bbb..2853bc1 100644 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModBlocks.java +++ b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModBlocks.java @@ -6,6 +6,7 @@ import com.box3lab.Box3; import com.box3lab.register.core.BlockRegistrar; import com.box3lab.register.creative.CreativeTabRegistrar; +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; import com.box3lab.register.sound.CategorySoundTypes; import com.box3lab.register.voxel.VoxelBlockFactories; import com.box3lab.register.voxel.VoxelBlockPropertiesFactory; @@ -62,6 +63,7 @@ public static void initialize() { } CreativeTabRegistrar.registerCreativeTabs(Box3.MOD_ID, BLOCKS, data); + PackModelBlockEntityRegistrar.registerAll(); } -} \ No newline at end of file +} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModItems.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModItems.java index 798e513..f8f2634 100644 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModItems.java +++ b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModItems.java @@ -6,9 +6,6 @@ private ModItems() { } public static void initialize() { - // Register all model-based display items discovered from resource packs - ModelItemRegistrar.registerAll(); - - ModelToolRegistrar.registerAll(); + // Reserved for future item registrations. } } diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java deleted file mode 100644 index 146e975..0000000 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.box3lab.register; - -import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDisplayItem; -import com.box3lab.register.creative.CreativeTabExtras; -import com.box3lab.register.creative.CreativeTabRegistrar; - -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -public final class ModelItemRegistrar { - private static final String ITEMS_DIR_PREFIX = "assets/" + Box3.MOD_ID + "/items/"; - public static final String DEFAULT_TAB = "models"; - - private ModelItemRegistrar() { - } - - public static void registerAll() { - Set itemPaths = discoverModelItemPaths(); - if (itemPaths.isEmpty()) { - return; - } - - for (String path : itemPaths) { - ResourceLocation id; - try { - id = ResourceLocation.fromNamespaceAndPath(Box3.MOD_ID, path); - } catch (IllegalArgumentException e) { - continue; - } - - if (BuiltInRegistries.ITEM.containsKey(id)) { - continue; - } - - Item item = new ModelDisplayItem(new Item.Properties()); - Registry.register(BuiltInRegistries.ITEM, id, item); - CreativeTabExtras.add(DEFAULT_TAB, item); - } - - CreativeTabRegistrar.registerModelTab(Box3.MOD_ID); - } - - private static Set discoverModelItemPaths() { - Set results = new LinkedHashSet<>(); - Path resourcepacksDir = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); - if (!Files.isDirectory(resourcepacksDir)) { - return results; - } - - try (DirectoryStream stream = Files.newDirectoryStream(resourcepacksDir)) { - for (Path entry : stream) { - if (Files.isDirectory(entry)) { - scanDirectoryPack(entry, results); - } else if (isArchive(entry)) { - scanZipPack(entry, results); - } - } - } catch (IOException ignored) { - } - - return results; - } - - private static boolean isArchive(Path path) { - String name = path.getFileName().toString().toLowerCase(Locale.ROOT); - return name.endsWith(".zip") || name.endsWith(".jar"); - } - - private static void scanDirectoryPack(Path packDir, Set out) { - Path itemsDir = packDir.resolve("assets").resolve(Box3.MOD_ID).resolve("items"); - if (!Files.isDirectory(itemsDir)) { - return; - } - - try (var paths = Files.walk(itemsDir)) { - paths.filter(Files::isRegularFile) - .forEach(file -> { - String name = file.getFileName().toString(); - if (!name.endsWith(".json")) { - return; - } - - String rel = itemsDir.relativize(file).toString().replace(File.separatorChar, '/'); - if (rel.endsWith(".json")) { - rel = rel.substring(0, rel.length() - 5); - } - if (!rel.isBlank()) { - out.add(rel); - } - }); - } catch (IOException ignored) { - } - } - - private static void scanZipPack(Path zipPath, Set out) { - try (ZipFile zip = new ZipFile(zipPath.toFile())) { - Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (entry.isDirectory()) { - continue; - } - String name = entry.getName(); - if (!name.startsWith(ITEMS_DIR_PREFIX) || !name.endsWith(".json")) { - continue; - } - String rel = name.substring(ITEMS_DIR_PREFIX.length(), name.length() - 5); - if (!rel.isBlank()) { - out.add(rel); - } - } - } catch (IOException ignored) { - } - } -} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java deleted file mode 100644 index 7ff2752..0000000 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.box3lab.register; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDestroyerItem; -import com.box3lab.register.creative.CreativeTabExtras; - -import net.fabricmc.fabric.api.event.player.UseEntityCallback; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; - -public final class ModelToolRegistrar { - private ModelToolRegistrar() { - } - - public static void registerAll() { - registerDestroyer(); - registerDestroyerHandler(); - } - - private static void registerDestroyer() { - ResourceLocation id = ResourceLocation.fromNamespaceAndPath(Box3.MOD_ID, "model_destroyer"); - if (BuiltInRegistries.ITEM.containsKey(id)) { - return; - } - - Item item = new ModelDestroyerItem(new Item.Properties().stacksTo(1)); - Registry.register(BuiltInRegistries.ITEM, id, item); - CreativeTabExtras.add(ModelItemRegistrar.DEFAULT_TAB, item); - } - - private static void registerDestroyerHandler() { - UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { - ItemStack stack = player.getItemInHand(hand); - if (!(stack.getItem() instanceof ModelDestroyerItem)) { - return InteractionResult.PASS; - } - - if (!(entity instanceof Display.ItemDisplay display)) { - return InteractionResult.PASS; - } - - if (!(world instanceof ServerLevel)) { - return InteractionResult.SUCCESS; - } - - display.discard(); - return InteractionResult.SUCCESS; - }); - } -} diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java index 2eb5a22..fb1a481 100644 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java +++ b/Fabric-1.21.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java @@ -13,17 +13,17 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraft.world.level.block.Block; import net.minecraft.world.level.ItemLike; - -import static com.box3lab.register.ModelItemRegistrar.DEFAULT_TAB; +import net.minecraft.world.level.block.Block; public final class CreativeTabRegistrar { + public static final String DEFAULT_MODEL_TAB = "models"; + private CreativeTabRegistrar() { } @@ -96,7 +96,7 @@ public static void registerCreativeTabs(String modId, Map blocks, } public static void registerModelTab(String modId) { - String categoryPath = sanitizeCategoryPath(DEFAULT_TAB); + String categoryPath = sanitizeCategoryPath(DEFAULT_MODEL_TAB); if (categoryPath.isBlank()) { return; } diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java new file mode 100644 index 0000000..52db5d8 --- /dev/null +++ b/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -0,0 +1,211 @@ +package com.box3lab.register.modelbe; + +import static com.box3lab.Box3.MOD_ID; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.box3lab.block.entity.PackModelEntityBlock; +import com.box3lab.register.creative.CreativeTabExtras; +import com.box3lab.register.creative.CreativeTabRegistrar; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; + +public final class PackModelBlockEntityRegistrar { + private static final String ASSET_PREFIX = "assets/" + MOD_ID + "/"; + + private static final Map> TYPES_BY_BLOCK = new LinkedHashMap<>(); + + private PackModelBlockEntityRegistrar() { + } + + public static void registerAll() { + Set modelNames = discoverPairedModelNames(); + if (modelNames.isEmpty()) { + return; + } + + for (String name : modelNames) { + ResourceLocation id = ResourceLocation.tryBuild(MOD_ID, name); + if (id == null) { + continue; + } + + ResourceKey blockKey = ResourceKey.create(Registries.BLOCK, id); + if (BuiltInRegistries.BLOCK.containsKey(blockKey)) { + continue; + } + + Block block = new PackModelEntityBlock( + BlockBehaviour.Properties.of() + .mapColor(MapColor.STONE) + .strength(1.5F, 6.0F) + .noOcclusion() + .isViewBlocking((state, level, pos) -> false) + .isSuffocating((state, level, pos) -> false) + .isRedstoneConductor((state, level, pos) -> false)); + Registry.register(BuiltInRegistries.BLOCK, blockKey, block); + + ResourceKey itemKey = ResourceKey.create( + Registries.ITEM, + ResourceLocation.fromNamespaceAndPath(MOD_ID, name)); + if (!BuiltInRegistries.ITEM.containsKey(itemKey)) { + Item item = new BlockItem(block, new Item.Properties()); + Registry.register(BuiltInRegistries.ITEM, itemKey, item); + CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, item); + } + + ResourceKey> blockEntityKey = ResourceKey.create(Registries.BLOCK_ENTITY_TYPE, id); + if (BuiltInRegistries.BLOCK_ENTITY_TYPE.containsKey(blockEntityKey)) { + continue; + } + + BlockEntityType type = FabricBlockEntityTypeBuilder + .create(com.box3lab.block.entity.PackModelBlockEntity::new, block) + .build(); + + Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, blockEntityKey, type); + TYPES_BY_BLOCK.put(block, type); + } + + CreativeTabRegistrar.registerModelTab(MOD_ID); + } + + public static BlockEntityType typeFor(Block block) { + BlockEntityType type = TYPES_BY_BLOCK.get(block); + if (type == null) { + throw new IllegalStateException("No block entity type bound for block: " + block); + } + return type; + } + + private static Set discoverPairedModelNames() { + Set result = new LinkedHashSet<>(); + Path packsRoot = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); + if (!Files.isDirectory(packsRoot)) { + return result; + } + + try (var entries = Files.list(packsRoot)) { + entries.forEach(entry -> { + if (Files.isDirectory(entry)) { + collectFromDirectory(entry, result); + } else if (isArchive(entry)) { + collectFromArchive(entry, result); + } + }); + } catch (IOException ignored) { + } + + return result; + } + + private static void collectFromDirectory(Path packDir, Set out) { + Path assetsRoot = packDir.resolve("assets").resolve(MOD_ID); + if (!Files.isDirectory(assetsRoot)) { + return; + } + + Set models = collectBaseNamesFromDirectory(assetsRoot, ".json"); + if (models.isEmpty()) { + return; + } + + Set textures = collectBaseNamesFromDirectory(assetsRoot, ".png"); + if (textures.isEmpty()) { + return; + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } + + private static Set collectBaseNamesFromDirectory(Path root, String suffix) { + Set names = new LinkedHashSet<>(); + try (var files = Files.walk(root)) { + files.filter(Files::isRegularFile).forEach(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.toLowerCase(Locale.ROOT).endsWith(suffix)) { + return; + } + + String base = fileName.substring(0, fileName.length() - suffix.length()).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + names.add(base); + } + }); + } catch (IOException ignored) { + } + return names; + } + + private static void collectFromArchive(Path archive, Set out) { + try (ZipFile zip = new ZipFile(archive.toFile())) { + Set models = new LinkedHashSet<>(); + Set textures = new LinkedHashSet<>(); + + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + continue; + } + + String name = entry.getName(); + if (!name.startsWith(ASSET_PREFIX)) { + continue; + } + + String fileName = name.substring(name.lastIndexOf('/') + 1); + String lower = fileName.toLowerCase(Locale.ROOT); + if (lower.endsWith(".json")) { + String base = fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + models.add(base); + } + } else if (lower.endsWith(".png")) { + String base = fileName.substring(0, fileName.length() - 4).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + textures.add(base); + } + } + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } catch (IOException ignored) { + } + } + + private static boolean isArchive(Path path) { + String name = path.getFileName().toString().toLowerCase(Locale.ROOT); + return name.endsWith(".zip") || name.endsWith(".jar"); + } +} diff --git a/Fabric-1.21.1/src/main/resources/assets/box3/lang/en_us.json b/Fabric-1.21.1/src/main/resources/assets/box3/lang/en_us.json index 9b34656..00c4e98 100644 --- a/Fabric-1.21.1/src/main/resources/assets/box3/lang/en_us.json +++ b/Fabric-1.21.1/src/main/resources/assets/box3/lang/en_us.json @@ -411,6 +411,15 @@ "command.box3.box3barrier.status": "Barrier visible: %s", "command.box3.box3barrier.set": "Barrier visibility set to: %s", "command.box3.box3barrier.toggled": "Barrier visibility toggled to: %s (re-enter the world to fully apply)", - "item.box3.model_destroyer": "Model destruction bucket", + "message.box3.model.config.mode": "Model config mode: %s (Stick: +, Blaze Rod: -, Paper: copy, Book: paste)", + "message.box3.model.config.status": "mode=%s scale=%s offset=(%s, %s, %s) rot=%s", + "message.box3.model.config.mode.scale": "Scale", + "message.box3.model.config.mode.offset_x": "Offset X", + "message.box3.model.config.mode.offset_y": "Offset Y", + "message.box3.model.config.mode.offset_z": "Offset Z", + "message.box3.model.config.mode.rotation": "Rotation", + "message.box3.model.config.copy.success": "Copied current model config.", + "message.box3.model.config.copy.empty": "No copied config found.", + "message.box3.model.config.copy.pasted": "Pasted copied model config.", "flat_world_preset.box3.box3_plains_world": "Box3 Plains" } diff --git a/Fabric-1.21.1/src/main/resources/assets/box3/lang/zh_cn.json b/Fabric-1.21.1/src/main/resources/assets/box3/lang/zh_cn.json index 29bebcc..82b8d1e 100644 --- a/Fabric-1.21.1/src/main/resources/assets/box3/lang/zh_cn.json +++ b/Fabric-1.21.1/src/main/resources/assets/box3/lang/zh_cn.json @@ -399,6 +399,15 @@ "command.box3.box3barrier.status": "屏障可见状态:%s", "command.box3.box3barrier.set": "屏障可见状态已设置为:%s", "command.box3.box3barrier.toggled": "屏障可见状态已切换为:%s(重新进入世界以完全生效)", - "item.box3.model_destroyer": "模型销毁桶", + "message.box3.model.config.mode": "模型配置模式:%s(木棍:增加,烈焰棒:减少,纸:复制,书:粘贴)", + "message.box3.model.config.status": "模式=%s 缩放=%s 偏移=(%s, %s, %s) 旋转=%s", + "message.box3.model.config.mode.scale": "缩放", + "message.box3.model.config.mode.offset_x": "X偏移", + "message.box3.model.config.mode.offset_y": "Y偏移", + "message.box3.model.config.mode.offset_z": "Z偏移", + "message.box3.model.config.mode.rotation": "旋转", + "message.box3.model.config.copy.success": "已复制当前模型参数。", + "message.box3.model.config.copy.empty": "没有可粘贴的已复制参数。", + "message.box3.model.config.copy.pasted": "已粘贴模型参数。", "flat_world_preset.box3.box3_plains_world": "神岛平原" } From 86cb1e901304e1c72e3269bad917d06966cc742c Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 20:07:59 +0800 Subject: [PATCH 10/11] =?UTF-8?q?feat(box3):=20=E7=A7=BB=E9=99=A4=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=A8=A1=E5=9E=8B=E7=89=A9=E5=93=81=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将mod_version从1.4.1-mc1.20.1更新到1.4.2-mc1.20.1 - 删除ModelDestroyerItem和ModelDisplayItem类 - 从ModItems中移除ModelItemRegistrar和ModelToolRegistrar的注册调用 - 添加PackModelBlockEntityRegistrar的注册调用 - 更新创意标签相关代码,将DEFAULT_TAB改为DEFAULT_MODEL_TAB - 从可见平面世界预设标签中移除box3_custom_noise - 添加模型配置模式相关的语言文件条目 --- Fabric-1.20.1/gradle.properties | 2 +- .../block/entity/PackModelBlockEntity.java | 246 ++++++++++++++++++ .../block/entity/PackModelEntityBlock.java | 157 +++++++++++ .../com/box3lab/item/ModelDestroyerItem.java | 72 ----- .../com/box3lab/item/ModelDisplayItem.java | 53 ---- .../java/com/box3lab/register/ModBlocks.java | 4 +- .../java/com/box3lab/register/ModItems.java | 5 +- .../box3lab/register/ModelItemRegistrar.java | 131 ---------- .../box3lab/register/ModelToolRegistrar.java | 56 ---- .../creative/CreativeTabRegistrar.java | 5 +- .../PackModelBlockEntityRegistrar.java | 211 +++++++++++++++ .../resources/assets/box3/lang/en_us.json | 11 +- .../resources/assets/box3/lang/zh_cn.json | 11 +- .../flat_level_generator_preset/visible.json | 2 +- 14 files changed, 643 insertions(+), 323 deletions(-) create mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java create mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java delete mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java delete mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDisplayItem.java delete mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java delete mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java create mode 100644 Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java diff --git a/Fabric-1.20.1/gradle.properties b/Fabric-1.20.1/gradle.properties index 4411101..475ba0e 100644 --- a/Fabric-1.20.1/gradle.properties +++ b/Fabric-1.20.1/gradle.properties @@ -12,7 +12,7 @@ loader_version=0.18.4 loom_version=1.15-SNAPSHOT # Mod Properties -mod_version=1.4.1-mc1.20.1 +mod_version=1.4.2-mc1.20.1 maven_group=com.box3lab archives_base_name=box3 diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java b/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java new file mode 100644 index 0000000..3f4d71a --- /dev/null +++ b/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelBlockEntity.java @@ -0,0 +1,246 @@ +package com.box3lab.block.entity; + +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; + +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Display; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class PackModelBlockEntity extends BlockEntity { + private static final long RESPAWN_INTERVAL_TICKS = 20L; + private static final String DISPLAY_TAG_PREFIX = "box3_pack_model:"; + + private static final float SCALE_STEP = 0.1F; + private static final float SCALE_MIN = 0.1F; + private static final float SCALE_MAX = 4.0F; + private static final float OFFSET_STEP = 0.05F; + private static final float ROTATION_STEP = 15.0F; + private static final Map CONFIG_CLIPBOARD = new ConcurrentHashMap<>(); + + private float scale = 1.0F; + private float offsetX = 0.0F; + private float offsetY = 0.0F; + private float offsetZ = 0.0F; + private float rotationOffset = 0.0F; + private int modeIndex = 0; + + public PackModelBlockEntity(BlockPos pos, BlockState state) { + super(PackModelBlockEntityRegistrar.typeFor(state.getBlock()), pos, state); + } + + @Override + public void setRemoved() { + removeDisplaysAt(this.level, this.getBlockPos()); + super.setRemoved(); + } + + public static void serverTick(Level level, BlockPos pos, BlockState state, PackModelBlockEntity blockEntity) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + String tag = displayTag(pos); + var displays = findDisplays(serverLevel, pos, tag); + + if (displays.isEmpty()) { + if (serverLevel.getGameTime() % RESPAWN_INTERVAL_TICKS != 0) { + return; + } + spawnDisplay(serverLevel, pos, state, blockEntity, tag); + return; + } + + for (Display.ItemDisplay display : displays) { + blockEntity.applyPose(serverLevel, pos, state, display); + } + } + + public void cycleMode(net.minecraft.world.entity.player.Player player) { + this.modeIndex = (this.modeIndex + 1) % Mode.values().length; + this.setChanged(); + player.displayClientMessage(Component.translatable( + "message.box3.model.config.mode", + Component.translatable(currentMode().translationKey())), true); + } + + public void adjustCurrentMode(ServerLevel level, BlockPos pos, BlockState state, int direction, + net.minecraft.world.entity.player.Player player) { + Mode mode = currentMode(); + switch (mode) { + case SCALE -> this.scale = clamp(this.scale + SCALE_STEP * direction, SCALE_MIN, SCALE_MAX); + case OFFSET_X -> this.offsetX += OFFSET_STEP * direction; + case OFFSET_Y -> this.offsetY += OFFSET_STEP * direction; + case OFFSET_Z -> this.offsetZ += OFFSET_STEP * direction; + case ROTATION -> this.rotationOffset = normalizeDegrees(this.rotationOffset + ROTATION_STEP * direction); + } + + this.setChanged(); + player.displayClientMessage(statusComponent(), true); + applyToDisplays(level, pos, state); + } + + public void copyConfig(net.minecraft.world.entity.player.Player player) { + CONFIG_CLIPBOARD.put(player.getUUID(), + new ConfigSnapshot(this.scale, this.offsetX, this.offsetY, this.offsetZ, this.rotationOffset)); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.success"), true); + } + + public void pasteConfig(ServerLevel level, BlockPos pos, BlockState state, + net.minecraft.world.entity.player.Player player) { + ConfigSnapshot snapshot = CONFIG_CLIPBOARD.get(player.getUUID()); + if (snapshot == null) { + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.empty"), true); + return; + } + + this.scale = clamp(snapshot.scale, SCALE_MIN, SCALE_MAX); + this.offsetX = snapshot.offsetX; + this.offsetY = snapshot.offsetY; + this.offsetZ = snapshot.offsetZ; + this.rotationOffset = normalizeDegrees(snapshot.rotationOffset); + this.setChanged(); + + applyToDisplays(level, pos, state); + player.displayClientMessage(Component.translatable("message.box3.model.config.copy.pasted"), true); + player.displayClientMessage(statusComponent(), true); + } + + public static void removeDisplaysAt(Level level, BlockPos pos) { + if (!(level instanceof ServerLevel serverLevel)) { + return; + } + + String tag = displayTag(pos); + for (Display.ItemDisplay display : findDisplays(serverLevel, pos, tag)) { + display.discard(); + } + } + + private static void spawnDisplay(ServerLevel level, BlockPos pos, BlockState state, PackModelBlockEntity be, + String tag) { + Display.ItemDisplay display = EntityType.ITEM_DISPLAY.create(level); + if (display == null) { + return; + } + + display.setNoGravity(true); + display.setInvulnerable(true); + display.getSlot(0).set(new ItemStack(state.getBlock())); + display.addTag(tag); + + be.applyPose(level, pos, state, display); + level.addFreshEntity(display); + } + + private void applyPose(ServerLevel level, BlockPos pos, BlockState state, Display.ItemDisplay display) { + double x = pos.getX() + 0.5D; + double y = pos.getY() + this.offsetY; + double z = pos.getZ() + 0.5D; + display.setPos(x, y, z); + + float baseYaw = 0.0F; + if (state.hasProperty(PackModelEntityBlock.HORIZONTAL_FACING)) { + Direction facing = state.getValue(PackModelEntityBlock.HORIZONTAL_FACING); + baseYaw = facing.toYRot(); + } + display.setYRot(normalizeDegrees(baseYaw + this.rotationOffset)); + + applyDisplayTransformation(level, display); + } + + private void applyDisplayTransformation(ServerLevel level, Display.ItemDisplay display) { + MinecraftServer server = level.getServer(); + CommandSourceStack source = server.createCommandSourceStack().withSuppressedOutput() + .withPermission(4); + + String cmd = String.format( + Locale.ROOT, + "data merge entity %s {item_display:\"fixed\",transformation:{translation:[%sf,%sf,%sf],left_rotation:[0f,0f,0f,1f],scale:[%sf,%sf,%sf],right_rotation:[0f,0f,0f,1f]}}", + display.getStringUUID(), + fmt(this.offsetX), fmt(0.0F), fmt(this.offsetZ), + fmt(this.scale), fmt(this.scale), fmt(this.scale)); + server.getCommands().performPrefixedCommand(source, cmd); + } + + private static String fmt(float value) { + return String.format(Locale.ROOT, "%.3f", value); + } + + private static java.util.List findDisplays(ServerLevel level, BlockPos pos, String tag) { + return level.getEntitiesOfClass( + Display.ItemDisplay.class, + new AABB(pos).inflate(0.25D), + display -> display.getTags().contains(tag)); + } + + private void applyToDisplays(ServerLevel level, BlockPos pos, BlockState state) { + for (Display.ItemDisplay display : findDisplays(level, pos, displayTag(pos))) { + applyPose(level, pos, state, display); + } + } + + private Mode currentMode() { + return Mode.values()[this.modeIndex]; + } + + private Component statusComponent() { + return Component.translatable( + "message.box3.model.config.status", + Component.translatable(currentMode().translationKey()), + String.format(Locale.ROOT, "%.2f", this.scale), + String.format(Locale.ROOT, "%.2f", this.offsetX), + String.format(Locale.ROOT, "%.2f", this.offsetY), + String.format(Locale.ROOT, "%.2f", this.offsetZ), + String.format(Locale.ROOT, "%.1f", this.rotationOffset)); + } + + private static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(max, value)); + } + + private static float normalizeDegrees(float value) { + float v = value % 360.0F; + return v < 0.0F ? v + 360.0F : v; + } + + private static String displayTag(BlockPos pos) { + return DISPLAY_TAG_PREFIX + pos.asLong(); + } + + private enum Mode { + SCALE("scale"), + OFFSET_X("offset_x"), + OFFSET_Y("offset_y"), + OFFSET_Z("offset_z"), + ROTATION("rotation"); + + private final String keyPart; + + Mode(String keyPart) { + this.keyPart = keyPart; + } + + public String translationKey() { + return "message.box3.model.config.mode." + this.keyPart; + } + } + + private record ConfigSnapshot(float scale, float offsetX, float offsetY, float offsetZ, float rotationOffset) { + } +} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java b/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java new file mode 100644 index 0000000..22a8a60 --- /dev/null +++ b/Fabric-1.20.1/src/main/java/com/box3lab/block/entity/PackModelEntityBlock.java @@ -0,0 +1,157 @@ +package com.box3lab.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.context.BlockPlaceContext; +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.Mirror; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.BlockHitResult; + +public class PackModelEntityBlock extends Block implements EntityBlock { + public static final EnumProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + + public PackModelEntityBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(HORIZONTAL_FACING, Direction.NORTH)); + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new PackModelBlockEntity(pos, state); + } + + @Override + public RenderShape getRenderShape(BlockState state) { + return RenderShape.INVISIBLE; + } + + @Override + protected void spawnDestroyParticles(Level level, Player player, BlockPos pos, BlockState state) { + // This block is rendered by ItemDisplay instead of block model, + // so vanilla block-break particles would resolve to missing textures. + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(HORIZONTAL_FACING); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState().setValue(HORIZONTAL_FACING, context.getHorizontalDirection().getOpposite()); + } + + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return state.setValue(HORIZONTAL_FACING, rotation.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return state.rotate(mirror.getRotation(state.getValue(HORIZONTAL_FACING))); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, + InteractionHand hand, BlockHitResult hitResult) { + if (hand != InteractionHand.MAIN_HAND) { + return InteractionResult.PASS; + } + + ItemStack stack = player.getItemInHand(hand); + BlockEntity be = level.getBlockEntity(pos); + if (!(be instanceof PackModelBlockEntity modelBe)) { + return InteractionResult.PASS; + } + + if (stack.is(Items.PAPER)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + modelBe.copyConfig(player); + return InteractionResult.SUCCESS; + } + + if (stack.is(Items.BOOK)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + if (level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.pasteConfig(serverLevel, pos, state, player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + if (stack.is(Items.STICK)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + if (level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.adjustCurrentMode(serverLevel, pos, state, 1, player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + if (stack.is(Items.BLAZE_ROD)) { + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + if (level instanceof net.minecraft.server.level.ServerLevel serverLevel) { + modelBe.adjustCurrentMode(serverLevel, pos, state, -1, player); + return InteractionResult.SUCCESS; + } + return InteractionResult.PASS; + } + + if (!stack.isEmpty()) { + return InteractionResult.PASS; + } + + if (level.isClientSide()) { + return InteractionResult.SUCCESS; + } + + modelBe.cycleMode(player); + return InteractionResult.SUCCESS; + } + + @Override + @SuppressWarnings("unchecked") + public BlockEntityTicker getTicker(Level level, BlockState state, + BlockEntityType blockEntityType) { + if (level.isClientSide()) { + return null; + } + + BlockEntityType expectedType = com.box3lab.register.modelbe.PackModelBlockEntityRegistrar + .typeFor(state.getBlock()); + if (blockEntityType != expectedType) { + return null; + } + + return (lvl, pos, blockState, be) -> PackModelBlockEntity.serverTick( + lvl, + pos, + blockState, + (PackModelBlockEntity) be); + } +} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java b/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java deleted file mode 100644 index b70e5de..0000000 --- a/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDestroyerItem.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.box3lab.item; - -import java.util.List; - -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; - -public class ModelDestroyerItem extends Item { - private static final double RANGE = 10.0; - - public ModelDestroyerItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Player player = context.getPlayer(); - if (player == null) { - return InteractionResult.PASS; - } - - boolean deleted = deleteTarget(serverLevel, player); - return deleted ? InteractionResult.SUCCESS : InteractionResult.PASS; - } - - private static boolean deleteTarget(ServerLevel level, Player player) { - Display.ItemDisplay target = findDisplay(level, player, RANGE); - if (target == null) { - return false; - } - target.discard(); - return true; - } - - private static Display.ItemDisplay findDisplay(ServerLevel level, Player player, double range) { - Vec3 start = player.getEyePosition(1.0f); - Vec3 look = player.getViewVector(1.0f); - Vec3 end = start.add(look.scale(range)); - AABB searchBox = player.getBoundingBox().expandTowards(look.scale(range)).inflate(1.5); - - List displays = level.getEntitiesOfClass(Display.ItemDisplay.class, searchBox); - Display.ItemDisplay closest = null; - double bestDist = range * range; - - for (Display.ItemDisplay display : displays) { - AABB box = display.getBoundingBox().inflate(0.8); - var hit = box.clip(start, end); - if (hit.isEmpty()) { - continue; - } - double dist = start.distanceToSqr(hit.get()); - if (dist < bestDist) { - bestDist = dist; - closest = display; - } - } - - return closest; - } -} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDisplayItem.java b/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDisplayItem.java deleted file mode 100644 index 7e2d5ca..0000000 --- a/Fabric-1.20.1/src/main/java/com/box3lab/item/ModelDisplayItem.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.box3lab.item; - -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; - -public class ModelDisplayItem extends Item { - public ModelDisplayItem(Properties properties) { - super(properties); - } - - @Override - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - if (!(level instanceof ServerLevel serverLevel)) { - return InteractionResult.SUCCESS; - } - - Display.ItemDisplay display = (Display.ItemDisplay) EntityType.ITEM_DISPLAY.create(serverLevel); - if (display == null) { - return InteractionResult.FAIL; - } - - BlockPos placePos = context.getClickedPos().relative(context.getClickedFace()); - Vec3 pos = Vec3.atCenterOf(placePos); - display.setPos(pos.x, pos.y, pos.z); - - Player player = context.getPlayer(); - if (player != null) { - display.setYRot(player.getYRot()); - } - - ItemStack displayStack = context.getItemInHand().copyWithCount(1); - display.getSlot(0).set(displayStack); - - display.setNoGravity(true); - serverLevel.addFreshEntity(display); - - if (player == null || !player.getAbilities().instabuild) { - context.getItemInHand().shrink(1); - } - - return InteractionResult.SUCCESS; - } -} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModBlocks.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModBlocks.java index f815bbb..2853bc1 100644 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModBlocks.java +++ b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModBlocks.java @@ -6,6 +6,7 @@ import com.box3lab.Box3; import com.box3lab.register.core.BlockRegistrar; import com.box3lab.register.creative.CreativeTabRegistrar; +import com.box3lab.register.modelbe.PackModelBlockEntityRegistrar; import com.box3lab.register.sound.CategorySoundTypes; import com.box3lab.register.voxel.VoxelBlockFactories; import com.box3lab.register.voxel.VoxelBlockPropertiesFactory; @@ -62,6 +63,7 @@ public static void initialize() { } CreativeTabRegistrar.registerCreativeTabs(Box3.MOD_ID, BLOCKS, data); + PackModelBlockEntityRegistrar.registerAll(); } -} \ No newline at end of file +} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModItems.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModItems.java index 798e513..f8f2634 100644 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModItems.java +++ b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModItems.java @@ -6,9 +6,6 @@ private ModItems() { } public static void initialize() { - // Register all model-based display items discovered from resource packs - ModelItemRegistrar.registerAll(); - - ModelToolRegistrar.registerAll(); + // Reserved for future item registrations. } } diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java deleted file mode 100644 index 895e5ac..0000000 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelItemRegistrar.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.box3lab.register; - -import java.io.File; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDisplayItem; -import com.box3lab.register.creative.CreativeTabExtras; -import com.box3lab.register.creative.CreativeTabRegistrar; - -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; - -public final class ModelItemRegistrar { - private static final String ITEMS_DIR_PREFIX = "assets/" + Box3.MOD_ID + "/items/"; - public static final String DEFAULT_TAB = "models"; - - private ModelItemRegistrar() { - } - - public static void registerAll() { - Set itemPaths = discoverModelItemPaths(); - if (itemPaths.isEmpty()) { - return; - } - - for (String path : itemPaths) { - ResourceLocation id; - try { - id = new ResourceLocation(Box3.MOD_ID, path); - } catch (IllegalArgumentException e) { - continue; - } - - if (BuiltInRegistries.ITEM.containsKey(id)) { - continue; - } - - Item item = new ModelDisplayItem(new Item.Properties()); - Registry.register(BuiltInRegistries.ITEM, id, item); - CreativeTabExtras.add(DEFAULT_TAB, item); - } - - CreativeTabRegistrar.registerModelTab(Box3.MOD_ID); - } - - private static Set discoverModelItemPaths() { - Set results = new LinkedHashSet<>(); - Path resourcepacksDir = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); - if (!Files.isDirectory(resourcepacksDir)) { - return results; - } - - try (DirectoryStream stream = Files.newDirectoryStream(resourcepacksDir)) { - for (Path entry : stream) { - if (Files.isDirectory(entry)) { - scanDirectoryPack(entry, results); - } else if (isArchive(entry)) { - scanZipPack(entry, results); - } - } - } catch (IOException ignored) { - } - - return results; - } - - private static boolean isArchive(Path path) { - String name = path.getFileName().toString().toLowerCase(Locale.ROOT); - return name.endsWith(".zip") || name.endsWith(".jar"); - } - - private static void scanDirectoryPack(Path packDir, Set out) { - Path itemsDir = packDir.resolve("assets").resolve(Box3.MOD_ID).resolve("items"); - if (!Files.isDirectory(itemsDir)) { - return; - } - - try (var paths = Files.walk(itemsDir)) { - paths.filter(Files::isRegularFile) - .forEach(file -> { - String name = file.getFileName().toString(); - if (!name.endsWith(".json")) { - return; - } - - String rel = itemsDir.relativize(file).toString().replace(File.separatorChar, '/'); - if (rel.endsWith(".json")) { - rel = rel.substring(0, rel.length() - 5); - } - if (!rel.isBlank()) { - out.add(rel); - } - }); - } catch (IOException ignored) { - } - } - - private static void scanZipPack(Path zipPath, Set out) { - try (ZipFile zip = new ZipFile(zipPath.toFile())) { - Enumeration entries = zip.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - if (entry.isDirectory()) { - continue; - } - String name = entry.getName(); - if (!name.startsWith(ITEMS_DIR_PREFIX) || !name.endsWith(".json")) { - continue; - } - String rel = name.substring(ITEMS_DIR_PREFIX.length(), name.length() - 5); - if (!rel.isBlank()) { - out.add(rel); - } - } - } catch (IOException ignored) { - } - } -} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java deleted file mode 100644 index c8ed800..0000000 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/ModelToolRegistrar.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.box3lab.register; - -import com.box3lab.Box3; -import com.box3lab.item.ModelDestroyerItem; -import com.box3lab.register.creative.CreativeTabExtras; - -import net.fabricmc.fabric.api.event.player.UseEntityCallback; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Display; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; - -public final class ModelToolRegistrar { - private ModelToolRegistrar() { - } - - public static void registerAll() { - registerDestroyer(); - registerDestroyerHandler(); - } - - private static void registerDestroyer() { - ResourceLocation id = new ResourceLocation(Box3.MOD_ID, "model_destroyer"); - if (BuiltInRegistries.ITEM.containsKey(id)) { - return; - } - - Item item = new ModelDestroyerItem(new Item.Properties().stacksTo(1)); - Registry.register(BuiltInRegistries.ITEM, id, item); - CreativeTabExtras.add(ModelItemRegistrar.DEFAULT_TAB, item); - } - - private static void registerDestroyerHandler() { - UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { - ItemStack stack = player.getItemInHand(hand); - if (!(stack.getItem() instanceof ModelDestroyerItem)) { - return InteractionResult.PASS; - } - - if (!(entity instanceof Display.ItemDisplay display)) { - return InteractionResult.PASS; - } - - if (!(world instanceof ServerLevel)) { - return InteractionResult.SUCCESS; - } - - display.discard(); - return InteractionResult.SUCCESS; - }); - } -} diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java index 3560e16..2560bf6 100644 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java +++ b/Fabric-1.20.1/src/main/java/com/box3lab/register/creative/CreativeTabRegistrar.java @@ -7,7 +7,6 @@ import java.util.Locale; import java.util.Map; -import static com.box3lab.register.ModelItemRegistrar.DEFAULT_TAB; import com.box3lab.util.BlockIndexData; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; @@ -23,6 +22,8 @@ import net.minecraft.world.level.block.Block; public final class CreativeTabRegistrar { + public static final String DEFAULT_MODEL_TAB = "models"; + private CreativeTabRegistrar() { } @@ -95,7 +96,7 @@ public static void registerCreativeTabs(String modId, Map blocks, } public static void registerModelTab(String modId) { - String categoryPath = sanitizeCategoryPath(DEFAULT_TAB); + String categoryPath = sanitizeCategoryPath(DEFAULT_MODEL_TAB); if (categoryPath.isBlank()) { return; } diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java new file mode 100644 index 0000000..a6a6679 --- /dev/null +++ b/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -0,0 +1,211 @@ +package com.box3lab.register.modelbe; + +import static com.box3lab.Box3.MOD_ID; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import com.box3lab.block.entity.PackModelEntityBlock; +import com.box3lab.register.creative.CreativeTabExtras; +import com.box3lab.register.creative.CreativeTabRegistrar; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; + +public final class PackModelBlockEntityRegistrar { + private static final String ASSET_PREFIX = "assets/" + MOD_ID + "/"; + + private static final Map> TYPES_BY_BLOCK = new LinkedHashMap<>(); + + private PackModelBlockEntityRegistrar() { + } + + public static void registerAll() { + Set modelNames = discoverPairedModelNames(); + if (modelNames.isEmpty()) { + return; + } + + for (String name : modelNames) { + ResourceLocation id = ResourceLocation.tryBuild(MOD_ID, name); + if (id == null) { + continue; + } + + ResourceKey blockKey = ResourceKey.create(Registries.BLOCK, id); + if (BuiltInRegistries.BLOCK.containsKey(blockKey)) { + continue; + } + + Block block = new PackModelEntityBlock( + BlockBehaviour.Properties.of() + .mapColor(MapColor.STONE) + .strength(1.5F, 6.0F) + .noOcclusion() + .isViewBlocking((state, level, pos) -> false) + .isSuffocating((state, level, pos) -> false) + .isRedstoneConductor((state, level, pos) -> false)); + Registry.register(BuiltInRegistries.BLOCK, blockKey, block); + + ResourceKey itemKey = ResourceKey.create( + Registries.ITEM, + new ResourceLocation(MOD_ID, name)); + if (!BuiltInRegistries.ITEM.containsKey(itemKey)) { + Item item = new BlockItem(block, new Item.Properties()); + Registry.register(BuiltInRegistries.ITEM, itemKey, item); + CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, item); + } + + ResourceKey> blockEntityKey = ResourceKey.create(Registries.BLOCK_ENTITY_TYPE, id); + if (BuiltInRegistries.BLOCK_ENTITY_TYPE.containsKey(blockEntityKey)) { + continue; + } + + BlockEntityType type = FabricBlockEntityTypeBuilder + .create(com.box3lab.block.entity.PackModelBlockEntity::new, block) + .build(); + + Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, blockEntityKey, type); + TYPES_BY_BLOCK.put(block, type); + } + + CreativeTabRegistrar.registerModelTab(MOD_ID); + } + + public static BlockEntityType typeFor(Block block) { + BlockEntityType type = TYPES_BY_BLOCK.get(block); + if (type == null) { + throw new IllegalStateException("No block entity type bound for block: " + block); + } + return type; + } + + private static Set discoverPairedModelNames() { + Set result = new LinkedHashSet<>(); + Path packsRoot = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); + if (!Files.isDirectory(packsRoot)) { + return result; + } + + try (var entries = Files.list(packsRoot)) { + entries.forEach(entry -> { + if (Files.isDirectory(entry)) { + collectFromDirectory(entry, result); + } else if (isArchive(entry)) { + collectFromArchive(entry, result); + } + }); + } catch (IOException ignored) { + } + + return result; + } + + private static void collectFromDirectory(Path packDir, Set out) { + Path assetsRoot = packDir.resolve("assets").resolve(MOD_ID); + if (!Files.isDirectory(assetsRoot)) { + return; + } + + Set models = collectBaseNamesFromDirectory(assetsRoot, ".json"); + if (models.isEmpty()) { + return; + } + + Set textures = collectBaseNamesFromDirectory(assetsRoot, ".png"); + if (textures.isEmpty()) { + return; + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } + + private static Set collectBaseNamesFromDirectory(Path root, String suffix) { + Set names = new LinkedHashSet<>(); + try (var files = Files.walk(root)) { + files.filter(Files::isRegularFile).forEach(path -> { + String fileName = path.getFileName().toString(); + if (!fileName.toLowerCase(Locale.ROOT).endsWith(suffix)) { + return; + } + + String base = fileName.substring(0, fileName.length() - suffix.length()).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + names.add(base); + } + }); + } catch (IOException ignored) { + } + return names; + } + + private static void collectFromArchive(Path archive, Set out) { + try (ZipFile zip = new ZipFile(archive.toFile())) { + Set models = new LinkedHashSet<>(); + Set textures = new LinkedHashSet<>(); + + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + continue; + } + + String name = entry.getName(); + if (!name.startsWith(ASSET_PREFIX)) { + continue; + } + + String fileName = name.substring(name.lastIndexOf('/') + 1); + String lower = fileName.toLowerCase(Locale.ROOT); + if (lower.endsWith(".json")) { + String base = fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + models.add(base); + } + } else if (lower.endsWith(".png")) { + String base = fileName.substring(0, fileName.length() - 4).toLowerCase(Locale.ROOT); + if (!base.isBlank()) { + textures.add(base); + } + } + } + + for (String model : models) { + if (textures.contains(model)) { + out.add(model); + } + } + } catch (IOException ignored) { + } + } + + private static boolean isArchive(Path path) { + String name = path.getFileName().toString().toLowerCase(Locale.ROOT); + return name.endsWith(".zip") || name.endsWith(".jar"); + } +} diff --git a/Fabric-1.20.1/src/main/resources/assets/box3/lang/en_us.json b/Fabric-1.20.1/src/main/resources/assets/box3/lang/en_us.json index 9b34656..00c4e98 100644 --- a/Fabric-1.20.1/src/main/resources/assets/box3/lang/en_us.json +++ b/Fabric-1.20.1/src/main/resources/assets/box3/lang/en_us.json @@ -411,6 +411,15 @@ "command.box3.box3barrier.status": "Barrier visible: %s", "command.box3.box3barrier.set": "Barrier visibility set to: %s", "command.box3.box3barrier.toggled": "Barrier visibility toggled to: %s (re-enter the world to fully apply)", - "item.box3.model_destroyer": "Model destruction bucket", + "message.box3.model.config.mode": "Model config mode: %s (Stick: +, Blaze Rod: -, Paper: copy, Book: paste)", + "message.box3.model.config.status": "mode=%s scale=%s offset=(%s, %s, %s) rot=%s", + "message.box3.model.config.mode.scale": "Scale", + "message.box3.model.config.mode.offset_x": "Offset X", + "message.box3.model.config.mode.offset_y": "Offset Y", + "message.box3.model.config.mode.offset_z": "Offset Z", + "message.box3.model.config.mode.rotation": "Rotation", + "message.box3.model.config.copy.success": "Copied current model config.", + "message.box3.model.config.copy.empty": "No copied config found.", + "message.box3.model.config.copy.pasted": "Pasted copied model config.", "flat_world_preset.box3.box3_plains_world": "Box3 Plains" } diff --git a/Fabric-1.20.1/src/main/resources/assets/box3/lang/zh_cn.json b/Fabric-1.20.1/src/main/resources/assets/box3/lang/zh_cn.json index 29bebcc..82b8d1e 100644 --- a/Fabric-1.20.1/src/main/resources/assets/box3/lang/zh_cn.json +++ b/Fabric-1.20.1/src/main/resources/assets/box3/lang/zh_cn.json @@ -399,6 +399,15 @@ "command.box3.box3barrier.status": "屏障可见状态:%s", "command.box3.box3barrier.set": "屏障可见状态已设置为:%s", "command.box3.box3barrier.toggled": "屏障可见状态已切换为:%s(重新进入世界以完全生效)", - "item.box3.model_destroyer": "模型销毁桶", + "message.box3.model.config.mode": "模型配置模式:%s(木棍:增加,烈焰棒:减少,纸:复制,书:粘贴)", + "message.box3.model.config.status": "模式=%s 缩放=%s 偏移=(%s, %s, %s) 旋转=%s", + "message.box3.model.config.mode.scale": "缩放", + "message.box3.model.config.mode.offset_x": "X偏移", + "message.box3.model.config.mode.offset_y": "Y偏移", + "message.box3.model.config.mode.offset_z": "Z偏移", + "message.box3.model.config.mode.rotation": "旋转", + "message.box3.model.config.copy.success": "已复制当前模型参数。", + "message.box3.model.config.copy.empty": "没有可粘贴的已复制参数。", + "message.box3.model.config.copy.pasted": "已粘贴模型参数。", "flat_world_preset.box3.box3_plains_world": "神岛平原" } diff --git a/Fabric-1.20.1/src/main/resources/data/minecraft/tags/worldgen/flat_level_generator_preset/visible.json b/Fabric-1.20.1/src/main/resources/data/minecraft/tags/worldgen/flat_level_generator_preset/visible.json index 7809017..3fa8ad8 100644 --- a/Fabric-1.20.1/src/main/resources/data/minecraft/tags/worldgen/flat_level_generator_preset/visible.json +++ b/Fabric-1.20.1/src/main/resources/data/minecraft/tags/worldgen/flat_level_generator_preset/visible.json @@ -1,4 +1,4 @@ { "replace": false, - "values": ["box3:box3_plains_world", "box3:box3_custom_noise"] + "values": ["box3:box3_plains_world"] } From 0eb81ba70e93158fef130e565e8b33aefce01d2c Mon Sep 17 00:00:00 2001 From: viyrs <2991883280@qq.com> Date: Wed, 25 Feb 2026 20:19:20 +0800 Subject: [PATCH 11/11] =?UTF-8?q?feat(register):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=89=A9=E5=93=81=E6=8F=8F=E8=BF=B0ID=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为PackModelBlockEntityRegistrar中的BlockItem添加自定义描述ID实现, 确保物品能够正确显示翻译名称。Fabric 1.20.1版本添加了getDescriptionId 方法重写,Fabric 1.21.1版本添加了getName方法重写,并引入了 ItemStack和Component相关依赖。 --- .../modelbe/PackModelBlockEntityRegistrar.java | 14 +++++++++++++- .../modelbe/PackModelBlockEntityRegistrar.java | 10 +++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java index a6a6679..e897554 100644 --- a/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java +++ b/Fabric-1.20.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -27,6 +27,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; @@ -71,7 +72,18 @@ public static void registerAll() { Registries.ITEM, new ResourceLocation(MOD_ID, name)); if (!BuiltInRegistries.ITEM.containsKey(itemKey)) { - Item item = new BlockItem(block, new Item.Properties()); + final String itemDescriptionId = "item." + MOD_ID + "." + name; + Item item = new BlockItem(block, new Item.Properties()) { + @Override + public String getDescriptionId() { + return itemDescriptionId; + } + + @Override + public String getDescriptionId(ItemStack stack) { + return itemDescriptionId; + } + }; Registry.register(BuiltInRegistries.ITEM, itemKey, item); CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, item); } diff --git a/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java b/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java index 52db5d8..2aa561c 100644 --- a/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java +++ b/Fabric-1.21.1/src/main/java/com/box3lab/register/modelbe/PackModelBlockEntityRegistrar.java @@ -23,10 +23,12 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; @@ -71,7 +73,13 @@ public static void registerAll() { Registries.ITEM, ResourceLocation.fromNamespaceAndPath(MOD_ID, name)); if (!BuiltInRegistries.ITEM.containsKey(itemKey)) { - Item item = new BlockItem(block, new Item.Properties()); + final String itemTranslationKey = "item." + MOD_ID + "." + name; + Item item = new BlockItem(block, new Item.Properties()) { + @Override + public Component getName(ItemStack stack) { + return Component.translatable(itemTranslationKey); + } + }; Registry.register(BuiltInRegistries.ITEM, itemKey, item); CreativeTabExtras.add(CreativeTabRegistrar.DEFAULT_MODEL_TAB, item); }