From a85b94392ee9945c645bc10e2e466467865d26de Mon Sep 17 00:00:00 2001 From: XiaoMoMi <972454774@qq.com> Date: Tue, 30 Sep 2025 01:14:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=96=E8=A7=82=E6=96=B9=E5=9D=97=E5=88=86?= =?UTF-8?q?=E9=85=8D=20=E6=9C=AA=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/api/BukkitAdaptors.java | 48 ++- .../bukkit/api/CraftEngineImages.java | 40 +++ .../bukkit/block/BukkitBlockManager.java | 17 +- .../bukkit/font/BukkitFontManager.java | 6 + .../bukkit/plugin/BukkitCraftEngine.java | 5 + .../plugin/command/BukkitCommandManager.java | 3 +- .../feature/DebugCleanCacheCommand.java | 84 +++++ common-files/src/main/resources/commands.yml | 7 + .../configuration/blocks/chessboard_block.yml | 8 +- .../configuration/blocks/chinese_lantern.yml | 2 +- .../configuration/blocks/copper_coil.yml | 4 +- .../blocks/ender_pearl_flower.yml | 6 +- .../configuration/blocks/fairy_flower.yml | 2 +- .../configuration/blocks/flame_cane.yml | 2 +- .../configuration/blocks/gunpowder_block.yml | 4 +- .../configuration/blocks/palm_tree.yml | 8 +- .../default/configuration/blocks/pebble.yml | 6 +- .../default/configuration/blocks/reed.yml | 2 +- .../configuration/blocks/safe_block.yml | 16 +- .../configuration/blocks/topaz_ore.yml | 2 +- .../default/configuration/templates.yml | 246 +------------ .../src/main/resources/translations/en.yml | 2 + .../core/block/AbstractBlockManager.java | 332 +++++++++++------- .../core/block/AbstractBlockStateWrapper.java | 12 + .../core/block/AutoStateGroup.java | 65 +++- .../craftengine/core/block/BlockKeys.java | 10 + .../core/block/BlockStateWrapper.java | 8 +- .../core/font/AbstractFontManager.java | 10 + .../craftengine/core/font/FontManager.java | 4 + .../core/item/AbstractItemManager.java | 8 + .../core/pack/AbstractPackManager.java | 10 +- .../pack/allocator/BlockStateAllocator.java | 33 -- .../core/pack/allocator/IdAllocator.java | 6 +- .../allocator/VisualBlockStateAllocator.java | 152 ++++++++ .../allocator/cache/AllocationCacheFile.java | 62 ++++ .../allocator/cache/CacheFileStorage.java | 98 ++++++ .../pack/allocator/cache/CacheFileType.java | 39 ++ .../pack/allocator/cache/CacheSerializer.java | 51 +++ .../pack/allocator/cache/CacheStorage.java | 32 ++ 39 files changed, 975 insertions(+), 477 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java index 6dcd0167a..c667f832e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java @@ -3,37 +3,63 @@ package net.momirealms.craftengine.bukkit.api; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; -import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitExistingBlock; import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.world.WorldPosition; -import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; public final class BukkitAdaptors { private BukkitAdaptors() {} - public static BukkitServerPlayer adapt(final Player player) { + /** + * Adapts a Bukkit Player to a CraftEngine BukkitServerPlayer. + * This provides access to CraftEngine-specific player functionality and data. + * + * @param player the Bukkit Player to adapt, must not be null + * @return a non-null BukkitServerPlayer instance wrapping the provided player + */ + @NotNull + public static BukkitServerPlayer adapt(@NotNull final Player player) { return BukkitCraftEngine.instance().adapt(player); } - public static BukkitWorld adapt(final World world) { + /** + * Adapts a Bukkit World to a CraftEngine BukkitWorld. + * This enables CraftEngine world operations on Bukkit world instances. + * + * @param world the Bukkit World to adapt, must not be null + * @return a non-null BukkitWorld instance wrapping the provided world + */ + @NotNull + public static BukkitWorld adapt(@NotNull final World world) { return new BukkitWorld(world); } - public static BukkitEntity adapt(final Entity entity) { + /** + * Adapts a Bukkit Entity to a CraftEngine BukkitEntity. + * This provides CraftEngine entity functionality for Bukkit entities. + * + * @param entity the Bukkit Entity to adapt, must not be null + * @return a non-null BukkitEntity instance wrapping the provided entity + */ + @NotNull + public static BukkitEntity adapt(@NotNull final Entity entity) { return new BukkitEntity(entity); } - public static BukkitExistingBlock adapt(final Block block) { + /** + * Adapts a Bukkit Block to a CraftEngine BukkitExistingBlock. + * This enables CraftEngine block operations on Bukkit block instances. + * + * @param block the Bukkit Block to adapt, must not be null + * @return a non-null BukkitExistingBlock instance wrapping the provided block + */ + @NotNull + public static BukkitExistingBlock adapt(@NotNull final Block block) { return new BukkitExistingBlock(block); } - - public static Location toLocation(WorldPosition position) { - return LocationUtils.toLocation(position); - } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java new file mode 100644 index 000000000..3a420339f --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineImages.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.bukkit.api; + +import net.momirealms.craftengine.bukkit.font.BukkitFontManager; +import net.momirealms.craftengine.core.font.BitmapImage; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public final class CraftEngineImages { + + private CraftEngineImages() {} + + /** + * Returns an unmodifiable map of all currently loaded custom images. + * The map keys represent unique identifiers, and the values are the corresponding BitmapImage instances. + * + *

Important: Do not attempt to access this method during the onEnable phase + * as it will be empty. Instead, listen for the {@code CraftEngineReloadEvent} and use this method + * after the event is fired to obtain the complete image list. + * + * @return a non-null map containing all loaded custom images + */ + @NotNull + public static Map loadedImages() { + return BukkitFontManager.instance().loadedImages(); + } + + /** + * Gets a custom image by ID + * + * @param id id + * @return the custom image + */ + @Nullable + public static BitmapImage byId(@NotNull Key id) { + return BukkitFontManager.instance().loadedImages().get(id); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 36c421336..abcf5d08f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -44,7 +44,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { // 事件监听器 private final BlockEventListener blockEventListener; // 用于缓存string形式的方块状态到原版方块状态 - private final Map blockStateCache = new HashMap<>(1024); + private final Map blockStateCache = new HashMap<>(1024); // 用于临时存储可燃烧自定义方块的列表 private final List burnableBlocks = new ArrayList<>(); // 可燃烧的方块 @@ -169,25 +169,22 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public BlockStateWrapper createVanillaBlockState(String blockState) { - Object state = parseBlockState(blockState); - if (state == null) return null; - return BlockStateUtils.toBlockStateWrapper(state); + return this.blockStateCache.computeIfAbsent(blockState, k -> { + Object state = parseBlockState(k); + if (state == null) return null; + return BlockStateUtils.toBlockStateWrapper(state); + }); } @Nullable private Object parseBlockState(String state) { - if (this.blockStateCache.containsKey(state)) { - return this.blockStateCache.get(state); - } try { Object registryOrLookUp = MBuiltInRegistries.BLOCK; if (CoreReflections.method$Registry$asLookup != null) { registryOrLookUp = CoreReflections.method$Registry$asLookup.invoke(registryOrLookUp); } Object result = CoreReflections.method$BlockStateParser$parseForBlock.invoke(null, registryOrLookUp, state, false); - Object resultState = CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); - this.blockStateCache.put(state, resultState); - return resultState; + return CoreReflections.method$BlockStateParser$BlockResult$blockState.invoke(result); } catch (Exception e) { Debugger.BLOCK.warn(() -> "Failed to create block state: " + state, e); return null; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java index f6304a758..637fb20a5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java @@ -36,11 +36,17 @@ import java.lang.reflect.InvocationTargetException; import java.util.*; public class BukkitFontManager extends AbstractFontManager implements Listener { + private static BukkitFontManager instance; private final BukkitCraftEngine plugin; public BukkitFontManager(BukkitCraftEngine plugin) { super(plugin); this.plugin = plugin; + instance = this; + } + + public static BukkitFontManager instance() { + return instance; } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index e5a984612..fe991ca7f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -348,6 +348,11 @@ public class BukkitCraftEngine extends CraftEngine { return (BukkitPackManager) packManager; } + @Override + public BukkitFontManager fontManager() { + return (BukkitFontManager) fontManager; + } + @SuppressWarnings("ResultOfMethodCallIgnored") @Override public void saveResource(String resourcePath) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index 024b38db9..14c4d9378 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -56,7 +56,8 @@ public class BukkitCommandManager extends AbstractCommandManager new ListResourceCommand(this, plugin), new UploadPackCommand(this, plugin), new SendResourcePackCommand(this, plugin), - new DebugSaveDefaultResourcesCommand(this, plugin) + new DebugSaveDefaultResourcesCommand(this, plugin), + new DebugCleanCacheCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java new file mode 100644 index 000000000..8337140f6 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugCleanCacheCommand.java @@ -0,0 +1,84 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.momirealms.craftengine.bukkit.api.CraftEngineItems; +import net.momirealms.craftengine.bukkit.font.BukkitFontManager; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.util.Key; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class DebugCleanCacheCommand extends BukkitCommandFeature { + + public DebugCleanCacheCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .required("type", StringParser.stringComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(List.of(Suggestion.suggestion("custom-model-data"), Suggestion.suggestion("custom-block-states"), Suggestion.suggestion("visual-block-states"), Suggestion.suggestion("font"))); + } + })) + .handler(context -> { + if (this.plugin().isReloading()) { + context.sender().sendMessage("The plugin is reloading. Please wait until the process is complete."); + return; + } + String type = context.get("type"); + switch (type) { + case "custom-model-data" -> { + BukkitItemManager instance = BukkitItemManager.instance(); + Set ids = CraftEngineItems.loadedItems().keySet().stream().map(Key::toString).collect(Collectors.toSet()); + int total = 0; + for (Map.Entry entry : instance.itemParser().idAllocators().entrySet()) { + List removed = entry.getValue().cleanupUnusedIds(i -> !ids.contains(i)); + total += removed.size(); + try { + entry.getValue().saveToCache(); + } catch (IOException e) { + this.plugin().logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e); + return; + } + for (String id : removed) { + this.plugin().logger().info("Cleaned unused item: " + id); + } + } + context.sender().sendMessage("Cleaned " + total + " unused custom model data"); + } + case "custom-block-states" -> { + } + case "visual-block-states" -> { + } + case "font", "images" -> { + BukkitFontManager instance = this.plugin().fontManager(); + + } + } + }); + } + + @Override + public String getFeatureID() { + return "debug_clean_cache"; + } +} diff --git a/common-files/src/main/resources/commands.yml b/common-files/src/main/resources/commands.yml index 3a2b6c8de..9ba12ceeb 100644 --- a/common-files/src/main/resources/commands.yml +++ b/common-files/src/main/resources/commands.yml @@ -210,6 +210,13 @@ debug_save_default_resources: - /craftengine debug save-default-resources - /ce debug save-default-resources +debug_clean_cache: + enable: true + permission: ce.command.debug.clean_cache + usage: + - /craftengine debug clean-cache + - /ce debug clean-cache + debug_test: enable: true permission: ce.command.debug.test diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml index 07e6a3f1e..7e36d6d45 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chessboard_block.yml @@ -34,7 +34,7 @@ items: default: north appearances: east: - state: note_block:18 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 270 @@ -43,16 +43,16 @@ items: textures: pattern: minecraft:block/custom/chessboard_block north: - state: note_block:19 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 180 south: - state: note_block:20 + auto-state: solid model: path: minecraft:block/custom/chessboard_block west: - state: note_block:21 + auto-state: solid model: path: minecraft:block/custom/chessboard_block y: 90 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml index 4776b2d4d..420809bc8 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/chinese_lantern.yml @@ -25,7 +25,7 @@ items: luminance: 15 map-color: 36 state: - state: note_block:15 + auto-state: solid model: path: minecraft:block/custom/chinese_lantern generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml index 94185e5aa..9d9b8bf83 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/copper_coil.yml @@ -36,7 +36,7 @@ items: default: false appearances: off: - state: cactus:0 + auto-state: cactus model: path: minecraft:block/custom/copper_coil generation: @@ -47,7 +47,7 @@ items: top: minecraft:block/custom/copper_coil side: minecraft:block/custom/copper_coil_side on: - state: cactus:1 + auto-state: cactus model: path: minecraft:block/custom/copper_coil_on generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml index ccb1fad33..0b61f7161 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/ender_pearl_flower.yml @@ -90,7 +90,7 @@ blocks: range: 0~2 appearances: stage_0: - state: tripwire:1 + auto-state: lower_tripwire models: - path: minecraft:block/custom/ender_pearl_flower_stage_0 generation: @@ -98,7 +98,7 @@ blocks: textures: cross: minecraft:block/custom/ender_pearl_flower_stage_0 stage_1: - state: tripwire:0 + auto-state: higher_tripwire models: - path: minecraft:block/custom/ender_pearl_flower_stage_1 generation: @@ -106,7 +106,7 @@ blocks: textures: cross: minecraft:block/custom/ender_pearl_flower_stage_1 stage_2: - state: sugar_cane:3 + auto-state: sugar_cane models: - path: minecraft:block/custom/ender_pearl_flower_stage_2 generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml index 5e2b00ba4..52a2815fd 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/fairy_flower.yml @@ -26,7 +26,7 @@ items: loot: template: default:loot_table/self state: - state: sugar_cane:0 + auto-state: sugar_cane models: - path: minecraft:block/custom/fairy_flower_1 weight: 100 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml index f24859eef..4213b62bd 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/flame_cane.yml @@ -53,7 +53,7 @@ items: range: 0~5 appearances: default: - state: sugar_cane:2 + auto-state: sugar_cane models: - path: minecraft:block/custom/flame_cane_1 weight: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml index 17ccff09d..e114b183c 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/gunpowder_block.yml @@ -27,7 +27,7 @@ items: instrument: snare map-color: 45 state: - state: note_block:16 + auto-state: solid model: path: minecraft:block/custom/gunpowder_block generation: @@ -59,7 +59,7 @@ items: instrument: basedrum map-color: 45 state: - state: note_block:17 + auto-state: solid model: path: minecraft:block/custom/solid_gunpowder_block generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml index 61ccd87bc..6002d41ac 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/palm_tree.yml @@ -27,7 +27,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/palm_log_top texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_log @@ -57,7 +56,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/stripped_palm_log_top texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_log @@ -90,7 +88,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/palm_log texture_side_path: minecraft:block/custom/palm_log model_vertical_path: minecraft:block/custom/palm_wood @@ -120,7 +117,6 @@ items: states: template: default:block_state/pillar arguments: - base_block: note_block texture_top_path: minecraft:block/custom/stripped_palm_log texture_side_path: minecraft:block/custom/stripped_palm_log model_vertical_path: minecraft:block/custom/stripped_palm_wood @@ -151,7 +147,7 @@ items: template: default:model/simplified_cube_all arguments: path: minecraft:block/custom/palm_planks - state: note_block:12 + auto-state: solid default:palm_sapling: material: nether_brick settings: @@ -187,7 +183,7 @@ items: range: 0~1 appearances: default: - state: oak_sapling:0 + auto-state: sapling model: path: minecraft:block/custom/palm_sapling generation: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml index 5a164c44e..23fe24d71 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/pebble.yml @@ -58,7 +58,7 @@ items: default: 1 appearances: one: - state: tripwire:2 + auto-state: lower_tripwire models: - path: minecraft:block/custom/pebble_1 weight: 1 @@ -72,7 +72,7 @@ items: weight: 1 y: 270 two: - state: tripwire:3 + auto-state: lower_tripwire models: - path: minecraft:block/custom/pebble_2 weight: 1 @@ -86,7 +86,7 @@ items: weight: 1 y: 270 three: - state: tripwire:4 + auto-state: higher_tripwire models: - path: minecraft:block/custom/pebble_3 weight: 1 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml index c43425c81..de1e00be9 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/reed.yml @@ -25,7 +25,7 @@ items: loot: template: default:loot_table/self state: - state: sugar_cane:1 + auto-state: sugar_cane model: path: minecraft:block/custom/reed recipes: diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml index 710804ef9..63b13e353 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/safe_block.yml @@ -48,7 +48,7 @@ items: default: false appearances: east: - state: note_block:22 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 90 @@ -59,7 +59,7 @@ items: side: minecraft:block/custom/safe_block_side top: minecraft:block/custom/safe_block_top east_open: - state: note_block:23 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 90 @@ -70,30 +70,30 @@ items: side: minecraft:block/custom/safe_block_side top: minecraft:block/custom/safe_block_top north: - state: note_block:24 + auto-state: note_block model: path: minecraft:block/custom/safe_block north_open: - state: note_block:25 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open south: - state: note_block:26 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 180 south_open: - state: note_block:27 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 180 west: - state: note_block:28 + auto-state: note_block model: path: minecraft:block/custom/safe_block y: 270 west_open: - state: note_block:29 + auto-state: note_block model: path: minecraft:block/custom/safe_block_open y: 270 diff --git a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml index 0eee914e4..6721d3027 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks/topaz_ore.yml @@ -50,7 +50,7 @@ blocks: arguments: break_power: 2 state: - state: note_block:13 + auto-state: solid model: template: default:model/simplified_cube_all arguments: diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 5912b31f5..da609c8b0 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -862,7 +862,7 @@ templates#block_states: default: y appearances: axisY: - state: ${base_block} + auto-state: solid model: path: ${model_vertical_path} generation: @@ -871,7 +871,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisX: - state: ${base_block} + auto-state: solid model: x: 90 y: 90 @@ -882,7 +882,7 @@ templates#block_states: end: ${texture_top_path} side: ${texture_side_path} axisZ: - state: ${base_block} + auto-state: solid model: x: 90 path: ${model_horizontal_path} @@ -913,7 +913,7 @@ templates#block_states: range: 1~7 appearances: default: - state: ${default_state} + auto-state: leaves model: path: ${model_path} generation: @@ -921,7 +921,7 @@ templates#block_states: textures: all: ${texture_path} waterlogged: - state: ${waterlogged_state} + auto-state: waterlogged_leaves model: path: ${model_path} variants: @@ -2231,6 +2231,11 @@ templates#block_states: x: 180 y: 180 variants: + waterlogged=true: + settings: + resistance: 1200.0 + burnable: false + fluid-state: water facing=east,half=bottom,shape=inner_left,waterlogged=false: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=false facing=east,half=bottom,shape=inner_right,waterlogged=false: @@ -2313,244 +2318,85 @@ templates#block_states: appearance: facing=west,half=top,shape=straight,waterlogged=false facing=east,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=east,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=east,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=bottom,shape=straight,waterlogged=true: appearance: facing=east,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=inner_left,waterlogged=true: appearance: facing=east,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=inner_right,waterlogged=true: appearance: facing=east,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=outer_left,waterlogged=true: appearance: facing=east,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=outer_right,waterlogged=true: appearance: facing=east,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=east,half=top,shape=straight,waterlogged=true: appearance: facing=east,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=north,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=north,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=bottom,shape=straight,waterlogged=true: appearance: facing=north,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=inner_left,waterlogged=true: appearance: facing=north,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=inner_right,waterlogged=true: appearance: facing=north,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=outer_left,waterlogged=true: appearance: facing=north,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=outer_right,waterlogged=true: appearance: facing=north,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=north,half=top,shape=straight,waterlogged=true: appearance: facing=north,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=south,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=south,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=bottom,shape=straight,waterlogged=true: appearance: facing=south,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=inner_left,waterlogged=true: appearance: facing=south,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=inner_right,waterlogged=true: appearance: facing=south,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=outer_left,waterlogged=true: appearance: facing=south,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=outer_right,waterlogged=true: appearance: facing=south,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=south,half=top,shape=straight,waterlogged=true: appearance: facing=south,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=inner_left,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=inner_right,waterlogged=true: appearance: facing=west,half=bottom,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=outer_left,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=outer_right,waterlogged=true: appearance: facing=west,half=bottom,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=bottom,shape=straight,waterlogged=true: appearance: facing=west,half=bottom,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=inner_left,waterlogged=true: appearance: facing=west,half=top,shape=inner_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=inner_right,waterlogged=true: appearance: facing=west,half=top,shape=inner_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=outer_left,waterlogged=true: appearance: facing=west,half=top,shape=outer_left,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=outer_right,waterlogged=true: appearance: facing=west,half=top,shape=outer_right,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water facing=west,half=top,shape=straight,waterlogged=true: appearance: facing=west,half=top,shape=straight,waterlogged=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water + # pressure plate block default:block_state/pressure_plate: properties: @@ -3171,6 +3017,11 @@ templates#block_states: - item: ${fence_side_item} rotation: 270 variants: + waterlogged=true: + settings: + resistance: 1200.0 + burnable: false + fluid-state: water east=false,north=false,south=false,waterlogged=false,west=false: appearance: east=false,north=false,south=false,waterlogged=false,west=false east=true,north=false,south=false,waterlogged=false,west=false: @@ -3205,100 +3056,37 @@ templates#block_states: appearance: east=true,north=true,south=true,waterlogged=false,west=true east=false,north=false,south=false,waterlogged=true,west=false: appearance: east=false,north=false,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=false,waterlogged=true,west=false: appearance: east=true,north=false,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=false,waterlogged=true,west=false: appearance: east=false,north=true,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=true,waterlogged=true,west=false: appearance: east=false,north=false,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=false,waterlogged=true,west=true: appearance: east=false,north=false,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=false,waterlogged=true,west=false: appearance: east=true,north=true,south=false,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=true,waterlogged=true,west=false: appearance: east=true,north=false,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=false,waterlogged=true,west=true: appearance: east=true,north=false,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=true,waterlogged=true,west=false: appearance: east=false,north=true,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=false,waterlogged=true,west=true: appearance: east=false,north=true,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=false,south=true,waterlogged=true,west=true: appearance: east=false,north=false,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=true,waterlogged=true,west=false: appearance: east=true,north=true,south=true,waterlogged=true,west=false - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=false,waterlogged=true,west=true: appearance: east=true,north=true,south=false,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=false,south=true,waterlogged=true,west=true: appearance: east=true,north=false,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=false,north=true,south=true,waterlogged=true,west=true: appearance: east=false,north=true,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water east=true,north=true,south=true,waterlogged=true,west=true: appearance: east=true,north=true,south=true,waterlogged=true,west=true - settings: - resistance: 1200.0 - burnable: false - fluid-state: water + # recipes templates#recipes: default:recipe/planks: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 5679ea63c..acd02e1a4 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -277,6 +277,8 @@ warning.config.block.state.entity_renderer.better_model.missing_model: " warning.config.block.state.entity_renderer.model_engine.missing_model: "Issue found in file - The block '' is missing the required 'model' argument for 'model_engine' entity renderer." warning.config.block.state.variant.invalid_appearance: "Issue found in file - The block '' has an error that the variant '' is using a non-existing appearance ''." warning.config.block.state.invalid_vanilla: "Issue found in file - The block '' is using an invalid vanilla block state ''." +warning.config.block.state.invalid_auto_state: "Issue found in file - The block '' is using an invalid auto-state ''. Allowed values: []." +warning.config.block.state.auto_state.exhausted: "Issue found in file - Cannot allocate visual block state for block '' as the slots('') in group '' have been exhausted." warning.config.block.state.unavailable_vanilla: "Issue found in file - The block '' is using an unavailable vanilla block state ''. Please free that state in block-state-mappings." warning.config.block.state.invalid_vanilla_id: "Issue found in file - The block '' is using a vanilla block state '' that exceeds the available slot range '0~'." warning.config.block.state.invalid_id: "Issue found in file - The block state ID range () used by block '' is outside the valid range of 0 to . Please add more server-side blocks in 'config.yml' if the current slots are exhausted." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index 5d5b66231..8f0af28ff 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -18,9 +18,9 @@ import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.PendingConfigSection; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.allocator.BlockStateAllocator; import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; import net.momirealms.craftengine.core.pack.allocator.IdAllocator; +import net.momirealms.craftengine.core.pack.allocator.VisualBlockStateAllocator; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -60,8 +60,6 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final List cachedSuggestions = new ArrayList<>(); // 缓存的使用中的命名空间 protected final Set namespacesInUse = new HashSet<>(); - // 用于检测单个外观方块状态是否被绑定了不同模型 - protected final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); // Map<方块类型, Map<方块状态NBT,模型>>,用于生成block state json protected final Map> blockStateOverrides = new HashMap<>(); // 用于生成mod使用的block state json @@ -81,7 +79,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 自定义状态列表,会随着重载变化 protected final ImmutableBlockState[] immutableBlockStates; // 倒推缓存 - protected final BlockStateCandidate[] reversedBlockStateArranger; + protected final BlockStateCandidate[] autoVisualBlockStateCandidates; + // 用于检测单个外观方块状态是否被绑定了不同模型 + protected final JsonElement[] tempVanillaBlockStateModels; // 临时存储哪些视觉方块被使用了 protected final Set tempVisualBlockStatesInUse = new HashSet<>(); protected final Set tempVisualBlocksInUse = new HashSet<>(); @@ -91,14 +91,15 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected AbstractBlockManager(CraftEngine plugin, int vanillaBlockStateCount, int customBlockCount) { super(plugin); this.vanillaBlockStateCount = vanillaBlockStateCount; - this.blockParser = new BlockParser(); - this.blockStateMappingParser = new BlockStateMappingParser(); this.customBlocks = new DelegatingBlock[customBlockCount]; this.customBlockHolders = new Object[customBlockCount]; this.customBlockStates = new DelegatingBlockState[customBlockCount]; this.immutableBlockStates = new ImmutableBlockState[customBlockCount]; this.blockStateMappings = new int[customBlockCount + vanillaBlockStateCount]; - this.reversedBlockStateArranger = new BlockStateCandidate[vanillaBlockStateCount]; + this.autoVisualBlockStateCandidates = new BlockStateCandidate[vanillaBlockStateCount]; + this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount]; + this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates); + this.blockStateMappingParser = new BlockStateMappingParser(); Arrays.fill(this.blockStateMappings, -1); } @@ -130,7 +131,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.appearanceToRealState.clear(); Arrays.fill(this.blockStateMappings, -1); Arrays.fill(this.immutableBlockStates, EmptyBlock.STATE); - Arrays.fill(this.reversedBlockStateArranger, null); + Arrays.fill(this.autoVisualBlockStateCandidates, null); + for (AutoStateGroup autoStateGroup : AutoStateGroup.values()) { + autoStateGroup.reset(); + } } @Override @@ -188,7 +192,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } protected void clearCache() { - this.tempVanillaBlockStateModels.clear(); + Arrays.fill(this.tempVanillaBlockStateModels, null); this.tempVisualBlockStatesInUse.clear(); this.tempVisualBlocksInUse.clear(); } @@ -275,26 +279,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem Key blockOwnerId = getBlockOwnerId(beforeState); List blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>()); blockStateWrappers.add(beforeState); - AbstractBlockManager.this.reversedBlockStateArranger[beforeState.registryId()] = blockParser.createVisualBlockCandidate(beforeState); - + AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState); } exceptionCollector.throwIfPresent(); } - } - - public class BlockParser extends IdSectionConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; - private final IdAllocator internalIdAllocator; - private final List pendingConfigSections = new ArrayList<>(); - private final BlockStateAllocator[] visualBlockStateAllocators = new BlockStateAllocator[AutoStateGroup.values().length]; - - public BlockParser() { - this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); - } - - public void addPendingConfigSection(PendingConfigSection section) { - this.pendingConfigSections.add(section); - } @Nullable public BlockStateCandidate createVisualBlockCandidate(BlockStateWrapper blockState) { @@ -302,40 +290,58 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (!groups.isEmpty()) { BlockStateCandidate candidate = new BlockStateCandidate(blockState); for (AutoStateGroup group : groups) { - getOrCreateBlockStateAllocator(group).addCandidate(candidate); + group.addCandidate(candidate); } return candidate; } return null; } + } - private BlockStateAllocator getOrCreateBlockStateAllocator(AutoStateGroup group) { - int index = group.ordinal(); - BlockStateAllocator visualBlockStateAllocator = this.visualBlockStateAllocators[index]; - if (visualBlockStateAllocator == null) { - visualBlockStateAllocator = new BlockStateAllocator(); - this.visualBlockStateAllocators[index] = visualBlockStateAllocator; - } - return visualBlockStateAllocator; + public class BlockParser extends IdSectionConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"}; + private final IdAllocator internalIdAllocator; + private final VisualBlockStateAllocator visualBlockStateAllocator; + private final List pendingConfigSections = new ArrayList<>(); + + public BlockParser(BlockStateCandidate[] candidates) { + this.internalIdAllocator = new IdAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("custom-block-states.json")); + this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState); + } + + public void addPendingConfigSection(PendingConfigSection section) { + this.pendingConfigSections.add(section); } @Override public void postProcess() { + this.visualBlockStateAllocator.processPendingAllocations(); + try { + this.visualBlockStateAllocator.saveToCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while saving visual block states allocation", e); + } this.internalIdAllocator.processPendingAllocations(); try { this.internalIdAllocator.saveToCache(); } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block state allocation", e); + AbstractBlockManager.this.plugin.logger().warn("Error while saving custom block states allocation", e); } } @Override public void preProcess() { + this.visualBlockStateAllocator.reset(); + try { + this.visualBlockStateAllocator.loadFromCache(); + } catch (IOException e) { + AbstractBlockManager.this.plugin.logger().warn("Error while loading visual block states allocation cache", e); + } this.internalIdAllocator.reset(0, Config.serverSideBlocks() - 1); try { this.internalIdAllocator.loadFromCache(); } catch (IOException e) { - AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block state allocation cache", e); + AbstractBlockManager.this.plugin.logger().warn("Error while loading custom block states allocation cache", e); } for (PendingConfigSection section : this.pendingConfigSections) { ResourceConfigUtils.runCatching( @@ -437,9 +443,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } } - CompletableFutures.allOf(internalIdAllocators).whenComplete((v, t) -> ResourceConfigUtils.runCatching(path, node, () -> { - if (t != null) { - if (t instanceof CompletionException e) { + CompletableFutures.allOf(internalIdAllocators).whenComplete((v1, t1) -> ResourceConfigUtils.runCatching(path, node, () -> { + if (t1 != null) { + if (t1 instanceof CompletionException e) { Throwable cause = e.getCause(); // 这里不会有conflict了,因为之前已经判断过了 if (cause instanceof IdAllocator.IdExhaustedException) { @@ -449,7 +455,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return; } } - throw new RuntimeException("Unknown error occurred", t); + throw new RuntimeException("Unknown error occurred", t1); } for (int i = 0; i < internalIdAllocators.size(); i++) { @@ -472,110 +478,164 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem ); BlockBehavior blockBehavior = createBlockBehavior(customBlock, MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))); - // 单状态 + Map> appearanceConfigs; + Map> futureVisualStates = new HashMap<>(); if (singleState) { - BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(stateSection.get("state"), "warning.config.block.state.missing_state")); - this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(stateSection, "model", "models")); - ImmutableBlockState onlyState = states.getFirst(); - // 为唯一的状态绑定外观 - onlyState.setVanillaBlockState(appearanceState); - parseBlockEntityRender(stateSection.get("entity-renderer")).ifPresent(onlyState::setConstantRenderers); + appearanceConfigs = Map.of("", stateSection); } else { - BlockStateWrapper anyAppearanceState = null; - Map appearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"); - // 也不能为空 - if (appearancesSection.isEmpty()) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances"); - } - Map appearances = Maps.newHashMap(); - // 先解析所有的外观 - for (Map.Entry entry : appearancesSection.entrySet()) { - Map appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - // 解析对应的视觉方块 - BlockStateWrapper appearanceState = parsePluginFormattedBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(appearanceSection.get("state"), "warning.config.block.state.missing_state")); - this.arrangeModelForStateAndVerify(appearanceState, ResourceConfigUtils.get(appearanceSection, "model", "models")); - appearances.put(entry.getKey(), new BlockStateAppearance(appearanceState, parseBlockEntityRender(appearanceSection.get("entity-renderer")))); - if (anyAppearanceState == null) { - anyAppearanceState = appearanceState; - } - } - // 解析变体 - Map variantsSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"); - for (Map.Entry entry : variantsSection.entrySet()) { - Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); - String variantNBT = entry.getKey(); - // 先解析nbt,找到需要修改的方块状态 - CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT); - if (tag == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT); - } - List possibleStates = variantProvider.getPossibleStates(tag); - Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); - if (anotherSetting != null) { - for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting)); - } - } - String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance")); - if (appearanceName != null) { - BlockStateAppearance appearance = appearances.get(appearanceName); - if (appearance == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName); - } - for (ImmutableBlockState possibleState : possibleStates) { - possibleState.setVanillaBlockState(appearance.blockState()); - appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); - } - } - } - // 为没有外观的方块状态填充 - for (ImmutableBlockState blockState : states) { - if (blockState.vanillaBlockState() == null) { - blockState.setVanillaBlockState(anyAppearanceState); - } + Map rawAppearancesSection = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"); + appearanceConfigs = new LinkedHashMap<>(4); + for (Map.Entry entry : rawAppearancesSection.entrySet()) { + appearanceConfigs.put(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey())); } } - // 获取方块实体行为 - EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior(); - boolean isEntityBlock = entityBlockBehavior != null; - - // 绑定行为 - for (ImmutableBlockState state : states) { - if (isEntityBlock) { - state.setBlockEntityType(entityBlockBehavior.blockEntityType()); - } - state.setBehavior(blockBehavior); - int internalId = state.customBlockState().registryId(); - BlockStateWrapper visualState = state.vanillaBlockState(); - int appearanceId = visualState.registryId(); - int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; - AbstractBlockManager.this.immutableBlockStates[index] = state; - AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; - AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); - AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState); - AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState)); - AbstractBlockManager.this.applyPlatformSettings(state); - // generate mod assets - if (Config.generateModAssets()) { - AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels.get(appearanceId)) - .orElseGet(() -> { - // 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题 - // 未来需要靠mod重构彻底解决问题 - JsonObject json = new JsonObject(); - json.addProperty("model", "minecraft:block/air"); - return json; - })); + for (Map.Entry> entry : appearanceConfigs.entrySet()) { + Map appearanceSection = entry.getValue(); + if (appearanceSection.containsKey("state")) { + String appearanceName = entry.getKey(); + futureVisualStates.put( + appearanceName, + this.visualBlockStateAllocator.assignFixedBlockState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, parsePluginFormattedBlockState(appearanceSection.get("state").toString())) + ); + } else if (stateSection.containsKey("auto-state")) { + String autoStateId = stateSection.get("auto-state").toString(); + AutoStateGroup group = AutoStateGroup.byId(autoStateId); + if (group == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_auto_state", autoStateId, EnumUtils.toString(AutoStateGroup.values())); + } + String appearanceName = entry.getKey(); + futureVisualStates.put( + appearanceName, + this.visualBlockStateAllocator.requestAutoState(appearanceName.isEmpty() ? id.asString() : id.asString() + ":" + appearanceName, group) + ); + } else { + throw new LocalizedResourceConfigException("warning.config.block.state.missing_state"); } } - // 一定要到最后再绑定 - customBlock.setBehavior(blockBehavior); - holder.bindValue(customBlock); + CompletableFutures.allOf(futureVisualStates.values()).whenComplete((v2, t2) -> { + if (t2 != null) { + if (t2 instanceof CompletionException e) { + Throwable cause = e.getCause(); + if (cause instanceof VisualBlockStateAllocator.StateExhaustedException exhausted) { + throw new LocalizedResourceConfigException("warning.config.block.state.auto_state.exhausted", exhausted.group().id(), String.valueOf(exhausted.group().candidateCount())); + } else { + Debugger.BLOCK.warn(() -> "Unknown error while allocating visual block state.", cause); + return; + } + } + throw new RuntimeException("Unknown error occurred", t2); + } - // 添加方块 - AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); + BlockStateAppearance anyAppearance = null; + Map appearances = new HashMap<>(); + for (Map.Entry> entry : appearanceConfigs.entrySet()) { + String appearanceName = entry.getKey(); + Map appearanceSection = entry.getValue(); + BlockStateWrapper visualBlockState; + try { + visualBlockState = futureVisualStates.get(appearanceName).get(); + } catch (InterruptedException | ExecutionException e) { + AbstractBlockManager.this.plugin.logger().warn("Interrupted while allocating visual block state for block " + id.asString(), e); + return; + } + this.arrangeModelForStateAndVerify(visualBlockState, ResourceConfigUtils.get(appearanceSection, "model", "models")); + BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))); + appearances.put(appearanceName, blockStateAppearance); + if (anyAppearance == null) { + anyAppearance = blockStateAppearance; + } + } + // 至少有一个外观吧 + Objects.requireNonNull(anyAppearance, "any appearance should not be null"); + + ExceptionCollector exceptionCollector = new ExceptionCollector<>(); + if (!singleState) { + Map variantsSection = ResourceConfigUtils.getAsMapOrNull(stateSection.get("variants"), "variants"); + if (variantsSection != null) { + for (Map.Entry entry : variantsSection.entrySet()) { + Map variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()); + String variantNBT = entry.getKey(); + // 先解析nbt,找到需要修改的方块状态 + CompoundTag tag = BlockNbtParser.deserialize(variantProvider, variantNBT); + if (tag == null) { + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", variantNBT)); + continue; + } + List possibleStates = variantProvider.getPossibleStates(tag); + Map anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings"); + if (anotherSetting != null) { + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setSettings(BlockSettings.ofFullCopy(possibleState.settings(), anotherSetting)); + } + } + String appearanceName = ResourceConfigUtils.getAsString(variantSection.get("appearance")); + if (appearanceName != null) { + BlockStateAppearance appearance = appearances.get(appearanceName); + if (appearance == null) { + exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearanceName)); + continue; + } + for (ImmutableBlockState possibleState : possibleStates) { + possibleState.setVanillaBlockState(appearance.blockState()); + appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); + } + } + } + } + } + + // 获取方块实体行为 + EntityBlockBehavior entityBlockBehavior = blockBehavior.getEntityBehavior(); + boolean isEntityBlock = entityBlockBehavior != null; + + // 绑定行为 + for (ImmutableBlockState state : states) { + if (isEntityBlock) { + state.setBlockEntityType(entityBlockBehavior.blockEntityType()); + } + state.setBehavior(blockBehavior); + int internalId = state.customBlockState().registryId(); + BlockStateWrapper visualState = state.vanillaBlockState(); + // 校验,为未绑定外观的强行添加外观 + if (visualState == null) { + visualState = anyAppearance.blockState(); + state.setVanillaBlockState(visualState); + anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); + } + int appearanceId = visualState.registryId(); + int index = internalId - AbstractBlockManager.this.vanillaBlockStateCount; + AbstractBlockManager.this.immutableBlockStates[index] = state; + AbstractBlockManager.this.blockStateMappings[internalId] = appearanceId; + AbstractBlockManager.this.appearanceToRealState.computeIfAbsent(appearanceId, k -> new IntArrayList()).add(internalId); + AbstractBlockManager.this.tempVisualBlockStatesInUse.add(visualState); + AbstractBlockManager.this.tempVisualBlocksInUse.add(getBlockOwnerId(visualState)); + AbstractBlockManager.this.applyPlatformSettings(state); + // generate mod assets + if (Config.generateModAssets()) { + AbstractBlockManager.this.modBlockStateOverrides.put(BlockManager.createCustomBlockKey(index), Optional.ofNullable(AbstractBlockManager.this.tempVanillaBlockStateModels[appearanceId]) + .orElseGet(() -> { + // 如果未指定模型,说明复用原版模型?但是部分模型是多部位模型,无法使用变体解决问题 + // 未来需要靠mod重构彻底解决问题 + JsonObject json = new JsonObject(); + json.addProperty("model", "minecraft:block/air"); + return json; + })); + } + } + + // 一定要到最后再绑定 + customBlock.setBehavior(blockBehavior); + holder.bindValue(customBlock); + + // 添加方块 + AbstractBlockManager.this.byId.put(customBlock.id(), customBlock); + + // 抛出次要警告 + exceptionCollector.throwIfPresent(); + }); }, () -> GsonHelper.get().toJson(section))); } @@ -622,12 +682,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem // 结合variants JsonElement combinedVariant = GsonHelper.combine(variants); Map overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()); - AbstractBlockManager.this.tempVanillaBlockStateModels.put(blockStateWrapper.registryId(), combinedVariant); JsonElement previous = overrideMap.get(propertyNBT); if (previous != null && !previous.equals(combinedVariant)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous)); } overrideMap.put(propertyNBT, combinedVariant); + AbstractBlockManager.this.tempVanillaBlockStateModels[blockStateWrapper.registryId()] = combinedVariant; } private JsonObject parseAppearanceModelSectionAsJson(Map section) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java index 7ce0ad545..4589397a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockStateWrapper.java @@ -23,4 +23,16 @@ public abstract class AbstractBlockStateWrapper implements BlockStateWrapper { public String toString() { return this.blockState.toString(); } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BlockStateWrapper that)) return false; + return this.registryId == that.registryId(); + } + + @Override + public int hashCode() { + return this.registryId; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java index b79a46f55..f917f72b4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AutoStateGroup.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.block; +import net.momirealms.craftengine.core.pack.allocator.BlockStateCandidate; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -9,37 +11,68 @@ import java.util.function.Predicate; public enum AutoStateGroup { LEAVES("leaves", Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), - (w) -> !(boolean) w.getProperty("waterlogged"), 0 + (w) -> !(boolean) w.getProperty("waterlogged") ), WATERLOGGED_LEAVES( "waterlogged_leaves", Set.of(BlockKeys.OAK_LEAVES, BlockKeys.SPRUCE_LEAVES, BlockKeys.BIRCH_LEAVES, BlockKeys.JUNGLE_LEAVES, BlockKeys.ACACIA_LEAVES, BlockKeys.DARK_OAK_LEAVES, BlockKeys.MANGROVE_LEAVES, BlockKeys.CHERRY_LEAVES, BlockKeys.PALE_OAK_LEAVES, BlockKeys.AZALEA_LEAVES, BlockKeys.FLOWERING_AZALEA_LEAVES), - (w) -> w.getProperty("waterlogged"), 0 + (w) -> w.getProperty("waterlogged") ), - TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true, 1), - LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached"), 0), - HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached"), 0), - NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true, 0), - BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true, 0), - RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true, 0), - MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true, 0), - MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true, 1), - SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true, 2); + LOWER_TRIPWIRE("lower_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> w.getProperty("attached")), + HIGHER_TRIPWIRE("higher_tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> !(boolean) w.getProperty("attached")), + NOTE_BLOCK("note_block", Set.of(BlockKeys.NOTE_BLOCK), (w) -> true), + BROWN_MUSHROOM("brown_mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK), (w) -> true), + RED_MUSHROOM("red_mushroom", Set.of(BlockKeys.RED_MUSHROOM_BLOCK), (w) -> true), + MUSHROOM_STEM("mushroom_stem", Set.of(BlockKeys.MUSHROOM_STEM), (w) -> true), + TRIPWIRE("tripwire", Set.of(BlockKeys.TRIPWIRE), (w) -> true), + SUGAR_CANE("sugar_cane", Set.of(BlockKeys.SUGAR_CANE), (w) -> true), + CACTUS("cactus", Set.of(BlockKeys.CACTUS), (w) -> true), + SAPLING("sapling", Set.of(BlockKeys.OAK_SAPLING, BlockKeys.SPRUCE_SAPLING, BlockKeys.BIRCH_SAPLING, BlockKeys.JUNGLE_SAPLING, BlockKeys.ACACIA_SAPLING, BlockKeys.DARK_OAK_SAPLING, BlockKeys.CHERRY_SAPLING, BlockKeys.PALE_OAK_SAPLING), (w) -> true), + MUSHROOM("mushroom", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM), (w) -> true), + SOLID("solid", Set.of(BlockKeys.BROWN_MUSHROOM_BLOCK, BlockKeys.RED_MUSHROOM_BLOCK, BlockKeys.MUSHROOM_STEM, BlockKeys.NOTE_BLOCK), (w) -> true); private final Set blocks; private final String id; private final Predicate predicate; - private final int priority; + private final List candidates = new ArrayList<>(); + private int pointer; - AutoStateGroup(String id, Set blocks, Predicate predicate, int priority) { + AutoStateGroup(String id, Set blocks, Predicate predicate) { this.id = id; this.blocks = blocks; this.predicate = predicate; - this.priority = priority; } - public int priority() { - return priority; + public void reset() { + this.pointer = 0; + this.candidates.clear(); + } + + public void addCandidate(@NotNull BlockStateCandidate candidate) { + this.candidates.add(candidate); + } + + public int candidateCount() { + return candidates.size(); + } + + @Nullable + public BlockStateCandidate findNextCandidate() { + while (this.pointer < this.candidates.size()) { + final BlockStateCandidate state = this.candidates.get(this.pointer); + if (!state.isUsed()) { + return state; + } + this.pointer++; + } + return null; + } + + public boolean test(BlockStateWrapper state) { + if (!this.blocks.contains(state.ownerId())) { + return false; + } + return this.predicate.test(state); } public Set blocks() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java index 30490bbad..94bbf2342 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockKeys.java @@ -7,6 +7,7 @@ import java.util.List; public final class BlockKeys { private BlockKeys() {} + public static final Key SUGAR_CANE = Key.of("minecraft:sugar_cane"); public static final Key NOTE_BLOCK = Key.of("minecraft:note_block"); public static final Key TRIPWIRE = Key.of("minecraft:tripwire"); public static final Key CRAFTING_TABLE = Key.of("minecraft:crafting_table"); @@ -262,6 +263,15 @@ public final class BlockKeys { public static final Key AZALEA_LEAVES = Key.of("minecraft:azalea_leaves"); public static final Key FLOWERING_AZALEA_LEAVES = Key.of("minecraft:flowering_azalea_leaves"); + public static final Key OAK_SAPLING = Key.of("minecraft:oak_sapling"); + public static final Key SPRUCE_SAPLING = Key.of("minecraft:spruce_sapling"); + public static final Key BIRCH_SAPLING = Key.of("minecraft:birch_sapling"); + public static final Key JUNGLE_SAPLING = Key.of("minecraft:jungle_sapling"); + public static final Key DARK_OAK_SAPLING = Key.of("minecraft:dark_oak_sapling"); + public static final Key ACACIA_SAPLING = Key.of("minecraft:acacia_sapling"); + public static final Key CHERRY_SAPLING = Key.of("minecraft:cherry_sapling"); + public static final Key PALE_OAK_SAPLING = Key.of("minecraft:pale_oak_sapling"); + public static final List WOODEN_TRAPDOORS = List.of(OAK_TRAPDOOR, SPRUCE_TRAPDOOR, BIRCH_TRAPDOOR, ACACIA_TRAPDOOR, PALE_OAK_TRAPDOOR, DARK_OAK_TRAPDOOR, MANGROVE_TRAPDOOR, JUNGLE_TRAPDOOR); public static final List CHERRY_TRAPDOORS = List.of(CHERRY_TRAPDOOR); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java index 0b19c2ee3..5ef5223b1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java @@ -1,8 +1,9 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; -public interface BlockStateWrapper { +public interface BlockStateWrapper extends Comparable { Object literalObject(); @@ -15,4 +16,9 @@ public interface BlockStateWrapper { boolean hasProperty(String propertyName); String getAsString(); + + @Override + default int compareTo(@NotNull BlockStateWrapper o) { + return Integer.compare(registryId(), o.registryId()); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index f8ed7e7c3..5c44b4428 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -80,6 +80,16 @@ public abstract class AbstractFontManager implements FontManager { return offsetFont; } + @Override + public Map loadedImages() { + return Collections.unmodifiableMap(this.images); + } + + @Override + public Map emojis() { + return Collections.unmodifiableMap(this.emojis); + } + @Override public void unload() { this.fonts.clear(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java index e56e39c3b..7cf65807b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/FontManager.java @@ -48,6 +48,10 @@ public interface FontManager extends Manageable { OffsetFont offsetFont(); + Map loadedImages(); + + Map emojis(); + ConfigParser[] parsers(); default EmojiTextProcessResult replaceMiniMessageEmoji(@NotNull String miniMessage, @Nullable Player player) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index b1d0539ed..9d3c7b909 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -69,6 +69,14 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl ItemDataModifiers.init(); } + public ItemParser itemParser() { + return itemParser; + } + + public EquipmentParser equipmentParser() { + return equipmentParser; + } + protected static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) { for (Key key : items) { VANILLA_ITEM_EXTRA_BEHAVIORS.computeIfAbsent(key, k -> new ArrayList<>()).add(behavior); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index c4ed3e352..6ee669d9a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -5,7 +5,6 @@ import com.google.common.collect.Multimap; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.google.gson.*; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.font.BitmapImage; import net.momirealms.craftengine.core.font.Font; import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; @@ -27,7 +26,9 @@ import net.momirealms.craftengine.core.pack.obfuscation.ObfA; import net.momirealms.craftengine.core.pack.revision.Revision; import net.momirealms.craftengine.core.pack.revision.Revisions; import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.config.*; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor; import net.momirealms.craftengine.core.plugin.locale.I18NData; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -615,7 +616,10 @@ public abstract class AbstractPackManager implements PackManager { long o2 = System.nanoTime(); this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms"); for (ConfigParser parser : this.sortedParsers) { - if (!predicate.test(parser)) continue; + if (!predicate.test(parser)) { + parser.clear(); + continue; + } long t1 = System.nanoTime(); parser.preProcess(); parser.loadAll(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java deleted file mode 100644 index 5669d0b27..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/BlockStateAllocator.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.momirealms.craftengine.core.pack.allocator; - -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public class BlockStateAllocator { - private final List blockStates = new ArrayList<>(); - private int pointer = 0; - private int max = -1; - - public void addCandidate(BlockStateCandidate state) { - this.blockStates.add(state); - this.max = this.blockStates.size() - 1; - } - - @Nullable - public BlockStateCandidate findNext() { - while (this.pointer < this.max) { - final BlockStateCandidate state = this.blockStates.get(this.pointer); - if (!state.isUsed()) { - return state; - } - this.pointer++; - } - return null; - } - - public void processPendingAllocations() { - - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java index 47c5e6bd3..9a84c875a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/IdAllocator.java @@ -152,7 +152,7 @@ public class IdAllocator { * @param shouldRemove 判断是否应该移除的谓词 * @return 被移除的ID数量 */ - public int cleanupUnusedIds(Predicate shouldRemove) { + public List cleanupUnusedIds(Predicate shouldRemove) { List idsToRemove = new ArrayList<>(); for (String id : this.cachedIdMap.keySet()) { if (shouldRemove.test(id)) { @@ -160,15 +160,13 @@ public class IdAllocator { } } - int removedCount = 0; for (String id : idsToRemove) { Integer removedId = this.cachedIdMap.remove(id); if (removedId != null && !this.forcedIdMap.containsValue(removedId)) { this.occupiedIdSet.clear(removedId); - removedCount++; } } - return removedCount; + return idsToRemove; } /** diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java new file mode 100644 index 000000000..ab7d7cd86 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/VisualBlockStateAllocator.java @@ -0,0 +1,152 @@ +package net.momirealms.craftengine.core.pack.allocator; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.momirealms.craftengine.core.block.AutoStateGroup; +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.util.FileUtils; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.Pair; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class VisualBlockStateAllocator { + private final Path cacheFilePath; + private final Map cachedBlockStates = new HashMap<>(); + private final Map>> pendingAllocations = new LinkedHashMap<>(); + @SuppressWarnings("unchecked") + private final List>>[] pendingAllocationFutures = new List[AutoStateGroup.values().length]; + private final BlockStateCandidate[] candidates; + private final Function factory; + + public VisualBlockStateAllocator(Path cacheFilePath, BlockStateCandidate[] candidates, Function factory) { + this.cacheFilePath = cacheFilePath; + this.candidates = candidates; + this.factory = factory; + } + + public void reset() { + Arrays.fill(this.pendingAllocationFutures, new ArrayList<>()); + this.cachedBlockStates.clear(); + this.pendingAllocations.clear(); + } + + public CompletableFuture assignFixedBlockState(String name, BlockStateWrapper state) { + this.cachedBlockStates.remove(name); + BlockStateCandidate candidate = this.candidates[state.registryId()]; + if (candidate != null) { + candidate.setUsed(); + } + return CompletableFuture.completedFuture(state); + } + + public CompletableFuture requestAutoState(String name, AutoStateGroup group) { + CompletableFuture future = new CompletableFuture<>(); + this.pendingAllocations.put(name, new Pair<>(group, future)); + this.pendingAllocationFutures[group.ordinal()].add(Pair.of(name, future)); + return future; + } + + public void processPendingAllocations() { + // 先处理缓存的 + for (Map.Entry entry : this.cachedBlockStates.entrySet()) { + int registryId = entry.getValue().registryId(); + // 检查候选方块是否可用 + BlockStateCandidate candidate = this.candidates[registryId]; + if (candidate != null) { + // 未被使用 + if (!candidate.isUsed()) { + // 获取当前的安排任务 + Pair> pair = this.pendingAllocations.get(entry.getKey()); + // 如果候选满足组,那么直接允许起飞 + if (pair != null && pair.left().test(candidate.blockState())) { + pair.right().complete(candidate.blockState()); + } + // 尽管未被使用,该槽位也应该被占用,以避免被自动分配到 + candidate.setUsed(); + } + // 被使用了就随他去 + } + // 没有候选也随他去 + } + + this.pendingAllocations.clear(); + + for (AutoStateGroup group : AutoStateGroup.values()) { + List>> pendingAllocationFuture = this.pendingAllocationFutures[group.ordinal()]; + for (Pair> pair : pendingAllocationFuture) { + BlockStateCandidate nextCandidate = group.findNextCandidate(); + if (nextCandidate != null) { + nextCandidate.setUsed(); + this.cachedBlockStates.put(pair.left(), nextCandidate.blockState()); + pair.right().complete(nextCandidate.blockState()); + } else { + pair.right().completeExceptionally(new StateExhaustedException(group)); + } + } + } + } + + public static class StateExhaustedException extends RuntimeException { + private final AutoStateGroup group; + + public StateExhaustedException(AutoStateGroup group) { + this.group = group; + } + + public AutoStateGroup group() { + return group; + } + } + + /** + * 从文件加载缓存 + */ + public void loadFromCache() throws IOException { + if (!Files.exists(this.cacheFilePath)) { + return; + } + JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath); + if (element instanceof JsonObject jsonObject) { + for (Map.Entry entry : jsonObject.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + String id = primitive.getAsString(); + BlockStateWrapper state = this.factory.apply(id); + if (state != null) { + this.cachedBlockStates.put(entry.getKey(), state); + } + } + } + } + } + + /** + * 保存缓存到文件 + */ + public void saveToCache() throws IOException { + // 创建按ID排序的TreeMap + Map sortedById = new TreeMap<>(); + for (Map.Entry entry : this.cachedBlockStates.entrySet()) { + sortedById.put(entry.getValue(), entry.getKey()); + } + // 创建有序的JSON对象 + JsonObject sortedJsonObject = new JsonObject(); + for (Map.Entry entry : sortedById.entrySet()) { + sortedJsonObject.addProperty(entry.getValue(), entry.getKey().getAsString()); + } + if (sortedJsonObject.isEmpty()) { + if (Files.exists(this.cacheFilePath)) { + Files.delete(this.cacheFilePath); + } + } else { + FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent()); + GsonHelper.writeJsonFile(sortedJsonObject, this.cacheFilePath); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java new file mode 100644 index 000000000..4a636da69 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/AllocationCacheFile.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; + +public class AllocationCacheFile, A> { + private final Map cache; + private final CacheStorage cacheStorage; + private final CacheSerializer serializer; + + public AllocationCacheFile(CacheStorage cacheStorage, CacheSerializer serializer) { + this.cache = new HashMap<>(); + this.cacheStorage = cacheStorage; + this.serializer = serializer; + } + + public Map cache() { + return this.cache; + } + + public void clear() { + this.cache.clear(); + } + + public CompletableFuture load() { + return this.cacheStorage.load().thenAccept(a -> { + Map deserialized = this.serializer.deserialize(a); + this.cache.putAll(deserialized); + }); + } + + public CompletableFuture save() { + Map sortedById = new TreeMap<>(); + for (Map.Entry entry : this.cache.entrySet()) { + sortedById.put(entry.getValue(), entry.getKey()); + } + return this.cacheStorage.save(this.serializer.serialize(sortedById)); + } + + public Iterable> entrySet() { + return this.cache.entrySet(); + } + + public Set keySet() { + return this.cache.keySet(); + } + + public void put(String name, T newId) { + this.cache.put(name, newId); + } + + public T remove(String name) { + return this.cache.remove(name); + } + + public T get(String name) { + return this.cache.get(name); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java new file mode 100644 index 000000000..d230558fa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileStorage.java @@ -0,0 +1,98 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public interface CacheFileStorage { + + CompletableFuture load(); + + CompletableFuture save(A value); + + boolean needForceUpdate(); + + static LocalFileCacheStorage local(Path path, CacheFileType type) { + return new LocalFileCacheStorage<>(path, type); + } + + abstract class AbstractRemoteFileCacheStorage implements CacheFileStorage { + + @Override + public boolean needForceUpdate() { + return true; + } + } + + class LocalFileCacheStorage implements CacheFileStorage { + private final CacheFileType fileType; + private final Path filePath; + private long lastModified = 0L; + + public LocalFileCacheStorage(Path filePath, CacheFileType type) { + this.filePath = filePath; + this.fileType = type; + updateLastModified(); + } + + @Override + public boolean needForceUpdate() { + try { + if (!Files.exists(this.filePath)) { + return this.lastModified != 0L; // 文件被删除了,需要更新 + } + long currentModified = Files.getLastModifiedTime(this.filePath).toMillis(); + if (currentModified > this.lastModified) { + this.lastModified = currentModified; + return true; // 文件被修改了,需要强制更新 + } + return false; + } catch (IOException e) { + // 如果无法读取文件信息,保守起见返回 true 强制更新 + return true; + } + } + + @Override + public CompletableFuture load() { + if (!Files.exists(this.filePath)) { + this.lastModified = 0L; // 重置最后修改时间 + return CompletableFuture.completedFuture(this.fileType.create()); + } + try { + A result = this.fileType.read(this.filePath); + updateLastModified(); // 加载成功后更新最后修改时间 + return CompletableFuture.completedFuture(result); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + @Override + public CompletableFuture save(A value) { + try { + this.fileType.write(this.filePath, value); + updateLastModified(); // 保存成功后更新最后修改时间 + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + /** + * 更新最后修改时间 + */ + private void updateLastModified() { + try { + if (Files.exists(this.filePath)) { + this.lastModified = Files.getLastModifiedTime(filePath).toMillis(); + } else { + this.lastModified = 0L; + } + } catch (IOException e) { + this.lastModified = 0L; // 出错时重置 + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java new file mode 100644 index 000000000..b4446a511 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheFileType.java @@ -0,0 +1,39 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import com.google.gson.JsonObject; +import net.momirealms.craftengine.core.util.GsonHelper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public interface CacheFileType { + JsonCacheFileType JSON = new JsonCacheFileType(); + + T read(Path path) throws IOException; + + void write(Path path, T value) throws IOException; + + T create(); + + class JsonCacheFileType implements CacheFileType { + + @Override + public JsonObject read(Path path) throws IOException { + if (Files.exists(path)) { + return GsonHelper.readJsonFile(path).getAsJsonObject(); + } + return new JsonObject(); + } + + @Override + public void write(Path path, JsonObject value) throws IOException { + GsonHelper.writeJsonFile(value, path); + } + + @Override + public JsonObject create() { + return new JsonObject(); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java new file mode 100644 index 000000000..161caad29 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheSerializer.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import java.util.HashMap; +import java.util.Map; + +public interface CacheSerializer, A> { + + A serialize(Map obj); + + Map deserialize(A obj); + + static > CacheSerializer json() { + return new JsonSerializer<>(); + } + + class JsonSerializer> implements CacheSerializer { + + @Override + public JsonObject serialize(Map obj) { + JsonObject jsonObject = new JsonObject(); + for (Map.Entry entry : obj.entrySet()) { + if (entry.getKey() instanceof Integer i) { + jsonObject.addProperty(entry.getValue(), i); + } else if (entry.getKey() instanceof String s) { + jsonObject.addProperty(entry.getValue(), s); + } + } + return jsonObject; + } + + @SuppressWarnings("unchecked") + @Override + public Map deserialize(JsonObject obj) { + Map map = new HashMap<>(); + for (Map.Entry entry : obj.entrySet()) { + if (entry.getValue() instanceof JsonPrimitive primitive) { + if (primitive.isNumber()) { + map.put(entry.getKey(), (T) (Integer) primitive.getAsInt()); + } else if (primitive.isString()) { + map.put(entry.getKey(), (T) primitive.getAsString()); + } + } + } + return map; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java new file mode 100644 index 000000000..10856d762 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/allocator/cache/CacheStorage.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.pack.allocator.cache; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CompletableFuture; + +public class CacheStorage { + private final CacheFileStorage storage; + private A lastReadValue; + + public CacheStorage(CacheFileStorage storage) { + this.storage = storage; + } + + public CompletableFuture save(@NotNull final A value) { + if (!value.equals(this.lastReadValue) || this.storage.needForceUpdate()) { + this.lastReadValue = value; + return this.storage.save(value); + } + return CompletableFuture.completedFuture(null); + } + + public CompletableFuture load() { + if (this.lastReadValue != null && !this.storage.needForceUpdate()) { + return CompletableFuture.completedFuture(this.lastReadValue); + } + return this.storage.load().thenApply(a -> { + this.lastReadValue = a; + return a; + }); + } +}