9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-30 20:39:10 +00:00

重构物品配置读取

This commit is contained in:
XiaoMoMi
2025-05-18 05:23:59 +08:00
parent ca9ab2ca12
commit eba3188c82
11 changed files with 503 additions and 482 deletions

View File

@@ -2,13 +2,14 @@ package net.momirealms.craftengine.bukkit.item;
import com.google.common.collect.ImmutableMap;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
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.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@@ -17,7 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
public class BukkitCustomItem implements CustomItem<ItemStack> {
private final Key id;
private final Holder<Key> id;
private final Key materialKey;
private final Material material;
private final ItemDataModifier<ItemStack>[] modifiers;
@@ -30,7 +31,7 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
private final EnumMap<EventTrigger, List<Function<PlayerOptionalContext>>> events;
@SuppressWarnings("unchecked")
public BukkitCustomItem(Key id,
public BukkitCustomItem(Holder<Key> id,
Key materialKey,
Material material,
List<ItemDataModifier<ItemStack>> modifiers,
@@ -78,6 +79,11 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public Key id() {
return this.id.value();
}
@Override
public Holder<Key> idHolder() {
return this.id;
}
@@ -147,22 +153,27 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
return this.behaviors;
}
public static Builder<ItemStack> builder() {
return new BuilderImpl();
public static Builder<ItemStack> builder(Material material) {
return new BuilderImpl(material);
}
public static class BuilderImpl implements Builder<ItemStack> {
private Key id;
private Material material;
private Holder<Key> id;
private Key materialKey;
private ItemSettings settings;
private EnumMap<EventTrigger, List<Function<PlayerOptionalContext>>> events = new EnumMap<>(EventTrigger.class);
private final Material material;
private final EnumMap<EventTrigger, List<Function<PlayerOptionalContext>>> events = new EnumMap<>(EventTrigger.class);
private final List<ItemBehavior> behaviors = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> modifiers = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> clientBoundModifiers = new ArrayList<>();
private ItemSettings settings;
public BuilderImpl(Material material) {
this.material = material;
this.materialKey = KeyUtils.namespacedKey2Key(material.getKey());
}
@Override
public Builder<ItemStack> id(Key id) {
public Builder<ItemStack> id(Holder<Key> id) {
this.id = id;
return this;
}
@@ -170,7 +181,6 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public Builder<ItemStack> material(Key material) {
this.materialKey = material;
this.material = MaterialUtils.getMaterial(material.value());
return this;
}
@@ -218,14 +228,14 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public Builder<ItemStack> events(EnumMap<EventTrigger, List<Function<PlayerOptionalContext>>> events) {
this.events = events;
this.events.putAll(events);
return this;
}
@Override
public CustomItem<ItemStack> build() {
this.modifiers.addAll(this.settings.modifiers());
return new BukkitCustomItem(this.id, this.materialKey, this.material, this.modifiers, this.clientBoundModifiers, this.behaviors, this.settings, this.events);
return new BukkitCustomItem(this.id, this.materialKey, this.material, List.copyOf(this.modifiers), List.copyOf(this.clientBoundModifiers), List.copyOf(this.behaviors), this.settings, this.events);
}
}
}

View File

@@ -13,45 +13,32 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.ItemUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviors;
import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.item.modifier.ItemModelModifier;
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.misc.EquipmentGeneration;
import net.momirealms.craftengine.core.pack.model.*;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.select.ChargeTypeSelectProperty;
import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectProperty;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.event.EventFunctions;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.ResourceKey;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.type.Either;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
public class BukkitItemManager extends AbstractItemManager<ItemStack> {
@@ -68,7 +55,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
private final ItemEventListener itemEventListener;
private final DebugStickListener debugStickListener;
private final ArmorEventListener armorEventListener;
private final ItemParser itemParser;
public BukkitItemManager(BukkitCraftEngine plugin) {
super(plugin);
@@ -78,7 +65,6 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.itemEventListener = new ItemEventListener(plugin);
this.debugStickListener = new DebugStickListener(plugin);
this.armorEventListener = new ArmorEventListener();
this.itemParser = new ItemParser();
this.registerAllVanillaItems();
if (plugin.hasMod()) {
Class<?> clazz$CustomStreamCodec = ReflectionUtils.getClazz("net.momirealms.craftengine.mod.item.CustomStreamCodec");
@@ -177,11 +163,6 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return this.factory.wrap(ItemTagStream.INSTANCE.fromBytes(bytes));
}
@Override
public ConfigParser parser() {
return this.itemParser;
}
@Override
public ItemStack buildCustomItemStack(Key id, Player player) {
return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null);
@@ -238,417 +219,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.id();
}
public class ItemParser implements ConfigParser {
public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"};
@Override
public String[] sectionId() {
return CONFIG_SECTION_NAME;
}
@Override
public int loadingSequence() {
return LoadingSequence.ITEM;
}
@Override
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
if (customItems.containsKey(id)) {
throw new LocalizedResourceConfigException("warning.config.item.duplicate");
}
// register for recipes
Holder.Reference<Key> holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id)
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
boolean isVanillaItem = id.namespace().equals("minecraft") && Registry.MATERIAL.get(new NamespacedKey(id.namespace(), id.value())) != null;
String materialStringId;
if (isVanillaItem) {
materialStringId = id.value();
} else {
materialStringId = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material");
}
Material material = MaterialUtils.getMaterial(materialStringId);
if (material == null) {
throw new LocalizedResourceConfigException("warning.config.item.invalid_material", materialStringId);
}
Key materialId = Key.of(material.getKey().namespace(), material.getKey().value());
int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data");
Key itemModelKey = null;
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
boolean hasItemModelSection = section.containsKey("item-model");
// To get at least one model provider
// Sets some basic model info
if (customModelData != 0) {
itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData));
}
// Requires the item to have model before apply item-model
else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) {
// check server version here because components require 1.21.2+
// customize or use the id
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
if (ResourceLocation.isValid(itemModelKey.toString())) {
itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
} else {
itemModelKey = null;
}
}
if (hasItemModelSection) {
itemModelKey = Key.from(section.get("item-model").toString());
itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
}
// Get item behaviors
Object behaviorConfig = section.get("behavior");
if (behaviorConfig instanceof List<?>) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> behavior = (List<Map<String, Object>>) behaviorConfig;
List<ItemBehavior> behaviors = new ArrayList<>();
for (Map<String, Object> behaviorMap : behavior) {
behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap));
}
itemBuilder.behaviors(behaviors);
} else if (behaviorConfig instanceof Map<?, ?>) {
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.get("behavior"), true);
if (behaviorSection != null) {
itemBuilder.behavior(ItemBehaviors.fromMap(pack, path, id, behaviorSection));
}
}
// Get item data
Map<String, Object> dataSection = MiscUtils.castToMap(section.get("data"), true);
if (dataSection != null) {
for (Map.Entry<String, Object> dataEntry : dataSection.entrySet()) {
Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> {
try {
itemBuilder.dataModifier(function.apply(dataEntry.getValue()));
} catch (IllegalArgumentException e) {
plugin.logger().warn("Invalid data format", e);
}
});
}
}
// Add it here to make sure that ce id is always applied
if (!isVanillaItem)
itemBuilder.dataModifier(new IdModifier<>(id));
// Get item data
Map<String, Object> clientSideDataSection = MiscUtils.castToMap(section.get("client-bound-data"), true);
if (clientSideDataSection != null) {
for (Map.Entry<String, Object> dataEntry : clientSideDataSection.entrySet()) {
Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> {
try {
itemBuilder.clientBoundDataModifier(function.apply(dataEntry.getValue()));
} catch (IllegalArgumentException e) {
plugin.logger().warn("Invalid client bound data format", e);
}
});
}
}
ItemSettings itemSettings = ItemSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true));
if (isVanillaItem) {
itemSettings.canPlaceRelatedVanillaBlock(true);
}
itemBuilder.settings(itemSettings);
itemBuilder.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")));
CustomItem<ItemStack> customItem = itemBuilder.build();
customItems.put(id, customItem);
// cache command suggestions
cachedSuggestions.add(Suggestion.suggestion(id.toString()));
// TODO Deprecated 理论支持任意物品类型
if (material == Material.TOTEM_OF_UNDYING)
cachedTotemSuggestions.add(Suggestion.suggestion(id.toString()));
// post process
// register tags
Set<Key> tags = customItem.settings().tags();
for (Key tag : tags) {
customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(holder);
}
// create trims
EquipmentGeneration equipment = customItem.settings().equipment();
if (equipment != null) {
EquipmentData modern = equipment.modernData();
// 1.21.2+
if (modern != null) {
equipmentsToGenerate.add(equipment);
}
// TODO 1.20
}
// add it to category
if (section.containsKey("category")) {
plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
}
// model part, can be null
// but if it exists, either custom model data or item model should be configured
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
if (modelSection == null) {
return;
}
ItemModel model = ItemModels.fromMap(modelSection);
boolean hasModel = false;
if (customModelData != 0) {
hasModel= true;
// use custom model data
// check conflict
Map<Integer, Key> conflict = cmdConflictChecker.computeIfAbsent(materialId, k -> new HashMap<>());
if (conflict.containsKey(customModelData)) {
throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString());
}
if (customModelData > 16_777_216) {
throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData));
}
conflict.put(customModelData, id);
// Parse models
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(generation);
}
if (Config.packMaxVersion() > 21.39f) {
TreeMap<Integer, ItemModel> map = modernOverrides.computeIfAbsent(materialId, k -> new TreeMap<>());
map.put(customModelData, model);
}
if (Config.packMinVersion() < 21.39f) {
// TODO 手动指定旧版格式
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, customModelData);
TreeSet<LegacyOverridesModel> lom = legacyOverrides.computeIfAbsent(materialId, k -> new TreeSet<>());
lom.addAll(legacyOverridesModels);
}
}
if (itemModelKey != null) {
hasModel = true;
for (ModelGeneration generation : model.modelsToGenerate()) {
prepareModelGeneration(generation);
}
if (Config.packMaxVersion() > 21.39f) {
modernItemModels1_21_4.put(itemModelKey, model);
}
if (Config.packMaxVersion() > 21.19f && Config.packMinVersion() < 21.39f) {
List<LegacyOverridesModel> legacyOverridesModels = new ArrayList<>();
processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, 0);
if (!legacyOverridesModels.isEmpty()) {
legacyOverridesModels.sort(LegacyOverridesModel::compareTo);
modernItemModels1_21_2.put(itemModelKey, legacyOverridesModels);
} else {
plugin.debug(() -> "Can't convert " + id + "'s model to legacy format.");
}
}
}
if (!hasModel) {
throw new LocalizedResourceConfigException("warning.config.item.missing_model_id");
}
}
}
private void processModelRecursively(
ItemModel currentModel,
Map<String, Object> accumulatedPredicates,
List<LegacyOverridesModel> resultList,
Key materialId,
int customModelData
) {
if (currentModel instanceof ConditionItemModel conditionModel) {
handleConditionModel(conditionModel, accumulatedPredicates, resultList, materialId, customModelData);
} else if (currentModel instanceof RangeDispatchItemModel rangeModel) {
handleRangeModel(rangeModel, accumulatedPredicates, resultList, materialId, customModelData);
} else if (currentModel instanceof SelectItemModel selectModel) {
handleSelectModel(selectModel, accumulatedPredicates, resultList, materialId, customModelData);
} else if (currentModel instanceof BaseItemModel baseModel) {
resultList.add(new LegacyOverridesModel(
new LinkedHashMap<>(accumulatedPredicates),
baseModel.path(),
customModelData
));
} else if (currentModel instanceof SpecialItemModel specialModel) {
resultList.add(new LegacyOverridesModel(
new LinkedHashMap<>(accumulatedPredicates),
specialModel.base(),
customModelData
));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleConditionModel(
ConditionItemModel model,
Map<String, Object> parentPredicates,
List<LegacyOverridesModel> resultList,
Key materialId,
int customModelData
) {
if (model.property() instanceof LegacyModelPredicate predicate) {
String predicateId = predicate.legacyPredicateId(materialId);
Map<String, Object> truePredicates = mergePredicates(
parentPredicates,
predicateId,
predicate.toLegacyValue(true)
);
processModelRecursively(
model.onTrue(),
truePredicates,
resultList,
materialId,
customModelData
);
Map<String, Object> falsePredicates = mergePredicates(
parentPredicates,
predicateId,
predicate.toLegacyValue(false)
);
processModelRecursively(
model.onFalse(),
falsePredicates,
resultList,
materialId,
customModelData
);
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleRangeModel(
RangeDispatchItemModel model,
Map<String, Object> parentPredicates,
List<LegacyOverridesModel> resultList,
Key materialId,
int customModelData
) {
if (model.property() instanceof LegacyModelPredicate predicate) {
String predicateId = predicate.legacyPredicateId(materialId);
for (Map.Entry<Float, ItemModel> entry : model.entries().entrySet()) {
Map<String, Object> merged = mergePredicates(
parentPredicates,
predicateId,
predicate.toLegacyValue(entry.getKey())
);
processModelRecursively(
entry.getValue(),
merged,
resultList,
materialId,
customModelData
);
}
if (model.fallBack() != null) {
Map<String, Object> merged = mergePredicates(
parentPredicates,
predicateId,
predicate.toLegacyValue(0f)
);
processModelRecursively(
model.fallBack(),
merged,
resultList,
materialId,
customModelData
);
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void handleSelectModel(
SelectItemModel model,
Map<String, Object> parentPredicates,
List<LegacyOverridesModel> resultList,
Key materialId,
int customModelData
) {
if (model.property() instanceof LegacyModelPredicate predicate) {
String predicateId = predicate.legacyPredicateId(materialId);
for (Map.Entry<Either<String, List<String>>, ItemModel> entry : model.whenMap().entrySet()) {
List<String> cases = entry.getKey().fallbackOrMapPrimary(List::of);
for (String caseValue : cases) {
Number legacyValue = predicate.toLegacyValue(caseValue);
if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) {
if (legacyValue.floatValue() > 1f) {
continue;
}
}
Map<String, Object> merged = mergePredicates(
parentPredicates,
predicateId,
legacyValue
);
// Additional check for crossbow
if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) {
merged = mergePredicates(
merged,
"charged",
1
);
}
processModelRecursively(
entry.getValue(),
merged,
resultList,
materialId,
customModelData
);
}
}
// Additional check for crossbow
if (model.fallBack() != null) {
if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) {
processModelRecursively(
model.fallBack(),
mergePredicates(
parentPredicates,
"charged",
0
),
resultList,
materialId,
customModelData
);
} else if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) {
processModelRecursively(
model.fallBack(),
mergePredicates(
parentPredicates,
"trim_type",
0f
),
resultList,
materialId,
customModelData
);
}
}
}
}
private Map<String, Object> mergePredicates(
Map<String, Object> existing,
String newKey,
Number newValue
) {
Map<String, Object> merged = new LinkedHashMap<>(existing);
if (newKey == null) return merged;
merged.put(newKey, newValue);
return merged;
@Override
protected CustomItem.Builder<ItemStack> createPlatformItemBuilder(Key materialId) {
Material material = ResourceConfigUtils.requireNonNullOrThrow(Registry.MATERIAL.get(KeyUtils.toNamespacedKey(materialId)), () -> new LocalizedResourceConfigException("warning.config.item.invalid_material", materialId.toString()));
return BukkitCustomItem.builder(material);
}
@SuppressWarnings("unchecked")