From eaf8d3663b15419507146389f354375871849d81 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Tue, 20 May 2025 04:10:35 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=96=B9=E5=9D=97=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 338 ++++++------------ .../bukkit/block/BukkitCustomBlock.java | 62 +++- .../bukkit/item/BukkitCustomItem.java | 4 +- .../bukkit/item/BukkitItemManager.java | 1 - .../DebugAppearanceStateUsageCommand.java | 2 +- .../feature/TotemAnimationCommand.java | 8 +- .../bukkit/util/BlockStateUtils.java | 4 +- .../bukkit/util/InteractUtils.java | 2 +- .../core/block/AbstractBlockManager.java | 77 +++- .../core/block/AbstractCustomBlock.java | 195 ++++++++++ .../craftengine/core/block/BlockManager.java | 14 + .../craftengine/core/block/BlockSettings.java | 1 + .../core/block/BlockStateMatcher.java | 2 +- .../craftengine/core/block/CustomBlock.java | 240 ++----------- .../craftengine/core/block/EmptyBlock.java | 6 +- .../core/block/ImmutableBlockState.java | 14 +- .../core/block/InactiveCustomBlock.java | 6 +- .../core/block/UnsafeBlockStateMatcher.java | 2 +- .../craftengine/core/block/VanillaBlock.java | 6 + .../core/block/behavior/BlockBehaviors.java | 2 +- .../core/pack/model/LegacyItemModel.java | 6 +- .../context/function/OpenWindowFunction.java | 5 +- .../plugin/context/function/RunFunction.java | 6 +- .../craftengine/core/util/GsonHelper.java | 13 + .../core/util/IntIdentityList.java | 1 - .../craftengine/core/util/ListUtils.java | 16 + .../core/util/ResourceConfigUtils.java | 49 +++ 27 files changed, 598 insertions(+), 484 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlock.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java 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 6f0b2a94d..5308d06ee 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 @@ -3,12 +3,10 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import dev.dejvokep.boostedyaml.YamlDocument; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -28,12 +26,8 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.event.EventFunctions; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; @@ -45,7 +39,6 @@ import org.bukkit.Registry; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -58,50 +51,34 @@ public class BukkitBlockManager extends AbstractBlockManager { private static BukkitBlockManager instance; private final BukkitCraftEngine plugin; private final BlockParser blockParser; - - // A temporary map used to detect whether the same block state corresponds to multiple models. - private final Map tempRegistryIdConflictMap = new Int2ObjectOpenHashMap<>(); - // A temporary map that converts the custom block registered on the server to the vanilla block ID. - private final Map tempBlockAppearanceConvertor = new Int2IntOpenHashMap(); - // A temporary map that stores the model path of a certain vanilla block state - private final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); - // The total amount of blocks registered private int customBlockCount; protected final ImmutableBlockState[] stateId2ImmutableBlockStates; // Minecraft objects // Cached new blocks $ holders - private ImmutableMap internalId2StateId; - private ImmutableMap stateId2BlockHolder; + private Map internalId2StateId; + private Map stateId2BlockHolder; // This map is used to change the block states that are not necessarily needed into a certain block state - private ImmutableMap blockAppearanceMapper; + private Map blockAppearanceMapper; // Used to automatically arrange block states for strings such as minecraft:note_block:0 - private ImmutableMap> blockAppearanceArranger; - private ImmutableMap> realBlockArranger; + private Map> blockAppearanceArranger; + private Map> realBlockArranger; // Record the amount of real blocks by block type - private LinkedHashMap registeredRealBlockSlots; + private Map registeredRealBlockSlots; // A set of blocks that sounds have been removed - private ImmutableSet affectedSoundBlocks; - private ImmutableMap soundMapper; + private Set affectedSoundBlocks; + private Map soundMapper; // A list to record the order of registration private List blockRegisterOrder = new ObjectArrayList<>(); - - // a reverted mapper - private final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); - // Used to store override information of json files - private final Map> blockStateOverrides = new HashMap<>(); - // for mod, real block id -> state models - private final Map modBlockStates = new HashMap<>(); // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - - private Map> clientBoundTags = Map.of(); - private Map> previousTags = Map.of(); + // cached tag packet protected Object cachedUpdateTagsPacket; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); + instance = this; this.plugin = plugin; this.blockParser = new BlockParser(); this.initVanillaRegistry(); @@ -120,42 +97,33 @@ public class BukkitBlockManager extends AbstractBlockManager { if (enableNoteBlocks) { this.recordVanillaNoteBlocks(); } - if (VersionHelper.isOrAbove1_20_3()) { - this.fallingBlockRemoveListener = new FallingBlockRemoveListener(); - } else this.fallingBlockRemoveListener = null; - this.stateId2ImmutableBlockStates = new ImmutableBlockState[customBlockCount]; + this.fallingBlockRemoveListener = VersionHelper.isOrAbove1_20_3() ? new FallingBlockRemoveListener() : null; + this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount]; Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - instance = this; this.resetPacketConsumers(); } - public List blockRegisterOrder() { - return Collections.unmodifiableList(this.blockRegisterOrder); - } - public static BukkitBlockManager instance() { return instance; } + public List blockRegisterOrder() { + return Collections.unmodifiableList(this.blockRegisterOrder); + } + @Override public void delayedInit() { - Bukkit.getPluginManager().registerEvents(this.blockEventListener, plugin.bootstrap()); + Bukkit.getPluginManager().registerEvents(this.blockEventListener, this.plugin.bootstrap()); if (this.fallingBlockRemoveListener != null) { - Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, plugin.bootstrap()); + Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, this.plugin.bootstrap()); } } @Override public void unload() { super.unload(); - this.clearCache(); - this.appearanceToRealState.clear(); - this.blockStateOverrides.clear(); - this.modBlockStates.clear(); if (EmptyBlock.STATE != null) Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.STATE); - this.previousTags = this.clientBoundTags; - this.clientBoundTags = new HashMap<>(); } @Override @@ -172,15 +140,14 @@ public class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - initSuggestions(); - resetPacketConsumers(); - clearCache(); - resendTags(); + this.resetPacketConsumers(); + super.delayedLoad(); } - private void resendTags() { + @Override + protected void resendTags() { // if there's no change - if (this.clientBoundTags.equals(this.previousTags)) return; + if (this.clientBoundTags.equals(this.previousClientBoundTags)) return; List list = new ArrayList<>(); for (Map.Entry> entry : this.clientBoundTags.entrySet()) { list.add(new TagUtils.TagEntry(entry.getKey(), entry.getValue())); @@ -197,23 +164,19 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - private void clearCache() { - this.tempRegistryIdConflictMap.clear(); - this.tempBlockAppearanceConvertor.clear(); - this.tempVanillaBlockStateModels.clear(); - } - @Nullable public Object getMinecraftBlockHolder(int stateId) { return stateId2BlockHolder.get(stateId); } @NotNull + @Override public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) { return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; } @Nullable + @Override public ImmutableBlockState getImmutableBlockState(int stateId) { if (!BlockStateUtils.isVanillaBlock(stateId)) { return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; @@ -221,34 +184,46 @@ public class BukkitBlockManager extends AbstractBlockManager { return null; } - @Override - public Map modBlockStates() { - return Collections.unmodifiableMap(this.modBlockStates); - } - @Override public ConfigParser parser() { return this.blockParser; } @Override - public Map> blockOverrides() { - return Collections.unmodifiableMap(this.blockStateOverrides); + public void addBlock(Key id, CustomBlock customBlock) { + // bind appearance and real state + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; + if (previous != null && !previous.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString()); + } + this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; + this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); + this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new IntArrayList()).add(state.customBlockState().registryId()); + } + super.addBlock(id, customBlock); } - public ImmutableMap> blockAppearanceArranger() { + @Override + public Key getBlockOwnerId(PackedBlockState state) { + return BlockStateUtils.getBlockOwnerIdFromState(state.handle()); + } + + @Override + public int availableAppearances(Key blockType) { + return Optional.ofNullable(this.registeredRealBlockSlots.get(blockType)).orElse(0); + } + + @NotNull + public Map> blockAppearanceArranger() { return this.blockAppearanceArranger; } - public ImmutableMap> realBlockArranger() { + @NotNull + public Map> realBlockArranger() { return this.realBlockArranger; } - @Nullable - public List appearanceToRealStates(int appearanceStateId) { - return this.appearanceToRealState.get(appearanceStateId); - } - private void initMirrorRegistry() { int size = RegistryUtils.currentBlockRegistrySize(); PackedBlockState[] states = new PackedBlockState[size]; @@ -276,25 +251,25 @@ public class BukkitBlockManager extends AbstractBlockManager { private void initVanillaRegistry() { int vanillaStateCount; - if (plugin.hasMod()) { + if (this.plugin.hasMod()) { try { Class modClass = ReflectionUtils.getClazz(CraftEngine.MOD_CLASS); Field amountField = ReflectionUtils.getDeclaredField(modClass, "vanillaRegistrySize"); vanillaStateCount = amountField.getInt(null); } catch (Exception e) { vanillaStateCount = RegistryUtils.currentBlockRegistrySize(); - plugin.logger().severe("Fatal error", e); + this.plugin.logger().severe("Fatal error", e); } } else { vanillaStateCount = RegistryUtils.currentBlockRegistrySize(); } - plugin.logger().info("Vanilla block count: " + vanillaStateCount); + this.plugin.logger().info("Vanilla block count: " + vanillaStateCount); BlockStateUtils.init(vanillaStateCount); } @SuppressWarnings("unchecked") private void registerBlocks() { - plugin.logger().info("Registering blocks. Please wait..."); + this.plugin.logger().info("Registering blocks. Please wait..."); try { ImmutableMap.Builder builder1 = ImmutableMap.builder(); ImmutableMap.Builder builder2 = ImmutableMap.builder(); @@ -367,7 +342,7 @@ public class BukkitBlockManager extends AbstractBlockManager { parseVanillaBlock(pack, path, id, section); } else { // check duplicated config - if (byId.containsKey(id)) { + if (BukkitBlockManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.block.duplicate"); } parseCustomBlock(pack, path, id, section); @@ -376,128 +351,73 @@ public class BukkitBlockManager extends AbstractBlockManager { private void parseCustomBlock(Pack pack, Path path, Key id, Map section) { // read block settings - BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false)); - - // read loot table - LootTable lootTable = LootTable.fromMap(MiscUtils.castToMap(section.getOrDefault("loot", Map.of()), false)); - + BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)); // read states Map> properties; Map appearances; Map variants; - Object stateObj = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"); - Map stateSection = MiscUtils.castToMap(stateObj, true); + Map stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true); + boolean singleState = !stateSection.containsKey("properties"); // single state - if (!stateSection.containsKey("properties")) { + if (singleState) { properties = Map.of(); - int internalId = ResourceConfigUtils.getAsInt(stateSection.getOrDefault("id", -1), "id"); - if (internalId < 0) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_real_id"); - } - - Pair pair = parseAppearanceSection(id, stateSection); - if (pair == null) return; - - appearances = Map.of("default", pair.right()); - String internalBlock = pair.left().value() + "_" + internalId; - Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, internalBlock); + int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); + VanillaBlock vanillaBlock = getVanillaBlock(id, stateSection); + appearances = Map.of("", vanillaBlock.registryId()); + Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, vanillaBlock.type().value() + "_" + internalId); int internalBlockRegistryId = Optional.ofNullable(internalId2StateId.get(internalBlockId)).orElse(-1); if (internalBlockRegistryId == -1) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", - internalBlock, - String.valueOf(registeredRealBlockSlots.get(pair.left()) - 1)); + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(vanillaBlock.type()) - 1)); } - variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId)); + variants = Map.of("", new VariantState("", settings, internalBlockRegistryId)); } else { // properties - Map propertySection = MiscUtils.castToMap(stateSection.get("properties"), true); - if (propertySection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_properties"); - } - properties = parseProperties(propertySection); + properties = getProperties(MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), true)); // appearance - Map appearancesSection = MiscUtils.castToMap(stateSection.get("appearances"), true); - if (appearancesSection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances"); - } appearances = new HashMap<>(); - Map tempTypeMap = new HashMap<>(); - for (Map.Entry appearanceEntry : appearancesSection.entrySet()) { - if (appearanceEntry.getValue() instanceof Map appearanceSection) { - Pair pair = parseAppearanceSection(id, MiscUtils.castToMap(appearanceSection, false)); - if (pair == null) return; - appearances.put(appearanceEntry.getKey(), pair.right()); - tempTypeMap.put(appearanceEntry.getKey(), pair.left()); + Map appearance2BlockType = new HashMap<>(); + for (Map.Entry appearanceEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), false).entrySet()) { + if (appearanceEntry.getValue() instanceof Map) { + VanillaBlock vanillaBlock = getVanillaBlock(id, MiscUtils.castToMap(appearanceEntry.getValue(), false)); + appearances.put(appearanceEntry.getKey(), vanillaBlock.registryId()); + appearance2BlockType.put(appearanceEntry.getKey(), vanillaBlock.type()); } } // variants - Map variantsSection = MiscUtils.castToMap(stateSection.get("variants"), true); - if (variantsSection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_variants"); - } variants = new HashMap<>(); - for (Map.Entry variantEntry : variantsSection.entrySet()) { - if (variantEntry.getValue() instanceof Map variantSection0) { - Map variantSection = MiscUtils.castToMap(variantSection0, false); - String variantName = variantEntry.getKey(); - String appearance = (String) variantSection.get("appearance"); - if (appearance == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.missing_appearance", variantName); - } + for (Map.Entry variantEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), false).entrySet()) { + if (variantEntry.getValue() instanceof Map) { + Map variantSection = MiscUtils.castToMap(variantEntry.getValue(), false); + String variantNBT = variantEntry.getKey(); + String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance"); if (!appearances.containsKey(appearance)) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantName, appearance); + throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance); } - int internalId = ResourceConfigUtils.getAsInt(variantSection.getOrDefault("id", -1), "id"); - Key baseBlock = tempTypeMap.get(appearance); + int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); + Key baseBlock = appearance2BlockType.get(appearance); Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, baseBlock.value() + "_" + internalId); int internalBlockRegistryId = Optional.ofNullable(internalId2StateId.get(internalBlockId)).orElse(-1); if (internalBlockRegistryId == -1) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", - internalBlockId.toString(), - String.valueOf(registeredRealBlockSlots.getOrDefault(baseBlock, 1) - 1)); + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(baseBlock) - 1)); } Map anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true); - variants.put(variantName, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId)); + variants.put(variantNBT, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId)); } } } - Object eventsObj = ResourceConfigUtils.get(section, "events", "event"); - EnumMap>> events = EventFunctions.parseEvents(eventsObj); - - Map behaviors = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false); CustomBlock block = BukkitCustomBlock.builder(id) .appearances(appearances) .variantMapper(variants) - .lootTable(lootTable) .properties(properties) .settings(settings) - .behavior(behaviors) - .events(events) + .lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true))) + .behavior(MiscUtils.castToMap(section.get("behavior"), true)) + .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) .build(); - // bind appearance and real state - for (ImmutableBlockState state : block.variantProvider().states()) { - ImmutableBlockState previous = stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; - if (previous != null && !previous.isEmpty()) { - TranslationManager.instance().log("warning.config.block.state.bind_failed", path.toString(), id.toString(), state.toString(), previous.toString()); - continue; - } - stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; - tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); - appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new IntArrayList()).add(state.customBlockState().registryId()); - } - - BukkitBlockManager.this.byId.put(id, block); - - // generate mod assets - if (Config.generateModAssets()) { - for (ImmutableBlockState state : block.variantProvider().states()) { - Key realBlockId = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState()); - modBlockStates.put(realBlockId, tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId())); - } - } + addBlock(id, block); } private void parseVanillaBlock(Pack pack, Path path, Key id, Map section) { @@ -518,7 +438,8 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - private Map> parseProperties(Map propertiesSection) { + @NotNull + private Map> getProperties(Map propertiesSection) { Map> properties = new HashMap<>(); for (Map.Entry entry : propertiesSection.entrySet()) { Property property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(entry.getValue(), false)); @@ -527,69 +448,40 @@ public class BukkitBlockManager extends AbstractBlockManager { return properties; } - @Nullable - private Pair parseAppearanceSection(Key id, Map section) { + @NotNull + private VanillaBlock getVanillaBlock(Key id, Map section) { // require state non null - Object vanillaStateString = section.get("state"); - if (vanillaStateString == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_state"); - } - + String vanillaBlockStateTag = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("state"), "warning.config.block.state.missing_state"); // get its registry id - int vanillaStateRegistryId = parseVanillaStateRegistryId(vanillaStateString.toString()); - - // check conflict - Key ifAny = this.tempRegistryIdConflictMap.get(vanillaStateRegistryId); + int vanillaBlockStateRegistryId = getVanillaBlockStateRegistryId(vanillaBlockStateTag); + // check if another block has occupied the appearance + // TODO blocks share the same look + Key ifAny = this.tempRegistryIdConflictMap.get(vanillaBlockStateRegistryId); if (ifAny != null && !ifAny.equals(id)) { - throw new LocalizedResourceConfigException("warning.config.block.state.conflict", BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaStateRegistryId)).getAsString(), ifAny.toString()); + throw new LocalizedResourceConfigException("warning.config.block.state.conflict", BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaBlockStateRegistryId)).getAsString(), ifAny.toString()); } - // require models not to be null - Object models = section.get("models"); - if (models == null) { - models = section.get("model"); - } - if (models == null) { + Object models = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "models", "model"), "warning.config.block.state.missing_model"); + List variants = ResourceConfigUtils.parseConfigAsList(models, this::getVariantModel); + if (variants.isEmpty()) { throw new LocalizedResourceConfigException("warning.config.block.state.missing_model"); } - - List variants = new ArrayList<>(); - if (models instanceof Map singleModelSection) { - loadVariantModel(variants, MiscUtils.castToMap(singleModelSection, false)); - } else if (models instanceof List modelList) { - for (Object model : modelList) { - if (model instanceof Map singleModelMap) { - loadVariantModel(variants, MiscUtils.castToMap(singleModelMap, false)); - } - } - } - if (variants.isEmpty()) return null; - - this.tempRegistryIdConflictMap.put(vanillaStateRegistryId, id); - String blockState = BlockStateUtils.idToBlockState(vanillaStateRegistryId).toString(); - Key block = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); - String propertyData = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); - Map paths = this.blockStateOverrides.computeIfAbsent(block, k -> new HashMap<>()); - if (variants.size() == 1) { - paths.put(propertyData, variants.get(0)); - this.tempVanillaBlockStateModels.put(vanillaStateRegistryId, variants.get(0)); - } else { - JsonArray array = new JsonArray(); - for (JsonObject object : variants) { - array.add(object); - } - paths.put(propertyData, array); - this.tempVanillaBlockStateModels.put(vanillaStateRegistryId, array); - } - return Pair.of(block, vanillaStateRegistryId); + // TODO blocks share the same look + this.tempRegistryIdConflictMap.put(vanillaBlockStateRegistryId, id); + // gets the full block state + String blockState = BlockStateUtils.idToBlockState(vanillaBlockStateRegistryId).toString(); + Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); + String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); + // for generating assets + JsonElement combinedVariant = GsonHelper.combine(variants); + this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()).put(propertyNBT, combinedVariant); + this.tempVanillaBlockStateModels.put(vanillaBlockStateRegistryId, combinedVariant); + return new VanillaBlock(blockId, propertyNBT, vanillaBlockStateRegistryId); } - private void loadVariantModel(List variants, Map singleModelMap) { + private JsonObject getVariantModel(Map singleModelMap) { JsonObject json = new JsonObject(); - String modelPath = (String) singleModelMap.get("path"); - if (modelPath == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.model.missing_path"); - } + String modelPath = ResourceConfigUtils.requireNonEmptyStringOrThrow(singleModelMap.get("path"), "warning.config.block.state.model.missing_path"); if (!ResourceLocation.isValid(modelPath)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.invalid_path", modelPath); } @@ -602,10 +494,10 @@ public class BukkitBlockManager extends AbstractBlockManager { if (generationMap != null) { prepareModelGeneration(ModelGeneration.of(Key.of(modelPath), generationMap)); } - variants.add(json); + return json; } - private int parseVanillaStateRegistryId(String blockState) { + private int getVanillaBlockStateRegistryId(String blockState) { String[] split = blockState.split(":", 3); if (split.length >= 4) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index 17891cded..c9897a727 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.*; -public class BukkitCustomBlock extends CustomBlock { +public class BukkitCustomBlock extends AbstractCustomBlock { protected BukkitCustomBlock( Key id, @@ -147,17 +147,67 @@ public class BukkitCustomBlock extends CustomBlock { } public static Builder builder(Key id) { - return new Builder(id); + return new BuilderImpl(id); } - public static class Builder extends CustomBlock.Builder { + public static class BuilderImpl implements Builder { + protected final Key id; + protected Map> properties; + protected Map appearances; + protected Map variantMapper; + protected BlockSettings settings; + protected Map behavior; + protected LootTable lootTable; + protected EnumMap>> events; - protected Builder(Key id) { - super(id); + public BuilderImpl(Key id) { + this.id = id; } @Override - public CustomBlock build() { + public Builder events(EnumMap>> events) { + this.events = events; + return this; + } + + @Override + public Builder appearances(Map appearances) { + this.appearances = appearances; + return this; + } + + @Override + public Builder behavior(Map behavior) { + this.behavior = behavior; + return this; + } + + @Override + public Builder lootTable(LootTable lootTable) { + this.lootTable = lootTable; + return this; + } + + @Override + public Builder properties(Map> properties) { + this.properties = properties; + return this; + } + + @Override + public Builder settings(BlockSettings settings) { + this.settings = settings; + return this; + } + + @Override + public Builder variantMapper(Map variantMapper) { + this.variantMapper = variantMapper; + return this; + } + + @Override + public @NotNull CustomBlock build() { // create or get block holder Holder.Reference holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() -> ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id))); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java index a0438f38c..fd7c32143 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java @@ -13,7 +13,9 @@ import net.momirealms.craftengine.core.util.Key; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; public class BukkitCustomItem extends AbstractCustomItem { private final Material material; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 57fa33974..f51b4ee82 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -36,7 +36,6 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java index 2aad44aad..f47eb842e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugAppearanceStateUsageCommand.java @@ -51,7 +51,7 @@ public class DebugAppearanceStateUsageCommand extends BukkitCommandFeature reals = blockManager.appearanceToRealStates(appearance); - if (reals == null) { + if (reals == null || reals.isEmpty()) { Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN)); text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java index e0381d921..63f72aaca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java @@ -3,16 +3,17 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.bukkit.util.MaterialUtils; import net.momirealms.craftengine.bukkit.util.PlayerUtils; -import net.momirealms.craftengine.core.item.*; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.FlagKeys; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; -import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -26,7 +27,6 @@ import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandInput; import org.incendo.cloud.parser.flag.CommandFlag; -import org.incendo.cloud.parser.standard.StringParser; import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java index 81163c601..a5d8d4446 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java @@ -101,10 +101,10 @@ public class BlockStateUtils { } public static Key getBlockOwnerId(Block block) { - return getBlockOwnerId(block.getBlockData()); + return getBlockOwnerIdFromData(block.getBlockData()); } - public static Key getBlockOwnerId(BlockData block) { + public static Key getBlockOwnerIdFromData(BlockData block) { Object blockState = blockDataToBlockState(block); return getBlockOwnerIdFromState(blockState); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java index 7152b3f98..fa6c73e44 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java @@ -274,7 +274,7 @@ public class InteractUtils { } public static boolean isInteractable(Player player, BlockData state, BlockHitResult hit, Item item) { - Key blockType = BlockStateUtils.getBlockOwnerId(state); + Key blockType = BlockStateUtils.getBlockOwnerIdFromData(state); if (INTERACTIONS.containsKey(blockType)) { return INTERACTIONS.get(blockType).apply(player, item, state, hit); } else { 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 ba3e03d8f..48bde480b 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 @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.block; +import com.google.gson.JsonElement; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.Key; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.NotNull; import java.util.*; @@ -14,11 +19,46 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final List cachedSuggestions = new ArrayList<>(); // Cached Namespace protected final Set namespacesInUse = new HashSet<>(); + // for mod, real block id -> state models + protected final Map modBlockStates = new HashMap<>(); + // A temporary map that stores the model path of a certain vanilla block state + protected final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); + // A temporary map used to detect whether the same block state corresponds to multiple models. + protected final Map tempRegistryIdConflictMap = new Int2ObjectOpenHashMap<>(); + // A temporary map that converts the custom block registered on the server to the vanilla block ID. + protected final Map tempBlockAppearanceConvertor = new Int2IntOpenHashMap(); + // Used to store override information of json files + protected final Map> blockStateOverrides = new HashMap<>(); + // a reverted mapper + protected final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); + // client side block tags + protected Map> clientBoundTags = Map.of(); + protected Map> previousClientBoundTags = Map.of(); - public AbstractBlockManager(CraftEngine plugin) { + protected AbstractBlockManager(CraftEngine plugin) { super(plugin); } + @Override + public void unload() { + super.clearModelsToGenerate(); + this.clearCache(); + this.cachedSuggestions.clear(); + this.blockStateOverrides.clear(); + this.modBlockStates.clear(); + this.byId.clear(); + this.previousClientBoundTags = this.clientBoundTags; + this.clientBoundTags = new HashMap<>(); + this.appearanceToRealState.clear(); + } + + @Override + public void delayedLoad() { + this.initSuggestions(); + this.clearCache(); + this.resendTags(); + } + @Override public Map blocks() { return Collections.unmodifiableMap(this.byId); @@ -30,10 +70,24 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } @Override - public void unload() { - super.clearModelsToGenerate(); - this.cachedSuggestions.clear(); - this.byId.clear(); + public void addBlock(Key id, CustomBlock customBlock) { + this.byId.put(id, customBlock); + // generate mod assets + if (Config.generateModAssets()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + this.modBlockStates.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId())); + } + } + } + + @Override + public Map modBlockStates() { + return Collections.unmodifiableMap(this.modBlockStates); + } + + @Override + public Map> blockOverrides() { + return Collections.unmodifiableMap(this.blockStateOverrides); } @Override @@ -45,6 +99,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Collections.unmodifiableSet(this.namespacesInUse); } + protected void clearCache() { + this.tempRegistryIdConflictMap.clear(); + this.tempBlockAppearanceConvertor.clear(); + this.tempVanillaBlockStateModels.clear(); + } + protected void initSuggestions() { this.cachedSuggestions.clear(); this.namespacesInUse.clear(); @@ -60,4 +120,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.cachedSuggestions.add(Suggestion.suggestion(state)); } } + + @NotNull + public List appearanceToRealStates(int appearanceStateId) { + return Optional.ofNullable(this.appearanceToRealState.get(appearanceStateId)).orElse(List.of()); + } + + protected abstract void resendTags(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java new file mode 100644 index 000000000..cb3efd929 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -0,0 +1,195 @@ +package net.momirealms.craftengine.core.block; + +import com.google.common.collect.ImmutableMap; +import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.plugin.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.shared.block.BlockBehavior; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiFunction; + +public abstract class AbstractCustomBlock implements CustomBlock { + protected final Holder holder; + protected final Key id; + protected final BlockStateVariantProvider variantProvider; + protected final Map> properties; + protected final BlockBehavior behavior; + protected final BiFunction placementFunction; + protected final ImmutableBlockState defaultState; + protected final Map>> events; + @Nullable + protected final LootTable lootTable; + + protected AbstractCustomBlock( + @NotNull Key id, + @NotNull Holder.Reference holder, + @NotNull Map> properties, + @NotNull Map appearances, + @NotNull Map variantMapper, + @NotNull BlockSettings settings, + @NotNull Map>> events, + @Nullable Map behavior, + @Nullable LootTable lootTable + ) { + holder.bindValue(this); + this.holder = holder; + this.id = id; + this.lootTable = lootTable; + this.properties = ImmutableMap.copyOf(properties); + this.events = events; + this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties); + this.defaultState = this.variantProvider.getDefaultState(); + this.behavior = BlockBehaviors.fromMap(this, behavior); + List> placements = new ArrayList<>(4); + for (Map.Entry> propertyEntry : this.properties.entrySet()) { + placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); + } + this.placementFunction = composite(placements); + for (Map.Entry entry : variantMapper.entrySet()) { + String nbtString = entry.getKey(); + CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); + if (tag == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); + } + VariantState variantState = entry.getValue(); + int vanillaStateRegistryId = appearances.getOrDefault(variantState.appearance(), -1); + // This should never happen + if (vanillaStateRegistryId == -1) { + vanillaStateRegistryId = appearances.values().iterator().next(); + } + // Late init states + for (ImmutableBlockState state : this.getPossibleStates(tag)) { + state.setBehavior(this.behavior); + state.setSettings(variantState.settings()); + state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); + state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(variantState.internalRegistryId())); + } + } + // double check if there's any invalid state + for (ImmutableBlockState state : this.variantProvider().states()) { + if (state.settings() == null) { + state.setSettings(settings); + } + } + this.applyPlatformSettings(); + } + + private static BiFunction composite(List> placements) { + return switch (placements.size()) { + case 0 -> (c, i) -> i; + case 1 -> placements.get(0); + case 2 -> { + BiFunction f1 = placements.get(0); + BiFunction f2 = placements.get(1); + yield (c, i) -> f2.apply(c, f1.apply(c, i)); + } + default -> (c, i) -> { + for (BiFunction f : placements) { + i = f.apply(c, i); + } + return i; + }; + }; + } + + protected abstract void applyPlatformSettings(); + + @Override + public @Nullable LootTable lootTable() { + return this.lootTable; + } + + @Override + public void execute(PlayerOptionalContext context, EventTrigger trigger) { + for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { + function.run(context); + } + } + + @NotNull + @Override + public BlockStateVariantProvider variantProvider() { + return this.variantProvider; + } + + @NotNull + @Override + public final Key id() { + return this.id; + } + + @Override + public List getPossibleStates(CompoundTag nbt) { + List tempStates = new ArrayList<>(); + tempStates.add(defaultState()); + for (Property property : this.variantProvider.getDefaultState().getProperties()) { + Tag value = nbt.get(property.name()); + if (value != null) { + tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); + } else { + List newStates = new ArrayList<>(); + for (ImmutableBlockState state : tempStates) { + for (Object possibleValue : property.possibleValues()) { + newStates.add(ImmutableBlockState.with(state, property, possibleValue)); + } + } + tempStates = newStates; + } + } + return tempStates; + } + + @Override + public ImmutableBlockState getBlockState(CompoundTag nbt) { + ImmutableBlockState state = defaultState(); + for (Map.Entry entry : nbt.tags.entrySet()) { + Property property = this.variantProvider.getProperty(entry.getKey()); + if (property != null) { + try { + state = ImmutableBlockState.with(state, property, property.unpack(entry.getValue())); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to parse block state: " + entry.getKey(), e); + } + } + } + return state; + } + + @Override + public @Nullable Property getProperty(String name) { + return this.properties.get(name); + } + + @Override + public @NotNull Collection> properties() { + return this.properties.values(); + } + + @Override + public final ImmutableBlockState defaultState() { + return this.defaultState; + } + + @Override + public ImmutableBlockState getStateForPlacement(BlockPlaceContext context) { + ImmutableBlockState state = this.placementFunction.apply(context, defaultState()); + if (this.behavior instanceof AbstractBlockBehavior blockBehavior) { + state = blockBehavior.updateStateForPlacement(context, state); + } + return state; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java index 8cbeb5a5a..208355f1a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java @@ -7,7 +7,9 @@ import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.util.Key; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Map; import java.util.Optional; @@ -26,7 +28,19 @@ public interface BlockManager extends Manageable, ModelGenerator { Optional blockById(Key key); + void addBlock(Key id, CustomBlock customBlock); + Collection cachedSuggestions(); Map soundMapper(); + + int availableAppearances(Key blockType); + + Key getBlockOwnerId(PackedBlockState state); + + @NotNull + ImmutableBlockState getImmutableBlockStateUnsafe(int stateId); + + @Nullable + ImmutableBlockState getImmutableBlockState(int stateId); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java index cfcfd042c..79e268a99 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java @@ -42,6 +42,7 @@ public class BlockSettings { } public static BlockSettings fromMap(Map map) { + if (map == null || map.isEmpty()) return BlockSettings.of(); return applyModifiers(BlockSettings.of(), map); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java index 3886220dc..87678487c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java @@ -24,7 +24,7 @@ public class BlockStateMatcher { } public boolean matches(ImmutableBlockState state) { - if (!state.owner().value().id.equals(this.id)) { + if (!state.owner().value().id().equals(this.id)) { return false; } for (Pair, Comparable> pair : this.properties) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index 73c324414..2e3fb29a7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -1,251 +1,59 @@ package net.momirealms.craftengine.core.block; -import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.function.Function; import net.momirealms.craftengine.core.plugin.event.EventTrigger; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.shared.block.BlockBehavior; import net.momirealms.sparrow.nbt.CompoundTag; -import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.BiFunction; +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; -public abstract class CustomBlock { - protected final Holder holder; - protected final Key id; - protected final BlockStateVariantProvider variantProvider; - protected final Map> properties; - protected final BlockBehavior behavior; - protected final List> placements; - protected final ImmutableBlockState defaultState; - protected final EnumMap>> events; - @Nullable - protected final LootTable lootTable; +public interface CustomBlock { - protected CustomBlock( - @NotNull Key id, - @NotNull Holder.Reference holder, - @NotNull Map> properties, - @NotNull Map appearances, - @NotNull Map variantMapper, - @NotNull BlockSettings settings, - @NotNull EnumMap>> events, - @Nullable Map behavior, - @Nullable LootTable lootTable - ) { - holder.bindValue(this); - this.holder = holder; - this.id = id; - this.lootTable = lootTable; - this.properties = properties; - this.placements = new ArrayList<>(); - this.events = events; - this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties); - this.defaultState = this.variantProvider.getDefaultState(); - this.behavior = BlockBehaviors.fromMap(this, behavior); - for (Map.Entry entry : variantMapper.entrySet()) { - String nbtString = entry.getKey(); - CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); - if (tag == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); - } - VariantState variantState = entry.getValue(); - int vanillaStateRegistryId = appearances.getOrDefault(variantState.appearance(), -1); - // This should never happen - if (vanillaStateRegistryId == -1) { - vanillaStateRegistryId = appearances.values().iterator().next(); - } - // Late init states - for (ImmutableBlockState state : this.getPossibleStates(tag)) { - state.setBehavior(this.behavior); - state.setSettings(variantState.settings()); - state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); - state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(variantState.internalRegistryId())); - } - } - // double check if there's any invalid state - for (ImmutableBlockState state : this.variantProvider().states()) { - if (state.settings() == null) { - state.setSettings(settings); - } - } - this.applyPlatformSettings(); - for (Map.Entry> propertyEntry : this.properties.entrySet()) { - this.placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); - } - } + Key id(); - protected abstract void applyPlatformSettings(); + @Nullable LootTable lootTable(); - @Nullable - public LootTable lootTable() { - return lootTable; - } + void execute(PlayerOptionalContext context, EventTrigger trigger); - public void execute(PlayerOptionalContext context, EventTrigger trigger) { - for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { - function.run(context); - } - } + @NotNull BlockStateVariantProvider variantProvider(); - @NotNull - public BlockStateVariantProvider variantProvider() { - return variantProvider; - } + List getPossibleStates(CompoundTag nbt); - @NotNull - public final Key id() { - return id; - } + ImmutableBlockState getBlockState(CompoundTag nbt); - private List getPossibleStates(CompoundTag nbt) { - List tempStates = new ArrayList<>(); - tempStates.add(defaultState()); - for (Property property : variantProvider.getDefaultState().getProperties()) { - Tag value = nbt.get(property.name()); - if (value != null) { - tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); - } else { - List newStates = new ArrayList<>(); - for (ImmutableBlockState state : tempStates) { - for (Object possibleValue : property.possibleValues()) { - newStates.add(ImmutableBlockState.with(state, property, possibleValue)); - } - } - tempStates = newStates; - } - } - return tempStates; - } + @Nullable Property getProperty(String name); - public ImmutableBlockState getBlockState(CompoundTag nbt) { - ImmutableBlockState state = defaultState(); - for (Map.Entry entry : nbt.tags.entrySet()) { - Property property = this.variantProvider.getProperty(entry.getKey()); - if (property != null) { - try { - state = ImmutableBlockState.with(state, property, property.unpack(entry.getValue())); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to parse block state: " + entry.getKey(), e); - } - } - } - return state; - } + @NotNull Collection> properties(); - @Nullable - public Property getProperty(String name) { - return this.properties.get(name); - } + ImmutableBlockState defaultState(); - @NotNull - public Collection> properties() { - return this.properties.values(); - } + ImmutableBlockState getStateForPlacement(BlockPlaceContext context); - public final ImmutableBlockState defaultState() { - return this.defaultState; - } + interface Builder { - public ImmutableBlockState getStateForPlacement(BlockPlaceContext context) { - ImmutableBlockState state = defaultState(); - for (BiFunction placement : this.placements) { - state = placement.apply(context, state); - } - if (this.behavior instanceof AbstractBlockBehavior blockBehavior) { - state = blockBehavior.updateStateForPlacement(context, state); - } - return state; - } + Builder events(EnumMap>> events); - public abstract static class Builder { - protected final Key id; - protected Map> properties; - protected Map appearances; - protected Map variantMapper; - protected BlockSettings settings; - protected Map behavior; - protected LootTable lootTable; - protected EnumMap>> events; + Builder appearances(Map appearances); - protected Builder(Key id) { - this.id = id; - } + Builder behavior(Map behavior); - public Builder events(EnumMap>> events) { - this.events = events; - return this; - } + Builder lootTable(LootTable lootTable); - public Builder appearances(Map appearances) { - this.appearances = appearances; - return this; - } + Builder properties(Map> properties); - public Builder behavior(Map behavior) { - this.behavior = behavior; - return this; - } + Builder settings(BlockSettings settings); - public Builder lootTable(LootTable lootTable) { - this.lootTable = lootTable; - return this; - } + Builder variantMapper(Map variantMapper); - public Builder properties(Map> properties) { - this.properties = properties; - return this; - } - - public Builder settings(BlockSettings settings) { - this.settings = settings; - return this; - } - - public Builder variantMapper(Map variantMapper) { - this.variantMapper = variantMapper; - return this; - } - - public Map appearances() { - return appearances; - } - - public Map behavior() { - return behavior; - } - - public Key id() { - return id; - } - - public LootTable lootTable() { - return lootTable; - } - - public Map> properties() { - return properties; - } - - public BlockSettings settings() { - return settings; - } - - public Map variantMapper() { - return variantMapper; - } - - public abstract CustomBlock build(); + @NotNull CustomBlock build(); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java index 4e5cca009..1e24acdc0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java @@ -1,18 +1,16 @@ package net.momirealms.craftengine.core.block; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; -import java.util.EnumMap; import java.util.Map; -public class EmptyBlock extends CustomBlock { +public class EmptyBlock extends AbstractCustomBlock { public static EmptyBlock INSTANCE; public static ImmutableBlockState STATE; public EmptyBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), new EnumMap<>(EventTrigger.class), null, null); + super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), null, null); INSTANCE = this; STATE = defaultState(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 772468379..32c5890a9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -35,7 +35,7 @@ public class ImmutableBlockState extends BlockStateHolder { } public BlockBehavior behavior() { - return behavior; + return this.behavior; } public void setBehavior(BlockBehavior behavior) { @@ -43,7 +43,7 @@ public class ImmutableBlockState extends BlockStateHolder { } public BlockSettings settings() { - return settings; + return this.settings; } public void setSettings(BlockSettings settings) { @@ -116,16 +116,16 @@ public class ImmutableBlockState extends BlockStateHolder { } public CompoundTag getNbtToSave() { - if (tag == null) { - tag = toNbtToSave(propertiesNbt()); + if (this.tag == null) { + this.tag = toNbtToSave(propertiesNbt()); } - return tag; + return this.tag; } public CompoundTag toNbtToSave(CompoundTag properties) { CompoundTag tag = new CompoundTag(); tag.put("properties", properties); - tag.put("id", NBT.createString(owner.value().id().toString())); + tag.put("id", NBT.createString(this.owner.value().id().asString())); return tag; } @@ -140,7 +140,7 @@ public class ImmutableBlockState extends BlockStateHolder { @SuppressWarnings("unchecked") public List> getDrops(@NotNull ContextHolder.Builder builder, @NotNull World world, @Nullable Player player) { - CustomBlock block = owner.value(); + CustomBlock block = this.owner.value(); if (block == null) return List.of(); LootTable lootTable = (LootTable) block.lootTable(); if (lootTable == null) return List.of(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java index a733f2bfd..29ee6e7a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java @@ -1,20 +1,18 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import net.momirealms.sparrow.nbt.CompoundTag; -import java.util.EnumMap; import java.util.HashMap; import java.util.Map; -public class InactiveCustomBlock extends CustomBlock { +public class InactiveCustomBlock extends AbstractCustomBlock { private final Map cachedData = new HashMap<>(); public InactiveCustomBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), new EnumMap<>(EventTrigger.class), null, null); + super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), null, null); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java b/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java index 00b9d4b49..f0174a4ad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java @@ -24,7 +24,7 @@ public class UnsafeBlockStateMatcher { } public boolean matches(ImmutableBlockState state) { - if (!state.owner().value().id.equals(this.id)) { + if (!state.owner().value().id().equals(this.id)) { return false; } CustomBlock customBlock = state.owner().value(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlock.java new file mode 100644 index 000000000..1ce3a53ef --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlock.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.block; + +import net.momirealms.craftengine.core.util.Key; + +public record VanillaBlock(Key type, String properties, int registryId) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java index d75ffc494..610dbb221 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java @@ -30,7 +30,7 @@ public class BlockBehaviors { Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); BlockBehaviorFactory factory = BuiltInRegistries.BLOCK_BEHAVIOR_FACTORY.getValue(key); if (factory == null) { - throw new LocalizedResourceConfigException("warning.config.block.behavior.invalid_type", type.toString()); + throw new LocalizedResourceConfigException("warning.config.block.behavior.invalid_type", type); } return factory.create(block, map); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java index aa1cdf921..925b2bb87 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java @@ -5,9 +5,11 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import software.amazon.awssdk.services.s3.endpoints.internal.Value; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class LegacyItemModel { private final List modelsToGenerate; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java index 242149a45..010098123 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java @@ -10,7 +10,10 @@ import net.momirealms.craftengine.core.plugin.context.text.TextProvider; import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.plugin.gui.GuiType; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.EnumUtils; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import javax.annotation.Nullable; import java.util.List; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java index 1cc08eb6c..801e13667 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -6,8 +6,10 @@ import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventFunctions; -import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; import java.util.ArrayList; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java index 7dcb40ce1..6245e85d6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java @@ -8,6 +8,7 @@ import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Map; public class GsonHelper { @@ -101,4 +102,16 @@ public class GsonHelper { throw new RuntimeException("Invalid JSON response: " + json, e); } } + + public static JsonElement combine(List jo) { + if (jo.size() == 1) { + return jo.get(0); + } else { + JsonArray ja = new JsonArray(); + for (JsonElement je : jo) { + ja.add(je); + } + return ja; + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java index fcceb235b..a60be053e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java @@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java new file mode 100644 index 000000000..7c8ef0ae0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.core.util; + +import java.util.Collections; +import java.util.List; + +public final class ListUtils { + + private ListUtils() {} + + public static List compact(final List list) { + if (list.isEmpty()) return Collections.emptyList(); + if (list.size() == 1) return List.of(list.get(0)); + if (list.size() == 2) return List.of(list.get(0), list.get(1)); + return list; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index b81e56ca0..0c0407385 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.util; +import com.mojang.datafixers.util.Either; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; public final class ResourceConfigUtils { @@ -36,6 +41,50 @@ public final class ResourceConfigUtils { return s; } + @SuppressWarnings("unchecked") + public static Either> parseConfigAsEither(Object config, Function, T> converter) { + if (config instanceof Map) { + return Either.left(converter.apply((Map) config)); + } else if (config instanceof List list) { + return switch (list.size()) { + case 0 -> Either.right(Collections.emptyList()); + case 1 -> Either.left(converter.apply((Map) list.get(0))); + case 2 -> Either.right(List.of(converter.apply((Map) list.get(0)), converter.apply((Map) list.get(1)))); + default -> { + List result = new ArrayList<>(list.size()); + for (Object o : list) { + result.add(converter.apply((Map) o)); + } + yield Either.right(result); + } + }; + } else { + return Either.right(Collections.emptyList()); + } + } + + @SuppressWarnings("unchecked") + public static List parseConfigAsList(Object config, Function, T> converter) { + if (config instanceof Map) { + return List.of(converter.apply((Map) config)); + } else if (config instanceof List list) { + return switch (list.size()) { + case 0 -> Collections.emptyList(); + case 1 -> List.of(converter.apply((Map) list.get(0))); + case 2 -> List.of(converter.apply((Map) list.get(0)), converter.apply((Map) list.get(1))); + default -> { + List result = new ArrayList<>(list.size()); + for (Object o : list) { + result.add(converter.apply((Map) o)); + } + yield result; + } + }; + } else { + return Collections.emptyList(); + } + } + public static Object get(Map arguments, String... keys) { for (String key : keys) { Object value = arguments.get(key);