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-17 17:51:57 +08:00
parent e7ca6a8a3c
commit 90d67b09aa
17 changed files with 271 additions and 24 deletions

View File

@@ -1,18 +1,24 @@
package net.momirealms.craftengine.core.block;
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 net.momirealms.craftengine.core.block.properties.Properties;
import net.momirealms.craftengine.core.block.properties.Property;
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.AbstractModelGenerator;
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.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import org.incendo.cloud.suggestion.Suggestion;
import org.jetbrains.annotations.NotNull;
@@ -41,6 +47,9 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 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 AbstractBlockManager(CraftEngine plugin) {
super(plugin);
@@ -139,6 +148,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
protected abstract int getBlockRegistryId(Key id);
public abstract String stateRegistryIdToStateSNBT(int id);
public class BlockParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[]{"blocks", "block"};
@@ -179,7 +190,161 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
private void parseCustomBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
// 获取方块设置
BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true));
//
// 读取基础外观配置
Map<String, Property<?>> properties;
Map<String, Integer> appearances;
Map<String, BlockStateVariant> variants;
// 读取states区域
Map<String, Object> stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(
ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true);
boolean singleState = !stateSection.containsKey("properties");
// 单方块状态
if (singleState) {
int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(
stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id");
// 获取原版外观的注册表id
int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow(
stateSection.get("state"), "warning.config.block.state.missing_state"));
// 为原版外观赋予外观模型并检查模型冲突
this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(stateSection, "model", "models"));
// 设置参数
properties = Map.of();
appearances = Map.of("", appearanceId);
variants = Map.of("", new BlockStateVariant("", settings, internalId));
}
// 多方块状态
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"));
}
}
private Map<String, Integer> parseBlockAppearances(Map<String, Object> appearancesSection) {
Map<String, Integer> appearances = new HashMap<>();
for (Map.Entry<String, Object> entry : appearancesSection.entrySet()) {
Map<String, Object> appearanceSection = ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey());
int appearanceId = pluginFormattedBlockStateToRegistryId(ResourceConfigUtils.requireNonEmptyStringOrThrow(
appearanceSection.get("state"), "warning.config.block.state.missing_state"));
this.arrangeModelForStateAndVerify(appearanceId, ResourceConfigUtils.get(appearanceSection, "model", "models"));
appearances.put(entry.getKey(), appearanceId);
}
return appearances;
}
@NotNull
private Map<String, Property<?>> parseBlockProperties(Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {
Property<?> property = Properties.fromMap(entry.getKey(), ResourceConfigUtils.getAsMap(entry.getValue(), entry.getKey()));
properties.put(entry.getKey(), property);
}
return properties;
}
private void arrangeModelForStateAndVerify(int registryId, Object modelOrModels) {
// 如果没有配置models
if (modelOrModels == null) {
return;
}
// 获取variants
List<JsonObject> variants;
if (modelOrModels instanceof String model) {
JsonObject json = new JsonObject();
json.addProperty("model", model);
variants = Collections.singletonList(json);
} else {
variants = ResourceConfigUtils.parseConfigAsList(modelOrModels, this::parseAppearanceModelSectionAsJson);
if (variants.isEmpty()) {
return;
}
}
// 拆分方块id与属性
String blockState = stateRegistryIdToStateSNBT(registryId);
Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}')));
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);
if (previous != null && !previous.equals(combinedVariant)) {
// todo 播报可能的冲突
plugin.logger().warn("warning 1");
}
}
private JsonObject parseAppearanceModelSectionAsJson(Map<String, Object> section) {
JsonObject json = new JsonObject();
String modelPath = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.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 (section.containsKey("x"))
json.addProperty("x", ResourceConfigUtils.getAsInt(section.get("x"), "x"));
if (section.containsKey("y"))
json.addProperty("y", ResourceConfigUtils.getAsInt(section.get("y"), "y"));
if (section.containsKey("uvlock")) json.addProperty("uvlock", ResourceConfigUtils.getAsBoolean(section.get("uvlock"), "uvlock"));
if (section.containsKey("weight"))
json.addProperty("weight", ResourceConfigUtils.getAsInt(section.get("weight"), "weight"));
Map<String, Object> generationMap = MiscUtils.castToMap(section.get("generation"), true);
if (generationMap != null) {
prepareModelGeneration(ModelGeneration.of(Key.of(modelPath), generationMap));
}
return json;
}
// 从方块外观的state里获取其原版方块的state id
private int pluginFormattedBlockStateToRegistryId(String blockState) {
// 五种合理情况
// minecraft:note_block:10
// note_block:10
// minecraft:note_block[xxx=xxx]
// note_block[xxx=xxx]
// minecraft:barrier
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 = false;
int arrangerIndex = 0;
try {
arrangerIndex = Integer.parseInt(stateOrId);
if (arrangerIndex < 0) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
isId = true;
} catch (NumberFormatException ignored) {
}
// 如果末尾是id则至少长度为2
if (isId) {
if (split.length == 1) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
// 获取原版方块的id
Key block = split.length == 2 ? Key.of(split[0]) : Key.of(split[0], split[1]);
try {
List<Integer> arranger = blockAppearanceArranger.get(block);
if (arranger == null) {
throw new LocalizedResourceConfigException("warning.config.block.state.unavailable_vanilla", blockState);
}
if (arrangerIndex >= arranger.size()) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla_id", blockState, String.valueOf(arranger.size() - 1));
}
registryId = arranger.get(arrangerIndex);
} catch (NumberFormatException e) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", e, blockState);
}
} else {
// 其他情况则是完整的方块
BlockStateWrapper packedBlockState = createPackedBlockState(blockState);
if (packedBlockState == null || !packedBlockState.isVanillaBlock()) {
throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState);
}
registryId = packedBlockState.registryId();
}
return registryId;
}
}
}

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
@@ -12,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
public class CustomNameModifier<I> implements SimpleNetworkItemDataModifier<I> {
public static final Factory<?> FACTORY = new Factory<>();
private final String argument;
private final FormattedLine line;
public CustomNameModifier(String argument) {
if (Config.addNonItalicTag()) {
@@ -23,6 +25,7 @@ public class CustomNameModifier<I> implements SimpleNetworkItemDataModifier<I> {
} else {
this.argument = argument;
}
this.line = FormattedLine.create(this.argument);
}
public String customName() {
@@ -36,7 +39,7 @@ public class CustomNameModifier<I> implements SimpleNetworkItemDataModifier<I> {
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.customNameComponent(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers()));
item.customNameComponent(this.line.parse(context));
return item;
}

View File

@@ -1,9 +1,9 @@
package net.momirealms.craftengine.core.item.modifier;
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.item.modifier.lore.OverwritableLoreModifier;
import net.momirealms.craftengine.core.item.modifier.lore.DynamicLoreModifier;
import net.momirealms.craftengine.core.item.modifier.lore.LoreModifier;
import net.momirealms.craftengine.core.item.modifier.lore.OverwritableLoreModifier;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Registries;
import net.momirealms.craftengine.core.registry.WritableRegistry;

View File

@@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
@@ -11,9 +12,11 @@ import org.jetbrains.annotations.Nullable;
public class ItemNameModifier<I> implements SimpleNetworkItemDataModifier<I> {
public static final Factory<?> FACTORY = new Factory<>();
private final String argument;
private final FormattedLine line;
public ItemNameModifier(String argument) {
this.argument = argument;
this.line = FormattedLine.create(argument);
}
public String itemName() {
@@ -27,7 +30,7 @@ public class ItemNameModifier<I> implements SimpleNetworkItemDataModifier<I> {
@Override
public Item<I> apply(Item<I> item, ItemBuildContext context) {
item.itemNameComponent(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers()));
item.itemNameComponent(this.line.parse(context));
return item;
}

View File

@@ -2,28 +2,28 @@ package net.momirealms.craftengine.core.item.modifier.lore;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.TriFunction;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// todo 可以考虑未来添加条件系统
public record LoreModification(Operation operation, boolean split, String[] content) {
public record LoreModification(Operation operation, boolean split, FormattedLine[] content) {
public Stream<Component> apply(Stream<Component> lore, ItemBuildContext context) {
return this.operation.function.apply(lore, context, this);
}
public Stream<Component> parseAsStream(ItemBuildContext context) {
Stream<Component> parsed = Arrays.stream(this.content).map(string -> AdventureHelper.miniMessage().deserialize(string, context.tagResolvers()));
Stream<Component> parsed = Arrays.stream(this.content).map(line -> line.parse(context));
return this.split ? parsed.map(AdventureHelper::splitLines).flatMap(List::stream) : parsed;
}
public List<Component> parseAsList(ItemBuildContext context) {
return this.parseAsStream(context).collect(Collectors.toList());
return this.parseAsStream(context).toList();
}
public enum Operation {

View File

@@ -8,6 +8,8 @@ import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifiers;
import net.momirealms.craftengine.core.item.modifier.SimpleNetworkItemDataModifier;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.text.minimessage.FormattedLine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
@@ -63,7 +65,9 @@ public sealed interface LoreModifier<I> extends SimpleNetworkItemDataModifier<I>
rawLore[i] = o.toString();
}
}
return new SingleLoreModifier<>(new LoreModification(LoreModification.Operation.APPEND, false, rawLore));
return new SingleLoreModifier<>(new LoreModification(LoreModification.Operation.APPEND, false,
Arrays.stream(rawLore).map(line -> Config.addNonItalicTag() ? FormattedLine.create("<!i>" + line) : FormattedLine.create(line))
.toArray(FormattedLine[]::new)));
}
List<LoreModificationHolder> modifications = new ArrayList<>(rawLoreData.size() + 1);
@@ -74,7 +78,9 @@ public sealed interface LoreModifier<I> extends SimpleNetworkItemDataModifier<I>
LoreModification.Operation operation = ResourceConfigUtils.getAsEnum(Optional.ofNullable(complexLore.get("operation")).map(String::valueOf).orElse(null), LoreModification.Operation.class, LoreModification.Operation.APPEND);
lastPriority = Optional.ofNullable(complexLore.get("priority")).map(it -> ResourceConfigUtils.getAsInt(it, "priority")).orElse(lastPriority);
boolean split = ResourceConfigUtils.getAsBoolean(complexLore.get("split-lines"), "split-lines");
modifications.add(new LoreModificationHolder(new LoreModification(operation, split, content), lastPriority));
modifications.add(new LoreModificationHolder(new LoreModification(operation, split,
Arrays.stream(content).map(line -> Config.addNonItalicTag() ? FormattedLine.create("<!i>" + line) : FormattedLine.create(line))
.toArray(FormattedLine[]::new)), lastPriority));
}
}
modifications.sort(LoreModificationHolder::compareTo);

View File

@@ -0,0 +1,75 @@
package net.momirealms.craftengine.core.plugin.text.minimessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.momirealms.craftengine.core.util.AdventureHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface FormattedLine {
TagResolver[] CUSTOM_RESOLVERS = new TagResolver[]{
createDummyResolvers("expr"),
createDummyResolvers("image"),
createDummyResolvers("arg"),
createDummyResolvers("shift"),
createDummyResolvers("i18n"),
createDummyResolvers("global"),
createDummyResolvers("papi"),
createDummyResolvers("rel_papi")
};
Component parse(net.momirealms.craftengine.core.plugin.context.Context context);
private static TagResolver createDummyResolvers(String tag) {
return new TagResolver() {
@Override
public boolean has(@NotNull String name) {
return tag.equals(name);
}
@Override
public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException {
return null;
}
};
}
static FormattedLine create(String line) {
if (line.equals(AdventureHelper.customMiniMessage().stripTags(line, CUSTOM_RESOLVERS))) {
return new PreParsedLine(AdventureHelper.miniMessage().deserialize(line));
} else {
return new DynamicLine(line);
}
}
class PreParsedLine implements FormattedLine {
private final Component parsed;
public PreParsedLine(Component parsed) {
this.parsed = parsed;
}
@Override
public Component parse(net.momirealms.craftengine.core.plugin.context.Context context) {
return this.parsed;
}
}
class DynamicLine implements FormattedLine {
private final String content;
public DynamicLine(String content) {
this.content = content;
}
@Override
public Component parse(net.momirealms.craftengine.core.plugin.context.Context context) {
return AdventureHelper.miniMessage().deserialize(this.content, context.tagResolvers());
}
}
}