9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-27 10:59:07 +00:00

添加基础验证器

This commit is contained in:
XiaoMoMi
2025-06-10 05:32:36 +08:00
parent 614f482a1e
commit 3a06814a68
5 changed files with 273 additions and 5 deletions

View File

@@ -353,5 +353,8 @@ warning.config.selector.invalid_target: "<yellow>Issue found in file <arg:0> - T
warning.config.resource_pack.item_model.conflict.vanilla: "<yellow>Failed to generate item model for '<arg:0>' because this item model has been occupied by a vanilla item.</yellow>"
warning.config.resource_pack.item_model.already_exist: "<yellow>Failed to generate item model for '<arg:0>' because the file '<arg:1>' already exists.</yellow>"
warning.config.resource_pack.model.generation.already_exist: "<yellow>Failed to generate model because the model file '<arg:0>' already exists.</yellow>"
warning.config.resource_pack.generation.missing_texture: "<yellow>Missing texture: '<arg:0>'.</yellow>"
warning.config.resource_pack.generation.missing_model: "<yellow>Missing model: '<arg:0>'.</yellow>"
warning.config.resource_pack.generation.missing_font_texture: "<yellow>Font '<arg:0>' is missing required texture: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_model_texture: "<yellow>Model '<arg:0>' is missing texture '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_item_model: "<yellow>Item '<arg:0>' is missing model file: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_block_model: "<yellow>Block '<arg:0>' is missing model file: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_parent_model: "<yellow>Model '<arg:0>' cannot find parent model: '<arg:1>'</yellow>"

View File

