9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-31 04:46:37 +00:00

重构配置读取,允许状态多绑一

This commit is contained in:
XiaoMoMi
2025-08-20 21:38:18 +08:00
parent c54ce3b660
commit 9b3c2e767d
10 changed files with 119 additions and 282 deletions

View File

@@ -3,8 +3,6 @@ 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.JsonElement;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -26,16 +24,7 @@ import net.momirealms.craftengine.bukkit.util.TagUtils;
import net.momirealms.craftengine.core.block.*;
import net.momirealms.craftengine.core.block.behavior.EmptyBlockBehavior;
import net.momirealms.craftengine.core.block.parser.BlockStateParser;
import net.momirealms.craftengine.core.block.properties.Properties;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.config.StringKeyConstructor;
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
@@ -47,7 +36,6 @@ import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
@@ -69,14 +57,12 @@ import java.util.function.Predicate;
public final class BukkitBlockManager extends AbstractBlockManager {
private static BukkitBlockManager instance;
private final BukkitCraftEngine plugin;
private final BlockParser blockParser;
// The total amount of blocks registered
private int customBlockCount;
private ImmutableBlockState[] stateId2ImmutableBlockStates;
// Minecraft objects
// Cached new blocks $ holders
private Map<Key, Integer> internalId2StateId;
private Map<Key, DelegatingBlock> registeredBlocks;
private Map<Integer, Object> stateId2BlockHolder;
// This map is used to change the block states that are not necessarily needed into a certain block state
private Map<Integer, Integer> blockAppearanceMapper;
@@ -100,7 +86,6 @@ public final class BukkitBlockManager extends AbstractBlockManager {
super(plugin);
instance = this;
this.plugin = plugin;
this.blockParser = new BlockParser();
this.initVanillaRegistry();
this.loadMappingsAndAdditionalBlocks();
this.registerBlocks();
@@ -230,12 +215,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
}
@Override
public ConfigParser parser() {
return this.blockParser;
}
@Override
public void addBlock(Key id, CustomBlock customBlock) {
public void addBlockInternal(Key id, CustomBlock customBlock) {
// bind appearance and real state
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()];
@@ -246,7 +226,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
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);
super.addBlockInternal(id, customBlock);
}
@Override
@@ -254,6 +234,11 @@ public final class BukkitBlockManager extends AbstractBlockManager {
return BlockStateUtils.getBlockOwnerIdFromState(state.handle());
}
@Override
protected Key getBlockOwnerId(int id) {
return BlockStateUtils.getBlockOwnerIdFromState(BlockStateUtils.idToBlockState(id));
}
@Override
public int availableAppearances(Key blockType) {
return Optional.ofNullable(this.registeredRealBlockSlots.get(blockType)).orElse(0);
@@ -300,6 +285,11 @@ public final class BukkitBlockManager extends AbstractBlockManager {
BlockStateUtils.init(vanillaStateCount);
}
@Override
protected CustomBlock.Builder platformBuilder(Key id) {
return BukkitCustomBlock.builder(id);
}
@SuppressWarnings("unchecked")
private void registerBlocks() {
this.plugin.logger().info("Registering blocks. Please wait...");
@@ -406,225 +396,6 @@ public final class BukkitBlockManager extends AbstractBlockManager {
return cachedUpdateTagsPacket;
}
public class BlockParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.BLOCK;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (id.namespace().equals("minecraft") && Registry.MATERIAL.get(KeyUtils.toNamespacedKey(id)) != null) {
parseVanillaBlock(pack, path, id, section);
} else {
// check duplicated config
if (BukkitBlockManager.this.byId.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.block.duplicate");
}
parseCustomBlock(pack, path, id, section);
}
}
private void parseCustomBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
// read block settings
BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true));
// read states
Map<String, Property<?>> properties;
Map<String, Integer> appearances;
Map<String, BlockStateVariant> variants;
Map<String, Object> stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true);
boolean singleState = !stateSection.containsKey("properties");
// single state
if (singleState) {
properties = Map.of();
int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id");
VanillaBlockState 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", internalBlockId.toString(), String.valueOf(availableAppearances(vanillaBlock.type()) - 1));
}
variants = Map.of("", new BlockStateVariant("", settings, internalBlockRegistryId));
} else {
// properties
properties = getProperties(MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), true));
// appearance
appearances = new HashMap<>();
Map<String, Key> appearance2BlockType = new HashMap<>();
for (Map.Entry<String, Object> appearanceEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), false).entrySet()) {
if (appearanceEntry.getValue() instanceof Map<?, ?>) {
VanillaBlockState vanillaBlock = getVanillaBlock(id, MiscUtils.castToMap(appearanceEntry.getValue(), false));
appearances.put(appearanceEntry.getKey(), vanillaBlock.registryId());
appearance2BlockType.put(appearanceEntry.getKey(), vanillaBlock.type());
}
}
// variants
variants = new HashMap<>();
for (Map.Entry<String, Object> variantEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), false).entrySet()) {
if (variantEntry.getValue() instanceof Map<?, ?>) {
Map<String, Object> 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", variantNBT, 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(availableAppearances(baseBlock) - 1));
}
Map<String, Object> anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true);
variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId));
}
}
}
CustomBlock block = BukkitCustomBlock.builder(id)
.appearances(appearances)
.variantMapper(variants)
.properties(properties)
.settings(settings)
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
.behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")))
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.build();
addBlock(id, block);
}
private void parseVanillaBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), true);
if (settings != null) {
Object clientBoundTags = settings.get("client-bound-tags");
if (clientBoundTags instanceof List<?> list) {
List<String> clientSideTags = MiscUtils.getAsStringList(list).stream().filter(ResourceLocation::isValid).toList();
Object nmsBlock = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id));
FastNMS.INSTANCE.method$IdMap$getId(MBuiltInRegistries.BLOCK, nmsBlock).ifPresent(i ->
BukkitBlockManager.this.clientBoundTags.put(i, clientSideTags));
}
}
}
}
@NotNull
private Map<String, Property<?>> getProperties(Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
Property<?> property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(entry.getValue(), false));
properties.put(entry.getKey(), property);
}
return properties;
}
@NotNull
private VanillaBlockState getVanillaBlock(Key id, Map<String, Object> section) {
// require state non null
String vanillaBlockStateTag = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("state"), "warning.config.block.state.missing_state");
// get its registry id
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(vanillaBlockStateRegistryId)).getAsString(), ifAny.toString());
}
// require models not to be null
Object models = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "models", "model"), "warning.config.block.state.missing_model");
List<JsonObject> variants = ResourceConfigUtils.parseConfigAsList(models, this::getVariantModel);
if (variants.isEmpty()) {
throw new LocalizedResourceConfigException("warning.config.block.state.missing_model");
}
// 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 VanillaBlockState(blockId, propertyNBT, vanillaBlockStateRegistryId);
}
public record VanillaBlockState(Key type, String properties, int registryId) {
}
private JsonObject getVariantModel(Map<String, Object> singleModelMap) {
JsonObject json = new JsonObject();
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);
}
json.addProperty("model", modelPath);
if (singleModelMap.containsKey("x"))
json.addProperty("x", ResourceConfigUtils.getAsInt(singleModelMap.get("x"), "x"));
if (singleModelMap.containsKey("y"))
json.addProperty("y", ResourceConfigUtils.getAsInt(singleModelMap.get("y"), "y"));
if (singleModelMap.containsKey("uvlock")) json.addProperty("uvlock", ResourceConfigUtils.getAsBoolean(singleModelMap.get("uvlock"), "uvlock"));
if (singleModelMap.containsKey("weight"))
json.addProperty("weight", ResourceConfigUtils.getAsInt(singleModelMap.get("weight"), "weight"));
Map<String, Object> generationMap = MiscUtils.castToMap(singleModelMap.get("generation"), true);
if (generationMap != null) {
prepareModelGeneration(ModelGeneration.of(Key.of(modelPath), generationMap));
}
return json;
}
private int getVanillaBlockStateRegistryId(String blockState) {
String[] split = blockState.split(":", 3);
if (split.length >= 4) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
int registryId;
String stateOrId = split[split.length - 1];
boolean isId = !stateOrId.contains("[") && !stateOrId.contains("]");
if (isId) {
if (split.length == 1) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
try {
int id = split.length == 2 ? Integer.parseInt(split[1]) : Integer.parseInt(split[2]);
if (id < 0) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
List<Integer> arranger = this.blockAppearanceArranger.get(block);
if (arranger == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState);
}
if (id >= arranger.size()) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla_id", blockState, String.valueOf(arranger.size() - 1));
}
registryId = arranger.get(id);
} catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", e, blockState);
}
} else {
try {
BlockData blockData = Bukkit.createBlockData(blockState);
registryId = BlockStateUtils.blockDataToId(blockData);
if (!this.blockAppearanceMapper.containsKey(registryId)) {
throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState);
}
} catch (IllegalArgumentException e) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", e, blockState);
}
}
return registryId;
}
private void loadMappingsAndAdditionalBlocks() {
this.plugin.logger().info("Loading mappings.yml.");
Path mappingsFile = this.plugin.dataFolderPath().resolve("mappings.yml");
@@ -762,13 +533,19 @@ public final class BukkitBlockManager extends AbstractBlockManager {
}
private LinkedHashMap<Key, Integer> buildRegisteredRealBlockSlots(Map<Key, Integer> counter, Map<String, Object> additionalYaml) {
LinkedHashMap<Key, Integer> map = new LinkedHashMap<>();
for (Map.Entry<Key, Integer> entry : counter.entrySet()) {
String id = entry.getKey().toString();
int additionalStates = (int) additionalYaml.getOrDefault(id, 0);
int internalIds = entry.getValue() + additionalStates;
plugin.logger().info("Loaded " + id + " with " + entry.getValue() + " appearances and " + internalIds + " real block states");
map.put(entry.getKey(), internalIds);
LinkedHashMap<Key, Integer> map = new LinkedHashMap<>(counter);
for (Map.Entry<String, Object> entry : additionalYaml.entrySet()) {
Key blockType = Key.of(entry.getKey());
if (entry.getValue() instanceof Integer i) {
int previous = map.getOrDefault(blockType, 0);
if (previous == 0) {
map.put(blockType, i);
this.plugin.logger().info("Loaded " + blockType + " with " + i + " real block states");
} else {
map.put(blockType, i + previous);
this.plugin.logger().info("Loaded " + blockType + " with " + previous + " appearances and " + (i + previous) + " real block states");
}
}
}
return map;
}

View File

@@ -2,27 +2,14 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.parser.BlockStateParser;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Minecart;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.standard.IntegerParser;
import org.incendo.cloud.parser.standard.StringParser;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class TestCommand extends BukkitCommandFeature<CommandSender> {

View File

@@ -267,6 +267,7 @@ warning.config.block.state.bind_failed: "<yellow>Issue found in file <arg:0> - T
warning.config.block.state.invalid_real_id: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using a real block state '<arg:2>' that exceeds the available slot range '0~<arg:3>'. Consider adding more real states in 'additional-real-blocks.yml' if the slots are used up.</yellow>"
warning.config.block.state.model.missing_path: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'path' option for 'model'.</yellow>"
warning.config.block.state.model.invalid_path: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' has a 'path' argument '<arg:2>' that contains illegal characters. Please read https://minecraft.wiki/w/Resource_location#Legal_characters.</yellow>"
warning.config.block.state.model.conflict: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is trying to bind model '<arg:2>' to block state '<arg:3>' which has already been bound to model '<arg:4>'</yellow>"
warning.config.block.settings.unknown: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an unknown setting type '<arg:2>'.</yellow>"
warning.config.block.behavior.missing_type: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'type' argument for its block behavior.</yellow>"
warning.config.block.behavior.invalid_type: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is using an invalid block behavior type '<arg:2>'.</yellow>"

View File

@@ -267,6 +267,7 @@ warning.config.block.state.bind_failed: "<yellow>在文件 <arg:0> 发现问题
warning.config.block.state.invalid_real_id: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 使用的真实方块状态 '<arg:2>' 超出可用槽位范围 '0~<arg:3>' 如果槽位已用尽 请在 additional-real-blocks.yml 中添加更多真实状态</yellow>"
warning.config.block.state.model.missing_path: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'model' 缺少必需的 'path' 选项</yellow>"
warning.config.block.state.model.invalid_path: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'path' 参数 '<arg:2>' 包含非法字符 请参考 https://zh.minecraft.wiki/w/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4ID#%E5%90%88%E6%B3%95%E5%AD%97%E7%AC%A6</yellow>"
warning.config.block.state.model.conflict: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 正尝试将模型 '<arg:2>' 绑定到方块状态 '<arg:3>' 上, 但是此状态已绑定了另一个模型 '<arg:4>'</yellow>"
warning.config.block.settings.unknown: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 使用了未知的设置类型 '<arg:2>'</yellow>"
warning.config.block.behavior.missing_type: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的行为配置缺少必需的 'type' 参数</yellow>"
warning.config.block.behavior.invalid_type: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 使用了无效的行为类型 '<arg:2>'</yellow>"

View File

@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.momirealms.craftengine.core.block.properties.Properties;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.loot.LootTable;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.pack.ResourceLocation;
@@ -14,6 +15,7 @@ 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.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
@@ -24,8 +26,10 @@ import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager {
protected final BlockParser blockParser;
// CraftEngine objects
protected final Map<Key, CustomBlock> byId = new HashMap<>();
// Cached command suggestions
@@ -44,15 +48,19 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
protected final Map<Key, Map<String, JsonElement>> blockStateOverrides = new HashMap<>();
// a reverted mapper
protected final Map<Integer, List<Integer>> appearanceToRealState = new Int2ObjectOpenHashMap<>();
// client side block tags
protected Map<Integer, List<String>> clientBoundTags = Map.of();
protected Map<Integer, List<String>> previousClientBoundTags = Map.of();
// Used to automatically arrange block states for strings such as minecraft:note_block:0
protected Map<Key, List<Integer>> blockAppearanceArranger;
protected Map<Key, List<Integer>> realBlockArranger;
protected Map<Key, Integer> internalId2StateId;
protected Map<Key, DelegatingBlock> registeredBlocks;
protected AbstractBlockManager(CraftEngine plugin) {
super(plugin);
this.blockParser = new BlockParser();
}
@Override
@@ -85,8 +93,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
return Optional.ofNullable(this.byId.get(id));
}
@Override
public void addBlock(Key id, CustomBlock customBlock) {
protected void addBlockInternal(Key id, CustomBlock customBlock) {
this.byId.put(id, customBlock);
// generate mod assets
if (Config.generateModAssets()) {
@@ -96,6 +103,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
}
@Override
public ConfigParser parser() {
return this.blockParser;
}
@Override
public Map<Key, JsonElement> modBlockStates() {
return Collections.unmodifiableMap(this.modBlockStates);
@@ -148,7 +160,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
protected abstract int getBlockRegistryId(Key id);
public abstract String stateRegistryIdToStateSNBT(int id);
protected abstract String stateRegistryIdToStateSNBT(int id);
protected abstract Key getBlockOwnerId(int id);
protected abstract CustomBlock.Builder platformBuilder(Key id);
public class BlockParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
@@ -210,14 +226,56 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 设置参数
properties = Map.of();
appearances = Map.of("", appearanceId);
variants = Map.of("", new BlockStateVariant("", settings, internalId));
variants = Map.of("", new BlockStateVariant("", settings, getInternalBlockId(internalId, appearanceId)));
}
// 多方块状态
else {
properties = parseBlockProperties(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), "properties"));
appearances = parseBlockAppearances(ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), "appearances"));
variants = parseBlockVariants(
ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), "variants"),
it -> appearances.getOrDefault(it, -1), settings
);
}
addBlockInternal(id, platformBuilder(id)
.appearances(appearances)
.variantMapper(variants)
.properties(properties)
.settings(settings)
.lootTable(LootTable.fromMap(ResourceConfigUtils.getAsMapOrNull(section.get("loot"), "loot")))
.behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")))
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.build());
}
private Map<String, BlockStateVariant> parseBlockVariants(Map<String, Object> variantsSection,
Function<String, Integer> appearanceValidator,
BlockSettings parentSettings) {
Map<String, BlockStateVariant> variants = new HashMap<>();
for (Map.Entry<String, Object> entry : variantsSection.entrySet()) {
Map<String, Object> variantSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
String variantNBT = entry.getKey();
String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance");
int appearanceId = appearanceValidator.apply(appearance);
if (appearanceId == -1) {
throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance);
}
int internalId = getInternalBlockId(ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id"), appearanceId);
Map<String, Object> anotherSetting = ResourceConfigUtils.getAsMapOrNull(variantSection.get("settings"), "settings");
variants.put(variantNBT, new BlockStateVariant(appearance, anotherSetting == null ? parentSettings : BlockSettings.ofFullCopy(parentSettings, anotherSetting), internalId));
}
return variants;
}
private int getInternalBlockId(int internalId, int appearanceId) {
Key baseBlock = getBlockOwnerId(appearanceId);
Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, baseBlock.value() + "_" + internalId);
int internalBlockRegistryId = Optional.ofNullable(AbstractBlockManager.this.internalId2StateId.get(internalBlockId)).orElse(-1);
if (internalBlockRegistryId == -1) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(baseBlock) - 1));
}
return internalBlockRegistryId;
}
private Map<String, Integer> parseBlockAppearances(Map<String, Object> appearancesSection) {
@@ -265,11 +323,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']'));
// 结合variants
JsonElement combinedVariant = GsonHelper.combine(variants);
JsonElement previous = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()).put(propertyNBT, combinedVariant);
Map<String, JsonElement> overrideMap = AbstractBlockManager.this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>());
JsonElement previous = overrideMap.get(propertyNBT);
if (previous != null && !previous.equals(combinedVariant)) {
// todo 播报可能的冲突
plugin.logger().warn("warning 1");
throw new LocalizedResourceConfigException("warning.config.block.state.model.conflict", GsonHelper.get().toJson(combinedVariant), blockState, GsonHelper.get().toJson(previous));
}
overrideMap.put(propertyNBT, combinedVariant);
}
private JsonObject parseAppearanceModelSectionAsJson(Map<String, Object> section) {

View File

@@ -64,6 +64,10 @@ public abstract class AbstractCustomBlock implements CustomBlock {
if (tag == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString);
}
List<ImmutableBlockState> possibleStates = this.getPossibleStates(tag);
if (possibleStates.size() != 1) {
throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString);
}
BlockStateVariant blockStateVariant = entry.getValue();
int vanillaStateRegistryId = appearances.getOrDefault(blockStateVariant.appearance(), -1);
// This should never happen
@@ -71,15 +75,14 @@ public abstract class AbstractCustomBlock implements CustomBlock {
vanillaStateRegistryId = appearances.values().iterator().next();
}
// Late init states
for (ImmutableBlockState state : this.getPossibleStates(tag)) {
state.setBehavior(this.behavior);
state.setSettings(blockStateVariant.settings());
state.setVanillaBlockState((BlockStateWrapper.VanillaBlockState) BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId));
state.setCustomBlockState((BlockStateWrapper.CustomBlockState) BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId()));
}
ImmutableBlockState state = possibleStates.getFirst();
state.setSettings(blockStateVariant.settings());
state.setVanillaBlockState((BlockStateWrapper.VanillaBlockState) BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId));
state.setCustomBlockState((BlockStateWrapper.CustomBlockState) BlockRegistryMirror.stateByRegistryId(blockStateVariant.internalRegistryId()));
}
// double check if there's any invalid state
for (ImmutableBlockState state : this.variantProvider().states()) {
state.setBehavior(this.behavior);
if (state.settings() == null) {
state.setSettings(settings);
}

View File

@@ -28,8 +28,6 @@ public interface BlockManager extends Manageable, ModelGenerator {
Optional<CustomBlock> blockById(Key key);
void addBlock(Key id, CustomBlock customBlock);
Collection<Suggestion> cachedSuggestions();
Map<Key, Key> soundMapper();

View File

@@ -6,13 +6,10 @@ import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import java.util.List;
import java.util.Map;
import java.util.Optional;

View File

@@ -212,4 +212,15 @@ public final class ResourceConfigUtils {
}
throw new LocalizedResourceConfigException("warning.config.type.map", String.valueOf(obj), option);
}
@SuppressWarnings("unchecked")
public static Map<String, Object> getAsMapOrNull(Object obj, String option) {
if (obj == null) {
return null;
}
if (obj instanceof Map<?, ?> map) {
return (Map<String, Object>) map;
}
throw new LocalizedResourceConfigException("warning.config.type.map", String.valueOf(obj), option);
}
}

View File

@@ -16,7 +16,10 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.stream.LongStream;
public class PalettedContainer<T> implements PaletteResizeListener<T>, ReadableContainer<T> {