9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 19:39:11 +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> {