@@ -352,4 +352,9 @@ warning.config.selector.invalid_type: "<yellow>在文件 <arg:0> 中发现问题
warning.config.selector.invalid_target: "<yellow>在文件 <arg:0> 中发现问题 - 配置项 '<arg:1>' 使用了无效的选择器目标 '<arg:2>'</yellow>"
warning.config.resource_pack.item_model.conflict.vanilla: "<yellow>无法为 '<arg:0>' 生成物品模型,因为该物品模型已被原版物品占用</yellow>"
warning.config.resource_pack.item_model.already_exist: "<yellow>无法为 '<arg:0>' 生成物品模型,因为文件 '<arg:1>' 已存在</yellow>"
warning.config.resource_pack.model.generation.already_exist: "<yellow>无法生成模型,因为模型文件 '<arg:0>' 已存在</yellow>"
warning.config.resource_pack.model.generation.already_exist: "<yellow>无法生成模型,因为模型文件 '<arg:0>' 已存在</yellow>"
warning.config.resource_pack.generation.missing_font_texture: "<yellow>字体'<arg:0>'缺少必要纹理: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_model_texture: "<yellow>模型'<arg:0>'缺少纹理'<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_item_model: "<yellow>物品'<arg:0>'缺少模型文件: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_block_model: "<yellow>方块'<arg:0>'缺少模型文件: '<arg:1>'</yellow>"
warning.config.resource_pack.generation.missing_parent_model: "<yellow>模型'<arg:0>'找不到父级模型文件: '<arg:1>'</yellow>"

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.pack;
import com.google.common.collect.*;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.google.gson.*;
@@ -20,6 +21,7 @@ import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel;
import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration;
import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator;
import net.momirealms.craftengine.core.pack.obfuscation.ObfA;
import net.momirealms.craftengine.core.pack.obfuscation.ObfH;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
@@ -111,7 +113,7 @@ public abstract class AbstractPackManager implements PackManager {
VANILLA_TEXTURES.add(Key.of("minecraft", "trims/items/" + trimItem + "_" + trimColorPalette));
}
}
loadInternalList("models", "block/", VANILLA_MODELS::add);
loadInternalList("models", "item/", VANILLA_MODELS::add);
}
@@ -594,6 +596,9 @@ public abstract class AbstractPackManager implements PackManager {
this.generateClientLang(generatedPackPath);
this.generateEquipments(generatedPackPath);
this.generateParticle(generatedPackPath);
if (Config.validateResourcePack()) {
this.validateResourcePack(generatedPackPath);
}
Path finalPath = resourcePackPath();
Files.createDirectories(finalPath.getParent());
try {
@@ -607,6 +612,234 @@ public abstract class AbstractPackManager implements PackManager {
}
}
@SuppressWarnings("DuplicatedCode")
private void validateResourcePack(Path path) throws IOException {
List<Path> rootPaths = FileUtils.collectOverlays(path);
Multimap<Key, Key> imageToFonts = ArrayListMultimap.create(); // 图片到字体的映射
Multimap<Key, Key> modelToItems = ArrayListMultimap.create(); // 模型到物品的映射
Multimap<Key, String> modelToBlocks = ArrayListMultimap.create(); // 模型到方块的映射
Multimap<Key, Key> imageToModels = ArrayListMultimap.create(); // 图片到模型的映射
for (Path rootPath : rootPaths) {
Path assetsPath = rootPath.resolve("assets");
if (!Files.isDirectory(assetsPath)) continue;
for (Path namespacePath : FileUtils.collectNamespaces(assetsPath)) {
Path fontPath = namespacePath.resolve("font");
if (Files.isDirectory(fontPath)) {
Files.walkFileTree(fontPath, new SimpleFileVisitor<>() {
@Override
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
if (!isJsonFile(file)) return FileVisitResult.CONTINUE;
JsonObject fontJson = GsonHelper.readJsonFile(file).getAsJsonObject();
JsonArray providers = fontJson.getAsJsonArray("providers");
if (providers != null) {
Key fontName = Key.of(namespacePath.getFileName().toString(), FileUtils.pathWithoutExtension(file.getFileName().toString()));
for (JsonElement provider : providers) {
if (provider instanceof JsonObject providerJO && providerJO.has("type")) {
String type = providerJO.get("type").getAsString();
if (type.equals("bitmap") && providerJO.has("file")) {
String pngFile = providerJO.get("file").getAsString();
Key resourceLocation = Key.of(FileUtils.pathWithoutExtension(pngFile));
imageToFonts.put(resourceLocation, fontName);
}
}
}
}
return FileVisitResult.CONTINUE;
}
});
}
Path itemsPath = namespacePath.resolve("items");
if (Files.isDirectory(itemsPath)) {
Files.walkFileTree(itemsPath, new SimpleFileVisitor<>() {
@Override
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
if (!isJsonFile(file)) return FileVisitResult.CONTINUE;
JsonObject itemJson = GsonHelper.readJsonFile(file).getAsJsonObject();
Key item = Key.of(namespacePath.getFileName().toString(), FileUtils.pathWithoutExtension(file.getFileName().toString()));
collectItemModelsDeeply(itemJson, (resourceLocation) -> modelToItems.put(resourceLocation, item));
return FileVisitResult.CONTINUE;
}
});
}
Path blockStatesPath = namespacePath.resolve("blockstates");
if (Files.isDirectory(blockStatesPath)) {
Files.walkFileTree(blockStatesPath, new SimpleFileVisitor<>() {
@Override
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
if (!isJsonFile(file)) return FileVisitResult.CONTINUE;
String blockId = FileUtils.pathWithoutExtension(file.getFileName().toString());
JsonObject blockStateJson = GsonHelper.readJsonFile(file).getAsJsonObject();
if (blockStateJson.has("multipart")) {
collectMultipart(blockStateJson.getAsJsonArray("multipart"), (location) -> modelToBlocks.put(location, blockId));
} else if (blockStateJson.has("variants")) {
collectVariants(blockId, blockStateJson.getAsJsonObject("variants"), modelToBlocks::put);
}
return FileVisitResult.CONTINUE;
}
});
}
}
}
label: for (Map.Entry<Key, Collection<Key>> entry : imageToFonts.asMap().entrySet()) {
Key key = entry.getKey();
if (VANILLA_TEXTURES.contains(key)) continue;
String imagePath = "assets/" + key.namespace() + "/textures/" + key.value() + ".png";
for (Path rootPath : rootPaths) {
if (Files.exists(rootPath.resolve(imagePath))) {
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_font_texture", entry.getValue().stream().distinct().toList().toString(), imagePath);
}
label: for (Map.Entry<Key, Collection<Key>> entry : modelToItems.asMap().entrySet()) {
Key key = entry.getKey();
String modelPath = "assets/" + key.namespace() + "/models/" + key.value() + ".json";
if (VANILLA_MODELS.contains(key)) continue;
for (Path rootPath : rootPaths) {
Path modelJsonPath = rootPath.resolve(modelPath);
if (Files.exists(rootPath.resolve(modelPath))) {
collectModels(key, GsonHelper.readJsonFile(modelJsonPath).getAsJsonObject(), rootPaths, imageToModels);
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_item_model", entry.getValue().stream().distinct().toList().toString(), modelPath);
}
label: for (Map.Entry<Key, Collection<String>> entry : modelToBlocks.asMap().entrySet()) {
Key key = entry.getKey();
String modelPath = "assets/" + key.namespace() + "/models/" + key.value() + ".json";
if (VANILLA_MODELS.contains(key)) continue;
for (Path rootPath : rootPaths) {
Path modelJsonPath = rootPath.resolve(modelPath);
if (Files.exists(modelJsonPath)) {
collectModels(key, GsonHelper.readJsonFile(modelJsonPath).getAsJsonObject(), rootPaths, imageToModels);
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_block_model", entry.getValue().stream().distinct().toList().toString(), modelPath);
}
label: for (Map.Entry<Key, Collection<Key>> entry : imageToModels.asMap().entrySet()) {
Key key = entry.getKey();
if (VANILLA_TEXTURES.contains(key)) continue;
String imagePath = "assets/" + key.namespace() + "/textures/" + key.value() + ".png";
for (Path rootPath : rootPaths) {
if (Files.exists(rootPath.resolve(imagePath))) {
continue label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_model_texture", entry.getValue().stream().distinct().toList().toString(), imagePath);
}
}
private static void collectModels(Key model, JsonObject modelJson, List<Path> rootPaths, Multimap<Key, Key> imageToModels) throws IOException {
if (modelJson.has("parent")) {
Key parentResourceLocation = Key.from(modelJson.get("parent").getAsString());
if (!VANILLA_MODELS.contains(parentResourceLocation)) {
String parentModelPath = "assets/" + parentResourceLocation.namespace() + "/models/" + parentResourceLocation.value() + ".json";
label: {
for (Path rootPath : rootPaths) {
Path modelJsonPath = rootPath.resolve(parentModelPath);
if (Files.exists(modelJsonPath)) {
collectModels(parentResourceLocation, GsonHelper.readJsonFile(modelJsonPath).getAsJsonObject(), rootPaths, imageToModels);
break label;
}
}
TranslationManager.instance().log("warning.config.resource_pack.generation.missing_parent_model", model.asString(), parentModelPath);
}
}
}
if (modelJson.has("textures")) {
JsonObject textures = modelJson.get("textures").getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : textures.entrySet()) {
// String textureId = entry.getKey();
String value = entry.getValue().getAsString();
Key textureResourceLocation = Key.from(value);
imageToModels.put(textureResourceLocation, model);
}
}
}
private static void collectMultipart(JsonArray jsonArray, Consumer<Key> callback) {
for (JsonElement element : jsonArray) {
if (element instanceof JsonObject jo) {
JsonElement applyJE = jo.get("apply");
if (applyJE instanceof JsonObject applyJO) {
String modelPath = applyJO.get("model").getAsString();
Key location = Key.from(modelPath);
callback.accept(location);
} else if (applyJE instanceof JsonArray applyJA) {
for (JsonElement applyInnerJE : applyJA) {
if (applyInnerJE instanceof JsonObject applyInnerJO) {
String modelPath = applyInnerJO.get("model").getAsString();
Key location = Key.from(modelPath);
callback.accept(location);
}
}
}
}
}
}
private static void collectVariants(String block, JsonObject jsonObject, BiConsumer<Key, String> callback) {
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
if (entry.getValue() instanceof JsonObject entryJO) {
String modelPath = entryJO.get("model").getAsString();
Key location = Key.from(modelPath);
callback.accept(location, block + "[" + entry.getKey() + "]");
} else if (entry.getValue() instanceof JsonArray entryJA) {
for (JsonElement entryInnerJE : entryJA) {
if (entryInnerJE instanceof JsonObject entryJO) {
String modelPath = entryJO.get("model").getAsString();
Key location = Key.from(modelPath);
callback.accept(location, block + "[" + entry.getKey() + "]");
}
}
}
}
}
private static boolean isJsonFile(Path filePath) {
String fileName = filePath.getFileName().toString();
return fileName.endsWith(".json") || fileName.endsWith(".mcmeta");
}
private static void collectItemModelsDeeply(JsonObject jo, Consumer<Key> callback) {
JsonElement modelJE = jo.get("model");
if (modelJE instanceof JsonPrimitive jsonPrimitive) {
Key location = Key.from(jsonPrimitive.getAsString());
callback.accept(location);
return;
}
if (jo.has("type") && jo.has("base")) {
if (jo.get("type") instanceof JsonPrimitive jp1 && jo.get("base") instanceof JsonPrimitive jp2) {
String type = jp1.getAsString();
if (type.equals("minecraft:special") || type.equals("special")) {
Key location = Key.from(jp2.getAsString());
callback.accept(location);
}
}
}
for (Map.Entry<String, JsonElement> entry : jo.entrySet()) {
if (entry.getValue() instanceof JsonObject innerJO) {
collectItemModelsDeeply(innerJO, callback);
} else if (entry.getValue() instanceof JsonArray innerJA) {
for (JsonElement innerElement : innerJA) {
if (innerElement instanceof JsonObject innerJO) {
collectItemModelsDeeply(innerJO, callback);
}
}
}
}
}
private void generateParticle(Path generatedPackPath) {
if (!Config.removeTintedLeavesParticle()) return;
if (Config.packMaxVersion() < 21.49f) return;

View File

@@ -1,5 +1,8 @@
package net.momirealms.craftengine.core.util;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.momirealms.craftengine.core.pack.ResourceLocation;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -57,4 +60,28 @@ public class FileUtils {
throw new RuntimeException("Failed to traverse directory: " + configFolder, e);
}
}
public static List<Path> collectOverlays(Path resourcePackFolder) throws IOException {
List<Path> folders = new ObjectArrayList<>();
folders.add(resourcePackFolder);
try (Stream<Path> paths = Files.list(resourcePackFolder)) {
folders.addAll(paths
.filter(Files::isDirectory)
.filter(path -> !path.getFileName().toString().equals("assets"))
.filter(path -> Files.exists(path.resolve("assets")))
.toList());
}
return folders;
}
public static List<Path> collectNamespaces(Path assetsFolder) throws IOException {
List<Path> folders;
try (Stream<Path> paths = Files.list(assetsFolder)) {
folders = new ObjectArrayList<>(paths
.filter(Files::isDirectory)
.filter(path -> ResourceLocation.isValidNamespace(path.getFileName().toString()))
.toList());
}
return folders;
}
}

View File

@@ -41,7 +41,7 @@ commons_io_version=2.18.0
commons_imaging_version=1.0.0-alpha6
commons_lang3_version=3.17.0
sparrow_nbt_version=0.9.1
sparrow_util_version=0.49
sparrow_util_version=0.49.1
fastutil_version=8.5.15
netty_version=4.1.121.Final
joml_version=1.10.8