mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-23 00:49:20 +00:00
初步分配
This commit is contained in:
@@ -120,7 +120,7 @@ public class BukkitAdvancementManager extends AbstractAdvancementManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (advancements.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.advancement.duplicate", path, id);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public class AxeItemBehavior extends ItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("block");
|
||||
if (id == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for block_item behavior"));
|
||||
@@ -242,9 +242,9 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
|
||||
if (id instanceof Map<?, ?> map) {
|
||||
if (map.containsKey(key.toString())) {
|
||||
// 防呆
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
} else {
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
|
||||
}
|
||||
return new BlockItemBehavior(key);
|
||||
} else {
|
||||
|
||||
@@ -79,7 +79,7 @@ public class CompostableItemBehavior extends ItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
double chance = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("chance", 0.55), "chance");
|
||||
return new CompostableItemBehavior(chance);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("block");
|
||||
if (id == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block", new IllegalArgumentException("Missing required parameter 'block' for double_high_block_item behavior"));
|
||||
@@ -48,9 +48,9 @@ public class DoubleHighBlockItemBehavior extends BlockItemBehavior {
|
||||
if (id instanceof Map<?, ?> map) {
|
||||
if (map.containsKey(key.toString())) {
|
||||
// 防呆
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
} else {
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
|
||||
}
|
||||
return new DoubleHighBlockItemBehavior(key);
|
||||
} else {
|
||||
|
||||
@@ -161,7 +161,7 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public class FurnitureItemBehavior extends ItemBehavior {
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("furniture");
|
||||
if (id == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.furniture.missing_furniture", new IllegalArgumentException("Missing required parameter 'furniture' for furniture_item behavior"));
|
||||
@@ -185,9 +185,9 @@ public class FurnitureItemBehavior extends ItemBehavior {
|
||||
if (id instanceof Map<?,?> map) {
|
||||
if (map.containsKey(key.toString())) {
|
||||
// 防呆
|
||||
BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
} else {
|
||||
BukkitFurnitureManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
BukkitFurnitureManager.instance().parser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
|
||||
}
|
||||
return new FurnitureItemBehavior(key);
|
||||
} else {
|
||||
|
||||
@@ -70,7 +70,7 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("block");
|
||||
if (id == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.liquid_collision.missing_block", new IllegalArgumentException("Missing required parameter 'block' for liquid_collision_block_item behavior"));
|
||||
@@ -79,9 +79,9 @@ public class LiquidCollisionBlockItemBehavior extends BlockItemBehavior {
|
||||
if (id instanceof Map<?, ?> map) {
|
||||
if (map.containsKey(key.toString())) {
|
||||
// 防呆
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
} else {
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
|
||||
}
|
||||
return new LiquidCollisionBlockItemBehavior(key, offset);
|
||||
} else {
|
||||
|
||||
@@ -35,7 +35,7 @@ public class WallBlockItemBehavior extends BlockItemBehavior {
|
||||
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key key, Map<String, Object> arguments) {
|
||||
Object id = arguments.get("block");
|
||||
if (id == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.wall_block.missing_block", new IllegalArgumentException("Missing required parameter 'block' for wall_block_item behavior"));
|
||||
@@ -43,9 +43,9 @@ public class WallBlockItemBehavior extends BlockItemBehavior {
|
||||
if (id instanceof Map<?, ?> map) {
|
||||
if (map.containsKey(key.toString())) {
|
||||
// 防呆
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map.get(key.toString()), false));
|
||||
} else {
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
|
||||
BukkitBlockManager.instance().blockParser().parseSection(pack, path, node, key, MiscUtils.castToMap(map, false));
|
||||
}
|
||||
return new WallBlockItemBehavior(key);
|
||||
} else {
|
||||
|
||||
@@ -105,7 +105,7 @@ public class BukkitVanillaLootManager extends AbstractVanillaLootManager impleme
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("type"), "warning.config.vanilla_loot.missing_type");
|
||||
VanillaLoot.Type typeEnum;
|
||||
try {
|
||||
|
||||
@@ -150,6 +150,11 @@ item:
|
||||
click-in-inventory: false # this option won't work for players in creative mode
|
||||
drop: false
|
||||
pick-up: false
|
||||
# Decided the starting value for automatic custom model data assignment.
|
||||
custom-model-data-starting-value:
|
||||
default: 10000
|
||||
overrides:
|
||||
paper: 20000
|
||||
|
||||
equipment:
|
||||
# The sacrificed-vanilla-armor argument determines which vanilla armor gets completely removed (loses all its trims)
|
||||
|
||||
@@ -20,7 +20,6 @@ import net.momirealms.craftengine.core.plugin.config.*;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -238,6 +237,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException {
|
||||
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
|
||||
for (Map.Entry<String, Object> entry : section.entrySet()) {
|
||||
String before = entry.getKey();
|
||||
String after = entry.getValue().toString();
|
||||
@@ -245,21 +245,25 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
BlockStateWrapper beforeState = createVanillaBlockState(before);
|
||||
BlockStateWrapper afterState = createVanillaBlockState(before);
|
||||
if (beforeState == null) {
|
||||
TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), before);
|
||||
return;
|
||||
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", before));
|
||||
continue;
|
||||
}
|
||||
if (afterState == null) {
|
||||
TranslationManager.instance().log("warning.config.block_state_mapping.invalid_state", path.toString(), after);
|
||||
return;
|
||||
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.invalid_state", after));
|
||||
continue;
|
||||
}
|
||||
int previous = AbstractBlockManager.this.blockStateMappings[beforeState.registryId()];
|
||||
if (previous != -1 && previous != afterState.registryId()) {
|
||||
TranslationManager.instance().log("warning.config.block_state_mapping.conflict", path.toString(), beforeState.toString(), afterState.toString(), BlockRegistryMirror.byId(previous).toString());
|
||||
return;
|
||||
exceptionCollector.add(new LocalizedResourceConfigException("warning.config.block_state_mapping.conflict",
|
||||
beforeState.toString(),
|
||||
afterState.toString(),
|
||||
BlockRegistryMirror.byId(previous).toString()));
|
||||
continue;
|
||||
}
|
||||
AbstractBlockManager.this.blockStateMappings[beforeState.registryId()] = afterState.registryId();
|
||||
AbstractBlockManager.this.blockStateArranger.computeIfAbsent(getBlockOwnerId(beforeState), k -> new ArrayList<>()).add(afterState);
|
||||
}
|
||||
exceptionCollector.throwIfPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,19 +281,19 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (isVanillaBlock(id)) {
|
||||
parseVanillaBlock(pack, path, id, section);
|
||||
parseVanillaBlock(id, section);
|
||||
} else {
|
||||
// check duplicated config
|
||||
if (AbstractBlockManager.this.byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.duplicate");
|
||||
}
|
||||
parseCustomBlock(pack, path, id, section);
|
||||
parseCustomBlock(id, section);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseVanillaBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
private void parseVanillaBlock(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");
|
||||
@@ -300,7 +304,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
}
|
||||
|
||||
private void parseCustomBlock(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
private void parseCustomBlock(Key id, Map<String, Object> section) {
|
||||
// 获取方块设置
|
||||
BlockSettings settings = BlockSettings.fromMap(id, MiscUtils.castToMap(section.get("settings"), true));
|
||||
// 读取基础外观配置
|
||||
|
||||
@@ -89,7 +89,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate");
|
||||
}
|
||||
|
||||
@@ -386,18 +386,18 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (emojis.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.duplicate", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.duplicate");
|
||||
}
|
||||
String permission = (String) section.get("permission");
|
||||
Object keywordsRaw = section.get("keywords");
|
||||
if (keywordsRaw == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords");
|
||||
}
|
||||
List<String> keywords = MiscUtils.getAsStringList(keywordsRaw);
|
||||
if (keywords.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.missing_keywords");
|
||||
}
|
||||
Object rawContent = section.getOrDefault("content", "<white><arg:emoji></white>");
|
||||
String content;
|
||||
@@ -416,7 +416,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
if (bitmapImage.isPresent()) {
|
||||
image = bitmapImage.get().miniMessageAt(0, 0);
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
|
||||
}
|
||||
} else if (split.length == 4) {
|
||||
Key imageId = new Key(split[0], split[1]);
|
||||
@@ -425,13 +425,13 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
try {
|
||||
image = bitmapImage.get().miniMessageAt(Integer.parseInt(split[2]), Integer.parseInt(split[3]));
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
|
||||
}
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
|
||||
}
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", path, id, rawImage);
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.invalid_image", rawImage);
|
||||
}
|
||||
}
|
||||
Emoji emoji = new Emoji(content, permission, image, keywords);
|
||||
@@ -453,24 +453,24 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (images.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.duplicate", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.duplicate");
|
||||
}
|
||||
|
||||
Object file = section.get("file");
|
||||
if (file == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_file", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_file");
|
||||
}
|
||||
|
||||
String resourceLocation = CharacterUtils.replaceBackslashWithSlash(file.toString());
|
||||
if (!ResourceLocation.isValid(resourceLocation)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", path, id, resourceLocation);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", resourceLocation);
|
||||
}
|
||||
|
||||
String fontName = section.getOrDefault("font", "minecraft:default").toString();
|
||||
if (!ResourceLocation.isValid(fontName)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", path, id, fontName);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", fontName);
|
||||
}
|
||||
|
||||
Key fontKey = Key.withDefaultNamespace(fontName, id.namespace());
|
||||
@@ -478,7 +478,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
List<char[]> chars;
|
||||
Object charsObj = ResourceConfigUtils.get(section, "chars", "char");
|
||||
if (charsObj == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
|
||||
}
|
||||
if (charsObj instanceof List<?> list) {
|
||||
chars = MiscUtils.getAsStringList(list).stream().map(it -> {
|
||||
@@ -489,7 +489,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
}
|
||||
}).toList();
|
||||
if (chars.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
|
||||
}
|
||||
} else {
|
||||
if (charsObj instanceof Integer integer) {
|
||||
@@ -497,7 +497,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
} else {
|
||||
String character = charsObj.toString();
|
||||
if (character.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
|
||||
}
|
||||
if (character.length() == 1) {
|
||||
chars = List.of(character.toCharArray());
|
||||
@@ -522,7 +522,7 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
for (int codepoint : codepoints) {
|
||||
if (font.isCodepointInUse(codepoint)) {
|
||||
BitmapImage image = font.bitmapImageByCodepoint(codepoint);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict", path, id,
|
||||
throw new LocalizedResourceConfigException("warning.config.image.codepoint_conflict",
|
||||
fontKey.toString(),
|
||||
CharacterUtils.encodeCharsToUnicode(Character.toChars(codepoint)),
|
||||
new String(Character.toChars(codepoint)),
|
||||
@@ -530,12 +530,12 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
}
|
||||
}
|
||||
if (codepoints.length == 0) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_char");
|
||||
}
|
||||
codepointGrid[i] = codepoints;
|
||||
if (size == -1) size = codepoints.length;
|
||||
if (size != codepoints.length) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.invalid_codepoint_grid");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,14 +558,14 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_height", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.image.missing_height");
|
||||
}
|
||||
}
|
||||
|
||||
int height = ResourceConfigUtils.getAsInt(heightObj, "height");
|
||||
int ascent = ResourceConfigUtils.getAsInt(section.getOrDefault("ascent", height - 1), "ascent");
|
||||
if (height < ascent) {
|
||||
throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", path, id, String.valueOf(height), String.valueOf(ascent));
|
||||
throw new LocalizedResourceConfigException("warning.config.image.height_ascent_conflict", String.valueOf(height), String.valueOf(ascent));
|
||||
}
|
||||
|
||||
BitmapImage bitmapImage = new BitmapImage(id, fontKey, height, ascent, resourceLocation, codepointGrid);
|
||||
|
||||
@@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.pack.AbstractPackManager;
|
||||
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.cache.IdAllocator;
|
||||
import net.momirealms.craftengine.core.pack.model.*;
|
||||
import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator;
|
||||
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
|
||||
@@ -32,8 +33,10 @@ import net.momirealms.craftengine.core.util.*;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.incendo.cloud.type.Either;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
@@ -282,7 +285,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractItemManager.this.equipments.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.equipment.duplicate");
|
||||
}
|
||||
@@ -313,6 +316,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
|
||||
public class ItemParser implements IdSectionConfigParser {
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"};
|
||||
private final Map<Key, IdAllocator> idAllocators = new HashMap<>();
|
||||
|
||||
private boolean isModernFormatRequired() {
|
||||
return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4);
|
||||
@@ -322,6 +326,18 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_4);
|
||||
}
|
||||
|
||||
private boolean needsCustomModelDataCompatibility() {
|
||||
return Config.packMinVersion().isBelow(MinecraftVersions.V1_21_2);
|
||||
}
|
||||
|
||||
private boolean needsItemModelCompatibility() {
|
||||
return Config.packMaxVersion().isAbove(MinecraftVersions.V1_21_2);
|
||||
}
|
||||
|
||||
public Map<Key, IdAllocator> idAllocators() {
|
||||
return this.idAllocators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] sectionId() {
|
||||
return CONFIG_SECTION_NAME;
|
||||
@@ -333,261 +349,326 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void preProcess() {
|
||||
this.idAllocators.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess() {
|
||||
for (Map.Entry<Key, IdAllocator> entry : this.idAllocators.entrySet()) {
|
||||
entry.getValue().processPendingAllocations();
|
||||
try {
|
||||
entry.getValue().saveToCache();
|
||||
} catch (IOException e) {
|
||||
AbstractItemManager.this.plugin.logger().warn("Error while saving custom model data allocation for material " + entry.getKey().asString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建或获取已有的自动分配器
|
||||
private IdAllocator getOrCreateIdAllocator(Key key) {
|
||||
return this.idAllocators.computeIfAbsent(key, k -> {
|
||||
IdAllocator newAllocator = new IdAllocator(plugin.dataFolderPath().resolve("cache").resolve("custom-model-data").resolve(k.value() + ".json"));
|
||||
newAllocator.reset(Config.customModelDataStartingValue(k), 16_777_216);
|
||||
try {
|
||||
newAllocator.loadFromCache();
|
||||
} catch (IOException e) {
|
||||
AbstractItemManager.this.plugin.logger().warn("Error while loading custom model data from cache for material " + k.asString(), e);
|
||||
}
|
||||
return newAllocator;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractItemManager.this.customItemsById.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.duplicate");
|
||||
}
|
||||
|
||||
// 创建UniqueKey,仅缓存用
|
||||
UniqueKey uniqueId = UniqueKey.create(id);
|
||||
// 判断是不是原版物品
|
||||
boolean isVanillaItem = isVanillaItem(id);
|
||||
Key material = Key.from(isVanillaItem ? id.value() : ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ENGLISH));
|
||||
Key clientBoundMaterial = section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ENGLISH)) : material;
|
||||
// 如果是原版物品,那么custom-model-data只能是0,即使用户设置了其他值
|
||||
int customModelData = isVanillaItem ? 0 : ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data");
|
||||
boolean clientBoundModel = section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel();
|
||||
if (customModelData < 0) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData));
|
||||
}
|
||||
if (customModelData > 16_777_216) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData));
|
||||
}
|
||||
|
||||
// item-model值
|
||||
Key itemModelKey = null;
|
||||
// 读取服务端侧材质
|
||||
Key material = isVanillaItem ? id : Key.from(ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ROOT));
|
||||
// 读取客户端侧材质
|
||||
Key clientBoundMaterial = VersionHelper.PREMIUM && section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ROOT)) : material;
|
||||
|
||||
CustomItem.Builder<I> itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial);
|
||||
boolean hasItemModelSection = section.containsKey("item-model");
|
||||
// custom model data
|
||||
CompletableFuture<Integer> customModelDataFuture;
|
||||
|
||||
// 如果custom-model-data不为0
|
||||
if (customModelData > 0) {
|
||||
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData));
|
||||
else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData));
|
||||
}
|
||||
// 如果没有item-model选项被配置,同时这个物品又含有 model 区域
|
||||
else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) {
|
||||
// 那么使用物品id当成item-model的值
|
||||
itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString());
|
||||
// 但是有个前提,id必须是有效的resource location
|
||||
if (ResourceLocation.isValid(itemModelKey.toString())) {
|
||||
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey));
|
||||
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
|
||||
} else {
|
||||
itemModelKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有item-model
|
||||
if (hasItemModelSection && VersionHelper.isOrAbove1_21_2()) {
|
||||
itemModelKey = Key.from(section.get("item-model").toString());
|
||||
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey));
|
||||
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey));
|
||||
}
|
||||
|
||||
// 对于不重要的配置,可以仅警告,不返回
|
||||
ExceptionCollector<LocalizedResourceConfigException> collector = new ExceptionCollector<>();
|
||||
|
||||
// 应用物品数据
|
||||
try {
|
||||
applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier);
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
// 应用客户端侧数据
|
||||
try {
|
||||
if (VersionHelper.PREMIUM) {
|
||||
applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier);
|
||||
}
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
|
||||
// 如果不是原版物品,那么加入ce的标识符
|
||||
if (!isVanillaItem)
|
||||
itemBuilder.dataModifier(new IdModifier<>(id));
|
||||
|
||||
// 事件
|
||||
Map<EventTrigger, List<net.momirealms.craftengine.core.plugin.context.function.Function<PlayerOptionalContext>>> eventTriggerListMap;
|
||||
try {
|
||||
eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
eventTriggerListMap = Map.of();
|
||||
}
|
||||
|
||||
// 设置
|
||||
ItemSettings settings;
|
||||
try {
|
||||
settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings"))
|
||||
.map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true)))
|
||||
.map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it)
|
||||
.orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem);
|
||||
}
|
||||
|
||||
// 行为
|
||||
List<ItemBehavior> behaviors;
|
||||
try {
|
||||
behaviors = ItemBehaviors.fromObj(pack, path, id, ResourceConfigUtils.get(section, "behavior", "behaviors"));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
behaviors = Collections.emptyList();
|
||||
}
|
||||
|
||||
// 如果有物品更新器
|
||||
if (section.containsKey("updater")) {
|
||||
Map<String, Object> updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater");
|
||||
List<ItemUpdateConfig.Version> versions = new ArrayList<>(2);
|
||||
for (Map.Entry<String, Object> entry : updater.entrySet()) {
|
||||
try {
|
||||
int version = Integer.parseInt(entry.getKey());
|
||||
versions.add(new ItemUpdateConfig.Version(
|
||||
version,
|
||||
ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0])
|
||||
));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
ItemUpdateConfig config = new ItemUpdateConfig(versions);
|
||||
itemBuilder.updater(config);
|
||||
itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion()));
|
||||
}
|
||||
|
||||
// 构建自定义物品
|
||||
CustomItem<I> customItem = itemBuilder
|
||||
.isVanillaItem(isVanillaItem)
|
||||
.behaviors(behaviors)
|
||||
.settings(settings)
|
||||
.events(eventTriggerListMap)
|
||||
.build();
|
||||
|
||||
// 添加到缓存
|
||||
addCustomItem(customItem);
|
||||
|
||||
// 如果有类别,则添加
|
||||
if (section.containsKey("category")) {
|
||||
AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
|
||||
}
|
||||
|
||||
// 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model
|
||||
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
|
||||
Map<String, Object> legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true);
|
||||
if (modelSection == null && legacyModelSection == null) {
|
||||
collector.throwIfPresent();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null);
|
||||
// 只对自定义物品有这个限制
|
||||
if (!isVanillaItem) {
|
||||
// 既没有模型值也没有item-model
|
||||
if (customModelData == 0 && itemModelKey == null) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id"));
|
||||
// 如果用户指定了,说明要手动分配,不管他是什么版本,都强制设置模型值
|
||||
if (section.containsKey("custom-model-data")) {
|
||||
int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data");
|
||||
if (customModelData < 0) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData));
|
||||
}
|
||||
if (customModelData > 16_777_216) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData));
|
||||
}
|
||||
customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).assignFixedId(id.asString(), customModelData);
|
||||
}
|
||||
}
|
||||
|
||||
// 新版格式
|
||||
ItemModel modernModel = null;
|
||||
// 旧版格式
|
||||
TreeSet<LegacyOverridesModel> legacyOverridesModels = null;
|
||||
// 如果需要支持新版item model 或者用户需要旧版本兼容,但是没配置legacy-model
|
||||
if (needsModelSection) {
|
||||
// 1.21.4+必须要配置model区域,如果不需要高版本兼容,则可以只写legacy-model
|
||||
if (modelSection == null) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
modernModel = ItemModels.fromMap(modelSection);
|
||||
for (ModelGeneration generation : modernModel.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
// 用户没指定custom-model-data,则看当前资源包版本兼容需求
|
||||
else {
|
||||
// 如果最低版本要1.21.1以下支持
|
||||
if (needsCustomModelDataCompatibility()) {
|
||||
customModelDataFuture = getOrCreateIdAllocator(clientBoundMaterial).requestAutoId(id.asString());
|
||||
}
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.addAndThrow(e);
|
||||
}
|
||||
}
|
||||
// 如果需要旧版本兼容
|
||||
if (needsLegacyCompatibility()) {
|
||||
if (legacyModelSection != null) {
|
||||
try {
|
||||
LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData);
|
||||
for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
}
|
||||
legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides());
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.addAndThrow(e);
|
||||
}
|
||||
} else {
|
||||
legacyOverridesModels = new TreeSet<>();
|
||||
processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData);
|
||||
if (legacyOverridesModels.isEmpty()) {
|
||||
collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义物品的model处理
|
||||
if (!isVanillaItem) {
|
||||
// 这个item-model是否存在,且是原版item-model
|
||||
boolean isVanillaItemModel = itemModelKey != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModelKey);
|
||||
// 使用了自定义模型值
|
||||
if (customModelData != 0) {
|
||||
// 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model
|
||||
Key finalBaseModel = isVanillaItemModel ? itemModelKey : clientBoundMaterial;
|
||||
// 检查cmd冲突
|
||||
Map<Integer, Key> conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>());
|
||||
if (conflict.containsKey(customModelData)) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString()));
|
||||
}
|
||||
conflict.put(customModelData, id);
|
||||
// 添加新版item model
|
||||
if (isModernFormatRequired() && modernModel != null) {
|
||||
TreeMap<Integer, ModernItemModel> map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>());
|
||||
map.put(customModelData, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
// 添加旧版 overrides
|
||||
if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
|
||||
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>());
|
||||
lom.addAll(legacyOverridesModels);
|
||||
}
|
||||
} else if (isVanillaItemModel) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModelKey.asString()));
|
||||
}
|
||||
|
||||
// 使用了item-model组件,且不是原版物品的
|
||||
if (itemModelKey != null && !isVanillaItemModel) {
|
||||
if (isModernFormatRequired() && modernModel != null) {
|
||||
AbstractItemManager.this.modernItemModels1_21_4.put(itemModelKey, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
|
||||
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModelKey, k -> new TreeSet<>());
|
||||
lom.addAll(legacyOverridesModels);
|
||||
// 否则不主动分配模型值
|
||||
else {
|
||||
customModelDataFuture = CompletableFuture.completedFuture(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 原版物品的item model覆写
|
||||
if (isModernFormatRequired()) {
|
||||
AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
// 原版物品不应该有这个
|
||||
customModelDataFuture = CompletableFuture.completedFuture(0);
|
||||
}
|
||||
|
||||
// 抛出异常
|
||||
collector.throwIfPresent();
|
||||
// 是否使用客户端侧模型
|
||||
boolean clientBoundModel = VersionHelper.PREMIUM && (section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-model"), "client-bound-model") : Config.globalClientboundModel());
|
||||
|
||||
// 当模型值完成分配的时候
|
||||
customModelDataFuture.thenAccept(customModelData -> ResourceConfigUtils.runCatching(path, node, () -> {
|
||||
|
||||
// item model
|
||||
Key itemModel = null;
|
||||
|
||||
// 如果这个版本可以使用 item model
|
||||
if (!isVanillaItem && needsItemModelCompatibility()) {
|
||||
// 如果用户主动设定了item model,那么肯定要设置
|
||||
if (section.containsKey("item-model")) {
|
||||
itemModel = Key.from(section.get("item-model").toString());
|
||||
}
|
||||
// 用户没设置item model也没设置custom model data,那么为他生成一个基于物品id的item model
|
||||
else if (customModelData == 0) {
|
||||
itemModel = id;
|
||||
}
|
||||
// 用户没设置item model但是有custom model data,那么就使用custom model data
|
||||
}
|
||||
|
||||
CustomItem.Builder<I> itemBuilder = createPlatformItemBuilder(uniqueId, material, clientBoundMaterial);
|
||||
if (customModelData > 0) {
|
||||
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData));
|
||||
else itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData));
|
||||
}
|
||||
if (itemModel != null) {
|
||||
if (clientBoundModel) itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModel));
|
||||
else itemBuilder.dataModifier(new ItemModelModifier<>(itemModel));
|
||||
}
|
||||
|
||||
// 对于不重要的配置,可以仅警告,不返回
|
||||
ExceptionCollector<LocalizedResourceConfigException> collector = new ExceptionCollector<>();
|
||||
|
||||
// 应用物品数据
|
||||
try {
|
||||
applyDataModifiers(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier);
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
|
||||
// 应用客户端侧数据
|
||||
try {
|
||||
if (VersionHelper.PREMIUM) {
|
||||
applyDataModifiers(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier);
|
||||
}
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
|
||||
// 如果不是原版物品,那么加入ce的标识符
|
||||
if (!isVanillaItem)
|
||||
itemBuilder.dataModifier(new IdModifier<>(id));
|
||||
|
||||
// 事件
|
||||
Map<EventTrigger, List<net.momirealms.craftengine.core.plugin.context.function.Function<PlayerOptionalContext>>> eventTriggerListMap;
|
||||
try {
|
||||
eventTriggerListMap = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
eventTriggerListMap = Map.of();
|
||||
}
|
||||
|
||||
// 设置
|
||||
ItemSettings settings;
|
||||
try {
|
||||
settings = Optional.ofNullable(ResourceConfigUtils.get(section, "settings"))
|
||||
.map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true)))
|
||||
.map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it)
|
||||
.orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
settings = ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem);
|
||||
}
|
||||
|
||||
// 行为
|
||||
List<ItemBehavior> behaviors;
|
||||
try {
|
||||
behaviors = ResourceConfigUtils.parseConfigAsList(ResourceConfigUtils.get(section, "behavior", "behaviors"), map -> ItemBehaviors.fromMap(pack, path, node, id, map));
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.add(e);
|
||||
behaviors = Collections.emptyList();
|
||||
}
|
||||
|
||||
// 如果有物品更新器
|
||||
if (section.containsKey("updater")) {
|
||||
Map<String, Object> updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater");
|
||||
List<ItemUpdateConfig.Version> versions = new ArrayList<>(2);
|
||||
for (Map.Entry<String, Object> entry : updater.entrySet()) {
|
||||
try {
|
||||
int version = Integer.parseInt(entry.getKey());
|
||||
versions.add(new ItemUpdateConfig.Version(
|
||||
version,
|
||||
ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0])
|
||||
));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
ItemUpdateConfig config = new ItemUpdateConfig(versions);
|
||||
itemBuilder.updater(config);
|
||||
itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion()));
|
||||
}
|
||||
|
||||
// 构建自定义物品
|
||||
CustomItem<I> customItem = itemBuilder
|
||||
.isVanillaItem(isVanillaItem)
|
||||
.behaviors(behaviors)
|
||||
.settings(settings)
|
||||
.events(eventTriggerListMap)
|
||||
.build();
|
||||
|
||||
// 添加到缓存
|
||||
addCustomItem(customItem);
|
||||
|
||||
// 如果有类别,则添加
|
||||
if (section.containsKey("category")) {
|
||||
AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList());
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================
|
||||
*
|
||||
* 模型配置分界线
|
||||
*
|
||||
* ========================
|
||||
*/
|
||||
|
||||
// 模型配置区域,如果这里被配置了,那么用户必须要配置custom-model-data或item-model
|
||||
Map<String, Object> modelSection = MiscUtils.castToMap(section.get("model"), true);
|
||||
Map<String, Object> legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true);
|
||||
if (modelSection == null && legacyModelSection == null) {
|
||||
collector.throwIfPresent();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needsModelSection = isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null);
|
||||
// 只对自定义物品有这个限制,既没有模型值也没有item-model
|
||||
if (!isVanillaItem && customModelData == 0 && itemModel == null) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model_id"));
|
||||
}
|
||||
|
||||
// 新版格式
|
||||
ItemModel modernModel = null;
|
||||
// 旧版格式
|
||||
TreeSet<LegacyOverridesModel> legacyOverridesModels = null;
|
||||
// 如果需要支持新版item model 或者用户需要旧版本兼容,但是没配置legacy-model
|
||||
if (needsModelSection) {
|
||||
// 1.21.4+必须要配置model区域,如果不需要高版本兼容,则可以只写legacy-model
|
||||
if (modelSection == null) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.missing_model"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
modernModel = ItemModels.fromMap(modelSection);
|
||||
for (ModelGeneration generation : modernModel.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
}
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.addAndThrow(e);
|
||||
}
|
||||
}
|
||||
// 如果需要旧版本兼容
|
||||
if (needsLegacyCompatibility()) {
|
||||
if (legacyModelSection != null) {
|
||||
try {
|
||||
LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData);
|
||||
for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) {
|
||||
prepareModelGeneration(generation);
|
||||
}
|
||||
legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides());
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
collector.addAndThrow(e);
|
||||
}
|
||||
} else {
|
||||
legacyOverridesModels = new TreeSet<>();
|
||||
processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, clientBoundMaterial, customModelData);
|
||||
if (legacyOverridesModels.isEmpty()) {
|
||||
collector.add(new LocalizedResourceConfigException("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义物品的model处理
|
||||
if (!isVanillaItem) {
|
||||
// 这个item-model是否存在,且是原版item-model
|
||||
boolean isVanillaItemModel = itemModel != null && AbstractPackManager.PRESET_ITEMS.containsKey(itemModel);
|
||||
// 使用了自定义模型值
|
||||
if (customModelData != 0) {
|
||||
// 如果用户主动设置了item-model且为原版物品,则使用item-model为基础模型,否则使用其视觉材质对应的item-model
|
||||
Key finalBaseModel = isVanillaItemModel ? itemModel : clientBoundMaterial;
|
||||
// 检查cmd冲突
|
||||
Map<Integer, Key> conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(finalBaseModel, k -> new HashMap<>());
|
||||
if (conflict.containsKey(customModelData)) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString()));
|
||||
}
|
||||
conflict.put(customModelData, id);
|
||||
// 添加新版item model
|
||||
if (isModernFormatRequired() && modernModel != null) {
|
||||
TreeMap<Integer, ModernItemModel> map = AbstractItemManager.this.modernOverrides.computeIfAbsent(finalBaseModel, k -> new TreeMap<>());
|
||||
map.put(customModelData, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
// 添加旧版 overrides
|
||||
if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
|
||||
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(finalBaseModel, k -> new TreeSet<>());
|
||||
lom.addAll(legacyOverridesModels);
|
||||
}
|
||||
} else if (isVanillaItemModel) {
|
||||
collector.addAndThrow(new LocalizedResourceConfigException("warning.config.item.item_model.conflict", itemModel.asString()));
|
||||
}
|
||||
|
||||
// 使用了item-model组件,且不是原版物品的
|
||||
if (itemModel != null && !isVanillaItemModel) {
|
||||
if (isModernFormatRequired() && modernModel != null) {
|
||||
AbstractItemManager.this.modernItemModels1_21_4.put(itemModel, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) {
|
||||
TreeSet<LegacyOverridesModel> lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModel, k -> new TreeSet<>());
|
||||
lom.addAll(legacyOverridesModels);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 原版物品的item model覆写
|
||||
if (isModernFormatRequired()) {
|
||||
AbstractItemManager.this.modernItemModels1_21_4.put(id, new ModernItemModel(
|
||||
modernModel,
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("oversized-in-gui", true), "oversized-in-gui"),
|
||||
ResourceConfigUtils.getAsBoolean(section.getOrDefault("hand-animation-on-swap", true), "hand-animation-on-swap")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 抛出异常
|
||||
collector.throwIfPresent();
|
||||
|
||||
}, () -> GsonHelper.get().toJson(section)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public class EmptyItemBehavior extends ItemBehavior {
|
||||
public static class Factory implements ItemBehaviorFactory {
|
||||
|
||||
@Override
|
||||
public ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments) {
|
||||
public ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments) {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ import java.util.Map;
|
||||
|
||||
public interface ItemBehaviorFactory {
|
||||
|
||||
ItemBehavior create(Pack pack, Path path, Key id, Map<String, Object> arguments);
|
||||
ItemBehavior create(Pack pack, Path path, String node, Key id, Map<String, Object> arguments);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ItemBehaviors {
|
||||
.register(ResourceKey.create(Registries.ITEM_BEHAVIOR_FACTORY.location(), key), factory);
|
||||
}
|
||||
|
||||
public static ItemBehavior fromMap(Pack pack, Path path, Key id, Map<String, Object> map) {
|
||||
public static ItemBehavior fromMap(Pack pack, Path path, String node, Key id, Map<String, Object> map) {
|
||||
if (map == null || map.isEmpty()) return EmptyItemBehavior.INSTANCE;
|
||||
String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.behavior.missing_type");
|
||||
Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE);
|
||||
@@ -31,25 +31,6 @@ public class ItemBehaviors {
|
||||
if (factory == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.item.behavior.invalid_type", type);
|
||||
}
|
||||
return factory.create(pack, path, id, map);
|
||||
}
|
||||
|
||||
public static List<ItemBehavior> fromList(Pack pack, Path path, Key id, List<Map<String, Object>> list) {
|
||||
List<ItemBehavior> behaviors = new ArrayList<>(list.size());
|
||||
for (Map<String, Object> map : list) {
|
||||
behaviors.add(fromMap(pack, path, id, map));
|
||||
}
|
||||
return behaviors;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<ItemBehavior> fromObj(Pack pack, Path path, Key id, Object behaviorObj) {
|
||||
if (behaviorObj instanceof Map<?,?>) {
|
||||
return List.of(fromMap(pack, path, id, MiscUtils.castToMap(behaviorObj, false)));
|
||||
} else if (behaviorObj instanceof List<?>) {
|
||||
return fromList(pack, path, id, (List<Map<String, Object>>) behaviorObj);
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
return factory.create(pack, path, node, id, map);
|
||||
}
|
||||
}
|
||||
@@ -135,10 +135,10 @@ public abstract class AbstractRecipeManager<T> implements RecipeManager<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (!Config.enableRecipeSystem()) return;
|
||||
if (AbstractRecipeManager.this.byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.recipe.duplicate", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.recipe.duplicate");
|
||||
}
|
||||
Recipe<T> recipe = RecipeSerializers.fromMap(id, section);
|
||||
try {
|
||||
|
||||
@@ -608,7 +608,6 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
return cachedConfigs;
|
||||
}
|
||||
|
||||
// todo 本地化日志
|
||||
private void loadResourceConfigs(Predicate<ConfigParser> predicate) {
|
||||
long o1 = System.nanoTime();
|
||||
TreeMap<ConfigParser, List<CachedConfigSection>> cachedConfigs = this.updateCachedConfigFiles();
|
||||
@@ -622,13 +621,12 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
switch (parser) {
|
||||
case SectionConfigParser configParser -> {
|
||||
for (CachedConfigSection cached : entry.getValue()) {
|
||||
try {
|
||||
configParser.parseSection(cached.pack(), cached.filePath(), cached.config());
|
||||
} catch (LocalizedException e) {
|
||||
printWarningRecursively(e, cached.filePath(), cached.prefix());
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(cached.config()), e);
|
||||
}
|
||||
ResourceConfigUtils.runCatching(
|
||||
cached.filePath(),
|
||||
cached.prefix(),
|
||||
() -> configParser.parseSection(cached.pack(), cached.filePath(), cached.config()),
|
||||
() -> GsonHelper.get().toJson(cached.config())
|
||||
);
|
||||
}
|
||||
}
|
||||
case IdObjectConfigParser configParser -> {
|
||||
@@ -636,13 +634,13 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
for (Map.Entry<String, Object> configEntry : cached.config().entrySet()) {
|
||||
String key = configEntry.getKey();
|
||||
Key id = Key.withDefaultNamespace(key, cached.pack().namespace());
|
||||
try {
|
||||
configParser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue());
|
||||
} catch (LocalizedException e) {
|
||||
printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e);
|
||||
}
|
||||
String node = cached.prefix() + "." + key;
|
||||
ResourceConfigUtils.runCatching(
|
||||
cached.filePath(),
|
||||
node,
|
||||
() -> configParser.parseObject(cached.pack(), cached.filePath(), node, id, configEntry.getValue()),
|
||||
() -> GsonHelper.get().toJson(configEntry.getValue())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,49 +649,37 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
for (Map.Entry<String, Object> configEntry : cached.config().entrySet()) {
|
||||
String key = configEntry.getKey();
|
||||
Key id = Key.withDefaultNamespace(key, cached.pack().namespace());
|
||||
try {
|
||||
if (configEntry.getValue() instanceof Map<?, ?> configSection0) {
|
||||
Map<String, Object> config = castToMap(configSection0, false);
|
||||
if ((boolean) config.getOrDefault("debug", false)) {
|
||||
this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config)));
|
||||
}
|
||||
if ((boolean) config.getOrDefault("enable", true)) {
|
||||
configParser.parseSection(cached.pack(), cached.filePath(), id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false));
|
||||
}
|
||||
} else {
|
||||
TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName());
|
||||
}
|
||||
} catch (LocalizedException e) {
|
||||
printWarningRecursively(e, cached.filePath(), cached.prefix() + "." + key);
|
||||
} catch (Exception e) {
|
||||
this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help. Additional info: " + GsonHelper.get().toJson(configEntry.getValue()), e);
|
||||
if (!(configEntry.getValue() instanceof Map<?, ?> section)) {
|
||||
TranslationManager.instance().log("warning.config.structure.not_section",
|
||||
cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName());
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> config = castToMap(section, false);
|
||||
if ((boolean) config.getOrDefault("debug", false)) {
|
||||
this.plugin.logger().info(GsonHelper.get().toJson(this.plugin.templateManager().applyTemplates(id, config)));
|
||||
}
|
||||
if (!(boolean) config.getOrDefault("enable", true)) {
|
||||
continue;
|
||||
}
|
||||
String node = cached.prefix() + "." + key;
|
||||
ResourceConfigUtils.runCatching(
|
||||
cached.filePath(),
|
||||
node,
|
||||
() -> configParser.parseSection(cached.pack(), cached.filePath(), node, id, MiscUtils.castToMap(this.plugin.templateManager().applyTemplates(id, config), false)),
|
||||
() -> GsonHelper.get().toJson(section)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
|
||||
parser.postProcess();
|
||||
long t2 = System.nanoTime();
|
||||
this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void printWarningRecursively(LocalizedException e, Path path, String prefix) {
|
||||
for (Throwable t : e.getSuppressed()) {
|
||||
if (t instanceof LocalizedException suppressed) {
|
||||
printWarningRecursively(suppressed, path, prefix);
|
||||
}
|
||||
}
|
||||
if (e instanceof LocalizedResourceConfigException exception) {
|
||||
exception.setPath(path);
|
||||
exception.setId(prefix);
|
||||
}
|
||||
TranslationManager.instance().log(e.node(), e.arguments());
|
||||
}
|
||||
|
||||
private void processConfigEntry(Map.Entry<String, Object> entry, Path path, Pack pack, BiConsumer<ConfigParser, CachedConfigSection> callback) {
|
||||
if (entry.getValue() instanceof Map<?,?> typeSections0) {
|
||||
String key = entry.getKey();
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
package net.momirealms.craftengine.core.pack.cache;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.momirealms.craftengine.core.util.FileUtils;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class AutoId {
|
||||
private final Path cachePath;
|
||||
private final BiMap<String, Integer> forcedIds = HashBiMap.create(128);
|
||||
|
||||
private final Map<String, Integer> cachedIds = new HashMap<>();
|
||||
private final BitSet occupiedIds = new BitSet();
|
||||
private final Map<String, CompletableFuture<Integer>> autoIds = new HashMap<>();
|
||||
private int currentAutoId;
|
||||
private int minId;
|
||||
private int maxId;
|
||||
|
||||
public AutoId(Path cachePath) {
|
||||
this.cachePath = cachePath;
|
||||
}
|
||||
|
||||
public void reset(int startIndex, int endIndex) {
|
||||
this.minId = startIndex;
|
||||
this.currentAutoId = startIndex;
|
||||
this.maxId = endIndex;
|
||||
this.occupiedIds.clear();
|
||||
this.forcedIds.clear();
|
||||
this.autoIds.clear();
|
||||
this.cachedIds.clear();
|
||||
}
|
||||
|
||||
public void arrangeForTheRest() {
|
||||
// 然后处理自动分配的ID
|
||||
for (Map.Entry<String, CompletableFuture<Integer>> entry : this.autoIds.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
CompletableFuture<Integer> future = entry.getValue();
|
||||
|
||||
// 不应该触发
|
||||
if (future.isDone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 尝试使用缓存的ID,并且其有效
|
||||
Integer cachedId = this.cachedIds.get(name);
|
||||
if (cachedId != null && !this.occupiedIds.get(cachedId) && cachedId >= this.minId && cachedId <= this.maxId) {
|
||||
this.occupiedIds.set(cachedId);
|
||||
future.complete(cachedId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 寻找下一个可用的自动ID
|
||||
int autoId = findNextAvailableAutoId();
|
||||
if (autoId == -1) {
|
||||
// 没有可用的ID
|
||||
future.completeExceptionally(new AutoIdExhaustedException(name, this.minId, this.maxId));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 分配找到的ID
|
||||
this.occupiedIds.set(autoId);
|
||||
future.complete(autoId);
|
||||
this.cachedIds.put(name, autoId);
|
||||
}
|
||||
|
||||
// 清空futureIds,因为所有请求都已处理
|
||||
this.autoIds.clear();
|
||||
}
|
||||
|
||||
private int findNextAvailableAutoId() {
|
||||
// 如果已经用尽
|
||||
if (this.currentAutoId > this.maxId) {
|
||||
return -1;
|
||||
}
|
||||
// 寻找下一个可用的id
|
||||
this.currentAutoId = this.occupiedIds.nextClearBit(this.currentAutoId);
|
||||
// 已经用完了
|
||||
if (this.currentAutoId > maxId) {
|
||||
return -1;
|
||||
}
|
||||
// 找到了
|
||||
return this.currentAutoId;
|
||||
}
|
||||
|
||||
// 强制使用某个id,这时候直接标记到occupiedIds,如果被占用,则直接抛出异常
|
||||
public CompletableFuture<Integer> forceId(final String name, int index) {
|
||||
// 检查ID是否在有效范围内,一般不会在这触发
|
||||
if (index < this.minId || index > this.maxId) {
|
||||
return CompletableFuture.failedFuture(new AutoIdOutOfRangeException(name, index, this.minId, this.maxId));
|
||||
}
|
||||
|
||||
// 检查ID是否已被其他名称占用
|
||||
String previous = this.forcedIds.inverse().get(index);
|
||||
if (previous != null && !previous.equals(name)) {
|
||||
return CompletableFuture.failedFuture(new AutoIdConflictException(previous, index));
|
||||
}
|
||||
|
||||
this.forcedIds.put(name, index);
|
||||
this.cachedIds.remove(name); // 如果曾经被缓存过,那么移除
|
||||
return CompletableFuture.completedFuture(index);
|
||||
}
|
||||
|
||||
// 自动分配id,优先使用缓存的值
|
||||
public CompletableFuture<Integer> autoId(final String name) {
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>();
|
||||
this.autoIds.put(name, future);
|
||||
return future;
|
||||
}
|
||||
|
||||
// 大多数时候通过指令,移除那些已经不再被使用的id,使用完以后记得调用saveCache以保存更改
|
||||
public int clearUnusedIds(Predicate<String> predicate) {
|
||||
List<String> toRemove = new ArrayList<>();
|
||||
for (String id : this.cachedIds.keySet()) {
|
||||
if (predicate.test(id)) {
|
||||
toRemove.add(id);
|
||||
}
|
||||
}
|
||||
for (String id : toRemove) {
|
||||
Integer removedId = this.cachedIds.remove(id);
|
||||
if (removedId != null) {
|
||||
// 只有当这个ID不是强制ID时才从occupiedIds中移除
|
||||
if (!forcedIds.containsValue(removedId)) {
|
||||
occupiedIds.clear(removedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return toRemove.size();
|
||||
}
|
||||
|
||||
// 获取已分配的ID(用于调试或查询)
|
||||
public Integer getId(String name) {
|
||||
if (forcedIds.containsKey(name)) {
|
||||
return forcedIds.get(name);
|
||||
}
|
||||
return cachedIds.get(name);
|
||||
}
|
||||
|
||||
// 获取所有已分配的ID映射
|
||||
public Map<String, Integer> getAllocatedIds() {
|
||||
Map<String, Integer> result = new HashMap<>();
|
||||
result.putAll(forcedIds);
|
||||
result.putAll(cachedIds);
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
// 检查某个ID是否已被占用
|
||||
public boolean isIdOccupied(int id) {
|
||||
return occupiedIds.get(id);
|
||||
}
|
||||
|
||||
// 从缓存中加载文件
|
||||
public void loadCache() throws IOException {
|
||||
if (!Files.exists(this.cachePath)) {
|
||||
return;
|
||||
}
|
||||
JsonElement element = GsonHelper.readJsonFile(this.cachePath);
|
||||
if (element instanceof JsonObject jsonObject) {
|
||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
||||
if (entry.getValue() instanceof JsonPrimitive primitive) {
|
||||
int id = primitive.getAsInt();
|
||||
this.cachedIds.put(entry.getKey(), id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存缓存到文件
|
||||
public void saveCache() throws IOException {
|
||||
FileUtils.createDirectoriesSafe(this.cachePath.getParent());
|
||||
GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIds), this.cachePath);
|
||||
}
|
||||
|
||||
public static class AutoIdConflictException extends RuntimeException {
|
||||
public AutoIdConflictException(String previousOwner, int id) {
|
||||
super("ID " + id + " is already occupied by: " + previousOwner);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoIdOutOfRangeException extends RuntimeException {
|
||||
public AutoIdOutOfRangeException(String name, int id, int min, int max) {
|
||||
super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoIdExhaustedException extends RuntimeException {
|
||||
public AutoIdExhaustedException(String name, int min, int max) {
|
||||
super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied.");
|
||||
}
|
||||
}
|
||||
}
|
||||
226
core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java
vendored
Normal file
226
core/src/main/java/net/momirealms/craftengine/core/pack/cache/IdAllocator.java
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package net.momirealms.craftengine.core.pack.cache;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import net.momirealms.craftengine.core.util.FileUtils;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class IdAllocator {
|
||||
private final Path cacheFilePath;
|
||||
private final BiMap<String, Integer> forcedIdMap = HashBiMap.create(128);
|
||||
private final Map<String, Integer> cachedIdMap = new HashMap<>();
|
||||
private final BitSet occupiedIdSet = new BitSet();
|
||||
private final Map<String, CompletableFuture<Integer>> pendingAllocations = new HashMap<>();
|
||||
|
||||
private int nextAutoId;
|
||||
private int minId;
|
||||
private int maxId;
|
||||
|
||||
public IdAllocator(Path cacheFilePath) {
|
||||
this.cacheFilePath = cacheFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置分配器状态
|
||||
* @param minId 最小ID(包含)
|
||||
* @param maxId 最大ID(包含)
|
||||
*/
|
||||
public void reset(int minId, int maxId) {
|
||||
this.minId = minId;
|
||||
this.nextAutoId = minId;
|
||||
this.maxId = maxId;
|
||||
this.occupiedIdSet.clear();
|
||||
this.forcedIdMap.clear();
|
||||
this.pendingAllocations.clear();
|
||||
this.cachedIdMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有待分配的自动ID请求
|
||||
*/
|
||||
public void processPendingAllocations() {
|
||||
for (Map.Entry<String, CompletableFuture<Integer>> entry : this.pendingAllocations.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
CompletableFuture<Integer> future = entry.getValue();
|
||||
|
||||
if (future.isDone()) {
|
||||
continue; // 不应该发生的情况
|
||||
}
|
||||
|
||||
// 优先尝试使用缓存的ID
|
||||
Integer cachedId = this.cachedIdMap.get(name);
|
||||
if (isIdAvailable(cachedId)) {
|
||||
allocateId(name, cachedId, future);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 分配新的自动ID
|
||||
int newId = findNextAvailableId();
|
||||
if (newId == -1) {
|
||||
future.completeExceptionally(new IdExhaustedException(name, this.minId, this.maxId));
|
||||
continue;
|
||||
}
|
||||
|
||||
allocateId(name, newId, future);
|
||||
this.cachedIdMap.put(name, newId);
|
||||
}
|
||||
|
||||
this.pendingAllocations.clear();
|
||||
}
|
||||
|
||||
private boolean isIdAvailable(Integer id) {
|
||||
return id != null && id >= this.minId && id <= this.maxId
|
||||
&& !this.occupiedIdSet.get(id);
|
||||
}
|
||||
|
||||
private void allocateId(String name, int id, CompletableFuture<Integer> future) {
|
||||
this.occupiedIdSet.set(id);
|
||||
future.complete(id);
|
||||
}
|
||||
|
||||
private int findNextAvailableId() {
|
||||
if (this.nextAutoId > this.maxId) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
this.nextAutoId = this.occupiedIdSet.nextClearBit(this.nextAutoId);
|
||||
return this.nextAutoId <= this.maxId ? this.nextAutoId : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制分配指定ID,无视限制
|
||||
* @param name 名称
|
||||
* @param id 要分配的ID
|
||||
* @return 分配结果的Future
|
||||
*/
|
||||
public CompletableFuture<Integer> assignFixedId(String name, int id) {
|
||||
// 检查ID是否被其他名称占用
|
||||
String existingOwner = this.forcedIdMap.inverse().get(id);
|
||||
if (existingOwner != null && !existingOwner.equals(name)) {
|
||||
return CompletableFuture.failedFuture(new IdConflictException(existingOwner, id));
|
||||
}
|
||||
|
||||
this.forcedIdMap.put(name, id);
|
||||
this.cachedIdMap.remove(name); // 清除可能的缓存
|
||||
this.occupiedIdSet.set(id);
|
||||
return CompletableFuture.completedFuture(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求自动分配ID
|
||||
* @param name 名称
|
||||
* @return 分配结果的Future
|
||||
*/
|
||||
public CompletableFuture<Integer> requestAutoId(String name) {
|
||||
CompletableFuture<Integer> future = new CompletableFuture<>();
|
||||
this.pendingAllocations.put(name, future);
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理不再使用的ID
|
||||
* @param shouldRemove 判断是否应该移除的谓词
|
||||
* @return 被移除的ID数量
|
||||
*/
|
||||
public int cleanupUnusedIds(Predicate<String> shouldRemove) {
|
||||
List<String> idsToRemove = new ArrayList<>();
|
||||
for (String id : this.cachedIdMap.keySet()) {
|
||||
if (shouldRemove.test(id)) {
|
||||
idsToRemove.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
int removedCount = 0;
|
||||
for (String id : idsToRemove) {
|
||||
Integer removedId = this.cachedIdMap.remove(id);
|
||||
if (removedId != null && !this.forcedIdMap.containsValue(removedId)) {
|
||||
this.occupiedIdSet.clear(removedId);
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定名称的ID
|
||||
* @param name 名称
|
||||
* @return ID,如果不存在返回null
|
||||
*/
|
||||
public Integer getId(String name) {
|
||||
Integer forcedId = this.forcedIdMap.get(name);
|
||||
return forcedId != null ? forcedId : this.cachedIdMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已分配的ID映射(不可修改)
|
||||
*/
|
||||
public Map<String, Integer> getAllAllocatedIds() {
|
||||
Map<String, Integer> result = new HashMap<>();
|
||||
result.putAll(this.forcedIdMap);
|
||||
result.putAll(this.cachedIdMap);
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查ID是否已被占用
|
||||
*/
|
||||
public boolean isIdOccupied(int id) {
|
||||
return this.occupiedIdSet.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载缓存
|
||||
*/
|
||||
public void loadFromCache() throws IOException {
|
||||
if (!Files.exists(this.cacheFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonElement element = GsonHelper.readJsonFile(this.cacheFilePath);
|
||||
if (element instanceof JsonObject jsonObject) {
|
||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
||||
if (entry.getValue() instanceof JsonPrimitive primitive) {
|
||||
int id = primitive.getAsInt();
|
||||
this.cachedIdMap.put(entry.getKey(), id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存缓存到文件
|
||||
*/
|
||||
public void saveToCache() throws IOException {
|
||||
FileUtils.createDirectoriesSafe(this.cacheFilePath.getParent());
|
||||
GsonHelper.writeJsonFile(GsonHelper.get().toJsonTree(this.cachedIdMap), this.cacheFilePath);
|
||||
}
|
||||
|
||||
// 异常类保持不变,但建议也重命名以保持一致性
|
||||
public static class IdConflictException extends RuntimeException {
|
||||
public IdConflictException(String previousOwner, int id) {
|
||||
super("ID " + id + " is already occupied by: " + previousOwner);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdOutOfRangeException extends RuntimeException {
|
||||
public IdOutOfRangeException(String name, int id, int min, int max) {
|
||||
super("ID " + id + " for '" + name + "' is out of range. Valid range: " + min + "-" + max);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IdExhaustedException extends RuntimeException {
|
||||
public IdExhaustedException(String name, int min, int max) {
|
||||
super("No available auto ID for '" + name + "'. All IDs in range " + min + "-" + max + " are occupied.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,8 @@ public class Config {
|
||||
protected boolean item$update_triggers$click_in_inventory;
|
||||
protected boolean item$update_triggers$drop;
|
||||
protected boolean item$update_triggers$pick_up;
|
||||
protected int item$custom_model_data_starting_value$default;
|
||||
protected Map<Key, Integer> item$custom_model_data_starting_value$overrides;
|
||||
|
||||
protected String equipment$sacrificed_vanilla_armor$type;
|
||||
protected Key equipment$sacrificed_vanilla_armor$asset_id;
|
||||
@@ -239,6 +241,7 @@ public class Config {
|
||||
.addIgnoredRoute(PluginProperties.getValue("config"), "resource-pack.delivery.hosting", '.')
|
||||
.addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-blocks.convert", '.')
|
||||
.addIgnoredRoute(PluginProperties.getValue("config"), "chunk-system.process-invalid-furniture.convert", '.')
|
||||
.addIgnoredRoute(PluginProperties.getValue("config"), "item.custom-model-data-starting-value.overrides", '.')
|
||||
.build());
|
||||
}
|
||||
try {
|
||||
@@ -398,6 +401,22 @@ public class Config {
|
||||
item$update_triggers$click_in_inventory = config.getBoolean("item.update-triggers.click-in-inventory", false);
|
||||
item$update_triggers$drop = config.getBoolean("item.update-triggers.drop", false);
|
||||
item$update_triggers$pick_up = config.getBoolean("item.update-triggers.pick-up", false);
|
||||
item$custom_model_data_starting_value$default = config.getInt("item.custom-model-data-starting-value.default", 10000);
|
||||
|
||||
Section customModelDataOverridesSection = config.getSection("item.custom-model-data-starting-value.overrides");
|
||||
if (customModelDataOverridesSection != null) {
|
||||
Map<Key, Integer> customModelDataOverrides = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : customModelDataOverridesSection.getStringRouteMappedValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof String s) {
|
||||
customModelDataOverrides.put(Key.of(entry.getKey()), Integer.parseInt(s));
|
||||
} else if (entry.getValue() instanceof Integer i) {
|
||||
customModelDataOverrides.put(Key.of(entry.getKey()), i);
|
||||
}
|
||||
}
|
||||
item$custom_model_data_starting_value$overrides = customModelDataOverrides;
|
||||
} else {
|
||||
item$custom_model_data_starting_value$overrides = Map.of();
|
||||
}
|
||||
|
||||
// block
|
||||
block$sound_system$enable = config.getBoolean("block.sound-system.enable", true);
|
||||
@@ -752,6 +771,13 @@ public class Config {
|
||||
return instance.furniture$hide_base_entity;
|
||||
}
|
||||
|
||||
public static int customModelDataStartingValue(Key material) {
|
||||
if (instance.item$custom_model_data_starting_value$overrides.containsKey(material)) {
|
||||
return instance.item$custom_model_data_starting_value$overrides.get(material);
|
||||
}
|
||||
return instance.item$custom_model_data_starting_value$default;
|
||||
}
|
||||
|
||||
public static int compressionMethod() {
|
||||
int id = instance.chunk_system$compression_method;
|
||||
if (id <= 0 || id > CompressionMethod.METHOD_COUNT) {
|
||||
|
||||
@@ -8,6 +8,6 @@ import java.nio.file.Path;
|
||||
|
||||
public interface IdObjectConfigParser extends ConfigParser {
|
||||
|
||||
default void parseObject(Pack pack, Path path, Key id, Object object) throws LocalizedException {
|
||||
default void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ import java.util.Map;
|
||||
|
||||
public interface IdSectionConfigParser extends ConfigParser {
|
||||
|
||||
default void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) throws LocalizedException {
|
||||
default void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) throws LocalizedException {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ public class TemplateManagerImpl implements TemplateManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseObject(Pack pack, Path path, Key id, Object obj) {
|
||||
public void parseObject(Pack pack, Path path, String node, Key id, Object obj) {
|
||||
if (templates.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.template.duplicate", path.toString(), id.toString());
|
||||
throw new LocalizedResourceConfigException("warning.config.template.duplicate");
|
||||
}
|
||||
// 预处理会将 string类型的键或值解析为ArgumentString,以加速模板应用。所以处理后不可能存在String类型。
|
||||
templates.put(id, preprocessUnknownValue(obj));
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.config.IdObjectConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -49,7 +50,7 @@ public class GlobalVariableManager implements Manageable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseObject(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Object object) throws LocalizedException {
|
||||
public void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException {
|
||||
if (object != null) {
|
||||
GlobalVariableManager.this.globalVariables.put(id.value(), object.toString());
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
String name = section.getOrDefault("name", id).toString();
|
||||
List<String> members = MiscUtils.getAsStringList(section.getOrDefault("list", List.of()));
|
||||
Key icon = Key.of(section.getOrDefault("icon", ItemKeys.STONE).toString());
|
||||
|
||||
@@ -37,7 +37,7 @@ public class LocalizedResourceConfigException extends LocalizedException {
|
||||
super.setArgument(0, path.toString());
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
public void setNode(String id) {
|
||||
super.setArgument(1, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,10 +257,10 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
Locale locale = TranslationManager.parseLocale(id.value());
|
||||
if (locale == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.i18n.unknown_locale", path, id);
|
||||
throw new LocalizedResourceConfigException("warning.config.i18n.unknown_locale");
|
||||
}
|
||||
|
||||
Map<String, String> bundle = new HashMap<>();
|
||||
@@ -288,7 +288,7 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
String langId = id.value().toLowerCase(Locale.ENGLISH);
|
||||
Map<String, String> sectionData = section.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class AbstractSoundManager implements SoundManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractSoundManager.this.songs.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.jukebox_song.duplicate");
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public abstract class AbstractSoundManager implements SoundManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Key id, Map<String, Object> section) {
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractSoundManager.this.byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.sound.duplicate");
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package net.momirealms.craftengine.core.util;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -280,4 +283,31 @@ public final class ResourceConfigUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void runCatching(Path configPath, String node, Runnable runnable, Supplier<String> config) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (LocalizedException e) {
|
||||
printWarningRecursively(e, configPath, node);
|
||||
} catch (Exception e) {
|
||||
String message = "Unexpected error loading file " + configPath + " - '" + node + "'.";
|
||||
if (config != null) {
|
||||
message += " Configuration details: " + config.get();
|
||||
}
|
||||
CraftEngine.instance().logger().warn(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void printWarningRecursively(LocalizedException e, Path path, String node) {
|
||||
for (Throwable t : e.getSuppressed()) {
|
||||
if (t instanceof LocalizedException suppressed) {
|
||||
printWarningRecursively(suppressed, path, node);
|
||||
}
|
||||
}
|
||||
if (e instanceof LocalizedResourceConfigException exception) {
|
||||
exception.setPath(path);
|
||||
exception.setNode(node);
|
||||
}
|
||||
TranslationManager.instance().log(e.node(), e.arguments());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user