From f2042dc9c8b219870093dc4c3aa557c18c5f761e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 17 Dec 2025 19:26:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E9=AA=8C=E8=AF=81=E9=80=BB?= =?UTF-8?q?=E8=BE=911?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 10 +- .../core/pack/AbstractPackManager.java | 83 ++++++++----- .../resolution/ResolutionMergePackMcMeta.java | 110 ++++-------------- .../craftengine/core/pack/mcmeta/Overlay.java | 106 +++++++++++++++++ .../core/pack/mcmeta/PackMcMeta.java | 36 ++++++ .../core/pack/mcmeta/PackVersion.java | 87 ++++++++++++++ .../pack/overlay/ResourcePackOverlay.java | 32 ----- .../core/pack/revision/Revision.java | 10 +- .../core/plugin/config/Config.java | 6 + .../craftengine/core/util/FileUtils.java | 15 ++- .../craftengine/core/util/ListUtils.java | 11 ++ .../core/util/MinecraftVersion.java | 48 ++++---- gradle.properties | 6 +- 13 files changed, 383 insertions(+), 177 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/Overlay.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackMcMeta.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackVersion.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/overlay/ResourcePackOverlay.java diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 34d59ee8b..2d2a2967c 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -98,9 +98,17 @@ resource-pack: # If your resource pack is compliant with the standard, you can disable validation to improve the resource pack generation speed. validation: enable: true + # Determines on which versions the resource pack validation will be performed. + # Allowed values: + # - 1.20.1, 1.21, 1.21.8, etc. + # - latest: the latest client version + # - server: the current server version + test-versions: + - server # Fix textures that are not within the atlas. It is unreasonable to always rely on plugins to fix your mistakes. + # CraftEngine will only fix the atlas of resource pack under the first version specified in the test-versions. # You should strive to make your resource pack more standardized after gaining some experience with resource packs. - # When a model file mixes textures from both the blocks atlas and the items atlas, you must manually fix the issue. + # The fix-atlas feature is not all-powerful since 1.21.11. In some cases, CraftEngine cannot fix it for you, and you will need to fix your model yourself. fix-atlas: true # Optimize your resource pack by reducing its size without any quality loss. optimization: diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index e45b16ae8..01d973b46 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -18,6 +18,8 @@ import net.momirealms.craftengine.core.pack.conflict.resolution.ResolutionCondit import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.pack.host.impl.NoneHost; +import net.momirealms.craftengine.core.pack.mcmeta.Overlay; +import net.momirealms.craftengine.core.pack.mcmeta.PackMcMeta; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.ModernItemModel; @@ -791,7 +793,9 @@ public abstract class AbstractPackManager implements PackManager { long time2 = System.currentTimeMillis(); this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.generate", String.valueOf(time2 - time1))); if (Config.validateResourcePack()) { - this.validateResourcePack(generatedPackPath); + for (MinecraftVersion version : Config.validationTestVersions()) { + this.validateResourcePack(generatedPackPath, version); + } } long time3 = System.currentTimeMillis(); this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.validate", String.valueOf(time3 - time2))); @@ -825,10 +829,10 @@ public abstract class AbstractPackManager implements PackManager { if (!rawMeta.has("pack")) { JsonObject pack = new JsonObject(); rawMeta.add("pack", pack); - pack.addProperty("pack_format", Config.packMinVersion().packFormat()); + pack.addProperty("pack_format", Config.packMinVersion().packFormat().major()); JsonObject supportedFormats = new JsonObject(); - supportedFormats.addProperty("min_inclusive", Config.packMinVersion().packFormat()); - supportedFormats.addProperty("max_inclusive", Config.packMaxVersion().packFormat()); + supportedFormats.addProperty("min_inclusive", Config.packMinVersion().packFormat().major()); + supportedFormats.addProperty("max_inclusive", Config.packMaxVersion().packFormat().major()); pack.add("supported_formats", supportedFormats); changed = true; } @@ -869,7 +873,7 @@ public abstract class AbstractPackManager implements PackManager { private void removeAllShaders(Path path) { List rootPaths; try { - rootPaths = FileUtils.collectOverlays(path); + rootPaths = MiscUtils.init(FileUtils.collectOverlays(path), a -> a.addFirst(path)); } catch (IOException e) { plugin.logger().warn("Failed to collect overlays for " + path.toAbsolutePath(), e); return; @@ -889,7 +893,7 @@ public abstract class AbstractPackManager implements PackManager { // 收集全部overlay Path[] rootPaths; try { - rootPaths = FileUtils.collectOverlays(path).toArray(new Path[0]); + rootPaths = MiscUtils.init(FileUtils.collectOverlays(path), a -> a.addFirst(path)).toArray(new Path[0]); } catch (IOException e) { this.plugin.logger().warn("Failed to collect overlays for " + path.toAbsolutePath(), e); return; @@ -1174,16 +1178,37 @@ public abstract class AbstractPackManager implements PackManager { } @SuppressWarnings("DuplicatedCode") - private void validateResourcePack(Path path) { - // 收集全部overlay - Path[] rootPaths; + private void validateResourcePack(Path path, MinecraftVersion version) { + Path packMcMetaPath = path.resolve("pack.mcmeta"); + PackMcMeta packMeta; try { - rootPaths = FileUtils.collectOverlays(path).toArray(new Path[0]); + packMeta = new PackMcMeta(GsonHelper.readJsonFile(packMcMetaPath).getAsJsonObject()); } catch (IOException e) { - this.plugin.logger().warn("Failed to collect overlays for " + path.toAbsolutePath(), e); + this.plugin.logger().warn("Failed to read pack.mcmeta " + packMcMetaPath.toAbsolutePath(), e); return; } + // 获取当前版本生效的overlays + List overlayDirectories = new ArrayList<>(); + for (Overlay overlay : packMeta.overlays()) { + if (overlay.test(version)) { + overlayDirectories.add(overlay.directory()); + } + } + overlayDirectories = overlayDirectories.stream().distinct().toList(); + + List rootPathList = new ArrayList<>(); + rootPathList.add(path); + for (String directory : overlayDirectories) { + Path resolve = path.resolve(directory); + if (Files.isDirectory(resolve)) { + rootPathList.add(resolve); + } + } + + // 收集全部overlay,按照正确的顺序加载 + Path[] rootPaths = rootPathList.toArray(new Path[0]); + Multimap glyphToFonts = HashMultimap.create(128, 32); // 图片到字体的映射 Multimap modelToItemDefinitions = HashMultimap.create(128, 4); // 模型到物品的映射 Multimap modelToBlockStates = HashMultimap.create(128, 32); // 模型到方块的映射 @@ -1191,8 +1216,11 @@ public abstract class AbstractPackManager implements PackManager { Multimap textureToEquipments = HashMultimap.create(128, 8); // 纹理到盔甲的映射 Multimap oggToSoundEvents = HashMultimap.create(128, 4); // 音频到声音的映射 - Map blockAtlasJsons = new LinkedHashMap<>(); - Map itemAtlasJsons = new LinkedHashMap<>(); + Map blockAtlasJsons = new HashMap<>(); + Map itemAtlasJsons = new HashMap<>(); + + JsonObject lastBlocksAtlas = null; + JsonObject lastItemAtlas = null; // 如果需要验证资源包,则需要先读取所有atlas for (Path rootPath : rootPaths) { @@ -1205,6 +1233,7 @@ public abstract class AbstractPackManager implements PackManager { try { JsonObject atlasJsonObject = GsonHelper.readJsonFile(blockAtlasFile).getAsJsonObject(); blockAtlasJsons.put(blockAtlasFile, atlasJsonObject); + lastBlocksAtlas = atlasJsonObject; } catch (IOException | JsonParseException e) { TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", blockAtlasFile.toAbsolutePath().toString()); } @@ -1218,6 +1247,7 @@ public abstract class AbstractPackManager implements PackManager { try { JsonObject atlasJsonObject = GsonHelper.readJsonFile(itemAtlasFile).getAsJsonObject(); itemAtlasJsons.put(itemAtlasFile, atlasJsonObject); + lastItemAtlas = atlasJsonObject; } catch (IOException | JsonParseException e) { TranslationManager.instance().log("warning.config.resource_pack.generation.malformatted_json", itemAtlasFile.toAbsolutePath().toString()); } @@ -1242,16 +1272,15 @@ public abstract class AbstractPackManager implements PackManager { */ - Atlas blockAtlas = new Atlas(MiscUtils.make(new ArrayList<>(4), k -> { - k.add(blockAtlasJsons.get(defaultBlockAtlas)); - k.add(this.vanillaBlockAtlas); - return k; - })); - Atlas itemAtlas = new Atlas(MiscUtils.make(new ArrayList<>(4), k -> { - k.add(itemAtlasJsons.get(defaultItemAtlas)); - k.add(this.vanillaItemAtlas); - return k; - })); + Atlas blockAtlas; + Atlas itemAtlas; + if (version.isAtOrAbove(MinecraftVersions.V1_21_11)) { + blockAtlas = new Atlas(ListUtils.newNonNullList(lastBlocksAtlas, this.vanillaBlockAtlas)); + itemAtlas = new Atlas(ListUtils.newNonNullList(lastItemAtlas, this.vanillaItemAtlas)); + } else { + blockAtlas = new Atlas(ListUtils.newNonNullList(lastBlocksAtlas, this.vanillaBlockAtlas, this.vanillaItemAtlas)); + itemAtlas = null; + } for (Path rootPath : rootPaths) { Path assetsPath = rootPath.resolve("assets"); @@ -1564,7 +1593,7 @@ public abstract class AbstractPackManager implements PackManager { Key spritePath = texture.getValue(); // 方块纹理不应该在item图集内,这样必然出问题 - boolean definedInItemAtlas = itemAtlas.isDefined(spritePath); + boolean definedInItemAtlas = itemAtlas != null && itemAtlas.isDefined(spritePath); if (definedInItemAtlas) { TranslationManager.instance().log("warning.config.resource_pack.generation.multiple_atlases", entry.getKey().asString(), "minecraft:textures/atlas/blocks.png", @@ -1614,7 +1643,7 @@ public abstract class AbstractPackManager implements PackManager { for (Map.Entry texture : textures.entrySet()) { Key spritePath = texture.getValue(); boolean definedInBlockAtlas = blockAtlas.isDefined(spritePath); - boolean definedInItemAtlas = itemAtlas.isDefined(spritePath); + boolean definedInItemAtlas = itemAtlas != null && itemAtlas.isDefined(spritePath); // 双倍定义 if (definedInItemAtlas && definedInBlockAtlas) { @@ -1735,7 +1764,7 @@ public abstract class AbstractPackManager implements PackManager { itemAtlasesToFix.clear(); } - if (!itemAtlasesToFix.isEmpty()) { + if (!itemAtlasesToFix.isEmpty() && itemAtlas != null) { List sourcesToAdd = new ArrayList<>(itemAtlasesToFix.size()); for (Key itemTexture : itemAtlasesToFix.keySet()) { itemAtlas.addSingle(itemTexture); @@ -1809,7 +1838,7 @@ public abstract class AbstractPackManager implements PackManager { Map textures = entry.getValue().textures; for (Map.Entry texture : textures.entrySet()) { Key spritePath = texture.getValue(); - Key sourceTexturePath = itemAtlas.getSourceTexturePath(spritePath); + Key sourceTexturePath = itemAtlas == null ? null : itemAtlas.getSourceTexturePath(spritePath); if (sourceTexturePath != null) { textureToModels.put(sourceTexturePath, entry.getKey()); } else { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java index 959d233ce..c524ecebe 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/resolution/ResolutionMergePackMcMeta.java @@ -6,23 +6,25 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import net.momirealms.craftengine.core.pack.conflict.PathContext; +import net.momirealms.craftengine.core.pack.mcmeta.PackVersion; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Pair; -import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; +import static net.momirealms.craftengine.core.pack.mcmeta.PackVersion.MAX_PACK_VERSION; +import static net.momirealms.craftengine.core.pack.mcmeta.PackVersion.MIN_PACK_VERSION; + public class ResolutionMergePackMcMeta implements Resolution { public static final Factory FACTORY = new Factory(); public static final Set STANDARD_PACK_KEYS = ImmutableSet.of("pack", "features", "filter", "overlays", "language"); - public static final PackVersion MIN_PACK_VERSION = new PackVersion(15, 0); // 1.20 - public static final PackVersion MAX_PACK_VERSION = new PackVersion(1000, 0); // future + private final String description; public ResolutionMergePackMcMeta(String description) { @@ -164,87 +166,23 @@ public class ResolutionMergePackMcMeta implements Resolution { PackVersion max = supportedVersions.right(); // 旧版格式支持 JsonObject supportedFormats = new JsonObject(); - supportedFormats.addProperty("min_inclusive", min.major); - supportedFormats.addProperty("max_inclusive", max.major); + supportedFormats.addProperty("min_inclusive", min.major()); + supportedFormats.addProperty("max_inclusive", max.major()); entryJson.add("formats", supportedFormats); // 新版格式支持 JsonArray minFormat = new JsonArray(); - minFormat.add(min.major); - minFormat.add(min.minor); + minFormat.add(min.major()); + minFormat.add(min.minor()); entryJson.add("min_format", minFormat); JsonArray maxFormat = new JsonArray(); - maxFormat.add(max.major); - maxFormat.add(max.minor); + maxFormat.add(max.major()); + maxFormat.add(max.minor()); entryJson.add("max_format", maxFormat); overlayCollector.accept(entryJson); } } } - public record PackVersion(int major, int minor) implements Comparable { - - @Override - public int compareTo(@NotNull ResolutionMergePackMcMeta.PackVersion o) { - // 首先比较 major 版本 - int majorCompare = Integer.compare(this.major, o.major); - if (majorCompare != 0) { - return majorCompare; - } - // 如果 major 相同,则比较 minor 版本 - return Integer.compare(this.minor, o.minor); - } - - /** - * 返回两个版本中较小的那个(版本较低的) - */ - public static PackVersion getLower(PackVersion v1, PackVersion v2) { - if (v1 == null) return v2; - if (v2 == null) return v1; - return v1.compareTo(v2) <= 0 ? v1 : v2; - } - - /** - * 返回两个版本中较大的那个(版本较高的) - */ - public static PackVersion getHigher(PackVersion v1, PackVersion v2) { - if (v1 == null) return v2; - if (v2 == null) return v1; - return v1.compareTo(v2) >= 0 ? v1 : v2; - } - - public static PackVersion getLowest(List versions) { - if (versions == null || versions.isEmpty()) { - return MIN_PACK_VERSION; - } - - PackVersion lowest = versions.getFirst(); - for (int i = 1; i < versions.size(); i++) { - lowest = getLower(lowest, versions.get(i)); - } - return lowest; - } - - public static PackVersion getHighest(List versions) { - if (versions == null || versions.isEmpty()) { - return MAX_PACK_VERSION; - } - - PackVersion highest = versions.getFirst(); - for (int i = 1; i < versions.size(); i++) { - highest = getHigher(highest, versions.get(i)); - } - return highest; - } - - public static PackVersion parse(float num) { - String str = String.valueOf(num); - String[] parts = str.split("\\."); - int integerPart = Integer.parseInt(parts[0]); - int decimalPart = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; - return new PackVersion(integerPart, decimalPart); - } - } - public static void mergePack(JsonObject merged, JsonObject pack1, JsonObject pack2) { Pair pack1Version = getSupportedVersions(pack1); Pair pack2Version = getSupportedVersions(pack2); @@ -252,18 +190,18 @@ public class ResolutionMergePackMcMeta implements Resolution { PackVersion max = PackVersion.getHigher(pack1Version.right(), pack2Version.right()); // 旧版格式支持 JsonObject supportedFormats = new JsonObject(); - supportedFormats.addProperty("min_inclusive", min.major); - supportedFormats.addProperty("max_inclusive", max.major); + supportedFormats.addProperty("min_inclusive", min.major()); + supportedFormats.addProperty("max_inclusive", max.major()); merged.add("supported_formats", supportedFormats); - merged.addProperty("pack_format", min.major); + merged.addProperty("pack_format", min.major()); // 新版格式支持 JsonArray minFormat = new JsonArray(); - minFormat.add(min.major); - minFormat.add(min.minor); + minFormat.add(min.major()); + minFormat.add(min.minor()); merged.add("min_format", minFormat); JsonArray maxFormat = new JsonArray(); - maxFormat.add(max.major); - maxFormat.add(max.minor); + maxFormat.add(max.major()); + maxFormat.add(max.minor()); merged.add("max_format", maxFormat); } @@ -307,15 +245,15 @@ public class ResolutionMergePackMcMeta implements Resolution { case JsonArray array -> { if (array.isEmpty()) return Pair.of(MIN_PACK_VERSION, MAX_PACK_VERSION); if (array.size() == 1) { - return new Pair<>(new PackVersion(GsonHelper.getAsInt(array.get(0), MIN_PACK_VERSION.major), 0), MAX_PACK_VERSION); + return new Pair<>(new PackVersion(GsonHelper.getAsInt(array.get(0), MIN_PACK_VERSION.major()), 0), MAX_PACK_VERSION); } if (array.size() == 2) { - return new Pair<>(new PackVersion(GsonHelper.getAsInt(array.get(0), MIN_PACK_VERSION.major), 0), new PackVersion(GsonHelper.getAsInt(array.get(1), MAX_PACK_VERSION.major), 0)); + return new Pair<>(new PackVersion(GsonHelper.getAsInt(array.get(0), MIN_PACK_VERSION.major()), 0), new PackVersion(GsonHelper.getAsInt(array.get(1), MAX_PACK_VERSION.major()), 0)); } } case JsonObject object -> { - int min = GsonHelper.getAsInt(object.get("min_inclusive"), MIN_PACK_VERSION.major); - int max = GsonHelper.getAsInt(object.get("max_inclusive"), MAX_PACK_VERSION.major); + int min = GsonHelper.getAsInt(object.get("min_inclusive"), MIN_PACK_VERSION.major()); + int max = GsonHelper.getAsInt(object.get("max_inclusive"), MAX_PACK_VERSION.major()); return new Pair<>(new PackVersion(min, 0), new PackVersion(max, 0)); } default -> { @@ -328,10 +266,10 @@ public class ResolutionMergePackMcMeta implements Resolution { if (format instanceof JsonArray array) { if (array.isEmpty()) return defaultVersion; if (array.size() == 1) { - return new PackVersion(GsonHelper.getAsInt(array.get(0), defaultVersion.major), 0); + return new PackVersion(GsonHelper.getAsInt(array.get(0), defaultVersion.major()), 0); } if (array.size() == 2) { - return new PackVersion(GsonHelper.getAsInt(array.get(0), defaultVersion.major), GsonHelper.getAsInt(array.get(1), defaultVersion.minor)); + return new PackVersion(GsonHelper.getAsInt(array.get(0), defaultVersion.major()), GsonHelper.getAsInt(array.get(1), defaultVersion.minor())); } } else if (format instanceof JsonPrimitive jsonPrimitive) { float version = jsonPrimitive.getAsFloat(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/Overlay.java b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/Overlay.java new file mode 100644 index 000000000..6b4a9d470 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/Overlay.java @@ -0,0 +1,106 @@ +package net.momirealms.craftengine.core.pack.mcmeta; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.craftengine.core.util.MinecraftVersion; +import net.momirealms.craftengine.core.util.Pair; + +import java.util.ArrayList; +import java.util.List; + +public class Overlay { + private final PackVersion minVersion; + private final PackVersion maxVersion; + private final String directory; + + public Overlay(PackVersion minVersion, + PackVersion maxVersion, + String directory + ) { + this.minVersion = minVersion; + this.maxVersion = maxVersion; + this.directory = directory; + } + + public Overlay(JsonObject overlay) { + this.directory = overlay.get("directory").getAsString(); + Pair supportedVersions = getSupportedVersions(overlay); + this.minVersion = supportedVersions.left(); + this.maxVersion = supportedVersions.right(); + } + + public PackVersion minVersion() { + return minVersion; + } + + public PackVersion maxVersion() { + return maxVersion; + } + + public String directory() { + return directory; + } + + public boolean test(MinecraftVersion version) { + return version.packFormat().isAtOrAbove(this.minVersion) && version.packFormat().isAtOrBelow(this.maxVersion); + } + + private static Pair getSupportedVersions(JsonObject pack) { + List minVersions = new ArrayList<>(); + List maxVersions = new ArrayList<>(); + if (pack.has("min_format")) { + minVersions.add(getFormatVersion(pack.get("min_format"))); + } + if (pack.has("max_format")) { + maxVersions.add(getFormatVersion(pack.get("max_format"))); + } + if (pack.has("formats")) { + Pair supportedFormats = parseSupportedFormats(pack.get("formats")); + minVersions.add(supportedFormats.left()); + maxVersions.add(supportedFormats.right()); + } + return Pair.of( + PackVersion.getLowest(minVersions), + PackVersion.getHighest(maxVersions) + ); + } + + private static PackVersion getFormatVersion(JsonElement format) { + if (format instanceof JsonArray array) { + if (array.size() == 1) { + return new PackVersion(GsonHelper.getAsInt(array.get(0), 15), 0); + } + if (array.size() == 2) { + return new PackVersion(GsonHelper.getAsInt(array.get(0), 15), GsonHelper.getAsInt(array.get(1), 1000)); + } + } else if (format instanceof JsonPrimitive jsonPrimitive) { + float version = jsonPrimitive.getAsFloat(); + return PackVersion.parse(version); + } + throw new IllegalArgumentException("Unknown overlay version format: " + format); + } + + private static Pair parseSupportedFormats(JsonElement formats) { + switch (formats) { + case JsonPrimitive jsonPrimitive -> { + return new Pair<>(new PackVersion(jsonPrimitive.getAsInt(), 0), new PackVersion(jsonPrimitive.getAsInt(), 0)); + } + case JsonArray array -> { + if (array.size() == 2) { + return new Pair<>(new PackVersion(GsonHelper.getAsInt(array.get(0), 15), 0), new PackVersion(GsonHelper.getAsInt(array.get(1), 1000), 0)); + } + } + case JsonObject object -> { + int min = GsonHelper.getAsInt(object.get("min_inclusive"), 15); + int max = GsonHelper.getAsInt(object.get("max_inclusive"), 1000); + return new Pair<>(new PackVersion(min, 0), new PackVersion(max, 0)); + } + default -> { + } + } + throw new IllegalArgumentException("Unsupported overlay version format: " + formats); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackMcMeta.java b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackMcMeta.java new file mode 100644 index 000000000..506b150fa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackMcMeta.java @@ -0,0 +1,36 @@ +package net.momirealms.craftengine.core.pack.mcmeta; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.List; + +public class PackMcMeta { + private final List overlays; + + public PackMcMeta(JsonObject mcmeta) { + this.overlays = getOverlays(mcmeta); + } + + private List getOverlays(JsonObject mcmeta) { + List overlays = new ArrayList<>(); + JsonObject overlaysJson = mcmeta.getAsJsonObject("overlays"); + if (overlaysJson != null) { + JsonArray entries = overlaysJson.getAsJsonArray("entries"); + if (entries != null) { + for (JsonElement overlayJson : entries) { + if (overlayJson instanceof JsonObject overlayJsonObj) { + overlays.add(new Overlay(overlayJsonObj)); + } + } + } + } + return overlays; + } + + public List overlays() { + return this.overlays; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackVersion.java b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackVersion.java new file mode 100644 index 000000000..609823392 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/mcmeta/PackVersion.java @@ -0,0 +1,87 @@ +package net.momirealms.craftengine.core.pack.mcmeta; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public record PackVersion(int major, int minor) implements Comparable { + public static final PackVersion MIN_PACK_VERSION = new PackVersion(15, 0); // 1.20 + public static final PackVersion MAX_PACK_VERSION = new PackVersion(1000, 0); // future + + @Override + public int compareTo(@NotNull PackVersion o) { + // 首先比较 major 版本 + int majorCompare = Integer.compare(this.major, o.major); + if (majorCompare != 0) { + return majorCompare; + } + // 如果 major 相同,则比较 minor 版本 + return Integer.compare(this.minor, o.minor); + } + + public boolean isAtOrAbove(PackVersion other) { + return this.compareTo(other) >= 0; + } + + public boolean isAbove(PackVersion other) { + return this.compareTo(other) > 0; + } + + public boolean isAtOrBelow(PackVersion other) { + return this.compareTo(other) <= 0; + } + + public boolean isBelow(PackVersion other) { + return this.compareTo(other) < 0; + } + + /** + * 返回两个版本中较小的那个(版本较低的) + */ + public static PackVersion getLower(PackVersion v1, PackVersion v2) { + if (v1 == null) return v2; + if (v2 == null) return v1; + return v1.compareTo(v2) <= 0 ? v1 : v2; + } + + /** + * 返回两个版本中较大的那个(版本较高的) + */ + public static PackVersion getHigher(PackVersion v1, PackVersion v2) { + if (v1 == null) return v2; + if (v2 == null) return v1; + return v1.compareTo(v2) >= 0 ? v1 : v2; + } + + public static PackVersion getLowest(List versions) { + if (versions == null || versions.isEmpty()) { + return MIN_PACK_VERSION; + } + + PackVersion lowest = versions.getFirst(); + for (int i = 1; i < versions.size(); i++) { + lowest = getLower(lowest, versions.get(i)); + } + return lowest; + } + + public static PackVersion getHighest(List versions) { + if (versions == null || versions.isEmpty()) { + return MAX_PACK_VERSION; + } + + PackVersion highest = versions.getFirst(); + for (int i = 1; i < versions.size(); i++) { + highest = getHigher(highest, versions.get(i)); + } + return highest; + } + + public static PackVersion parse(float num) { + String str = String.valueOf(num); + String[] parts = str.split("\\."); + int integerPart = Integer.parseInt(parts[0]); + int decimalPart = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; + return new PackVersion(integerPart, decimalPart); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/overlay/ResourcePackOverlay.java b/core/src/main/java/net/momirealms/craftengine/core/pack/overlay/ResourcePackOverlay.java deleted file mode 100644 index 960c7da75..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/overlay/ResourcePackOverlay.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.momirealms.craftengine.core.pack.overlay; - -import net.momirealms.craftengine.core.util.MinecraftVersion; - -import java.nio.file.Path; - -public class ResourcePackOverlay { - private final MinecraftVersion minVersion; - private final MinecraftVersion maxVersion; - private final Path folder; - - public ResourcePackOverlay(MinecraftVersion minVersion, - MinecraftVersion maxVersion, - Path folder - ) { - this.minVersion = minVersion; - this.maxVersion = maxVersion; - this.folder = folder; - } - - public MinecraftVersion minVersion() { - return minVersion; - } - - public MinecraftVersion maxVersion() { - return maxVersion; - } - - public Path folder() { - return folder; - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revision.java b/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revision.java index 731d3f77e..949174f6f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revision.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revision.java @@ -60,12 +60,14 @@ public interface Revision { @Override public int maxPackVersion() { - return MinecraftVersions.FUTURE.packFormat(); + // todo 重构revision系统 + return MinecraftVersions.FUTURE.packFormat().major(); } @Override public int minPackVersion() { - return this.minVersion.packFormat(); + // todo 重构revision系统 + return this.minVersion.packFormat().major(); } @Override @@ -112,12 +114,12 @@ public interface Revision { @Override public int minPackVersion() { - return this.minVersion.packFormat(); + return this.minVersion.packFormat().major(); } @Override public int maxPackVersion() { - return this.maxVersion.packFormat(); + return this.maxVersion.packFormat().major(); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 6bd5d3ad9..298a48d7d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -79,6 +79,7 @@ public class Config { protected boolean resource_pack$validation$enable; protected boolean resource_pack$validation$fix_atlas; + protected List resource_pack$validation$test_versions; protected boolean resource_pack$exclude_core_shaders; protected boolean resource_pack$protection$obfuscation$enable; @@ -391,6 +392,7 @@ public class Config { }).collect(Collectors.toSet()); resource_pack$validation$enable = config.getBoolean("resource-pack.validation.enable", true); resource_pack$validation$fix_atlas = config.getBoolean("resource-pack.validation.fix-atlas", true); + resource_pack$validation$test_versions = config.getStringList("resource-pack.validation.test-versions", List.of("server")).stream().map(Config::getVersion).distinct().toList(); resource_pack$exclude_core_shaders = config.getBoolean("resource-pack.exclude-core-shaders", false); resource_pack$overlay_format = config.getString("resource-pack.overlay-format", "overlay_{version}"); if (!resource_pack$overlay_format.contains("{version}")) { @@ -1110,6 +1112,10 @@ public class Config { return instance.resource_pack$validation$fix_atlas; } + public static List validationTestVersions() { + return instance.resource_pack$validation$test_versions; + } + public static boolean excludeShaders() { return instance.resource_pack$exclude_core_shaders; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java index e98fb261c..8ae7c6b3c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java @@ -10,6 +10,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.Comparator; import java.util.EnumSet; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Stream; public class FileUtils { @@ -67,9 +68,21 @@ public class FileUtils { } } + public static List collectOverlays(Path resourcePackFolder, Predicate directoryPredicate) throws IOException { + List folders = new ObjectArrayList<>(); + try (Stream paths = Files.list(resourcePackFolder)) { + folders.addAll(paths + .filter(Files::isDirectory) + .filter(path -> directoryPredicate.test(path.getFileName().toString())) + .filter(path -> !path.getFileName().toString().equals("assets")) + .filter(path -> Files.exists(path.resolve("assets"))) + .toList()); + } + return folders; + } + public static List collectOverlays(Path resourcePackFolder) throws IOException { List folders = new ObjectArrayList<>(); - folders.add(resourcePackFolder); try (Stream paths = Files.list(resourcePackFolder)) { folders.addAll(paths .filter(Files::isDirectory) diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java index 7c8ef0ae0..16982d59a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java @@ -1,5 +1,7 @@ package net.momirealms.craftengine.core.util; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -13,4 +15,13 @@ public final class ListUtils { if (list.size() == 2) return List.of(list.get(0), list.get(1)); return list; } + + @SafeVarargs + public static List newNonNullList(T... elements) { + List list = new ArrayList<>(elements.length); + for (T element : elements) { + if (element != null) list.add(element); + } + return list; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java index 5489f3002..34c00fa46 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MinecraftVersion.java @@ -1,36 +1,38 @@ package net.momirealms.craftengine.core.util; +import net.momirealms.craftengine.core.pack.mcmeta.PackVersion; + import java.util.HashMap; import java.util.Map; public final class MinecraftVersion implements Comparable { - public static final Map PACK_FORMATS = new HashMap<>(); + public static final Map PACK_FORMATS = new HashMap<>(); static { - PACK_FORMATS.put(1_20_00, 15); - PACK_FORMATS.put(1_20_01, 15); - PACK_FORMATS.put(1_20_02, 18); - PACK_FORMATS.put(1_20_03, 22); - PACK_FORMATS.put(1_20_04, 22); - PACK_FORMATS.put(1_20_05, 32); - PACK_FORMATS.put(1_20_06, 32); - PACK_FORMATS.put(1_21_00, 34); - PACK_FORMATS.put(1_21_01, 34); - PACK_FORMATS.put(1_21_02, 42); - PACK_FORMATS.put(1_21_03, 42); - PACK_FORMATS.put(1_21_04, 46); - PACK_FORMATS.put(1_21_05, 55); - PACK_FORMATS.put(1_21_06, 63); - PACK_FORMATS.put(1_21_07, 64); - PACK_FORMATS.put(1_21_08, 64); - PACK_FORMATS.put(1_21_09, 69); - PACK_FORMATS.put(1_21_10, 69); - PACK_FORMATS.put(1_21_11, 75); - PACK_FORMATS.put(1_99_99, 1000); + PACK_FORMATS.put(1_20_00, new PackVersion(15, 0)); + PACK_FORMATS.put(1_20_01, new PackVersion(15, 0)); + PACK_FORMATS.put(1_20_02, new PackVersion(18, 0)); + PACK_FORMATS.put(1_20_03, new PackVersion(22, 0)); + PACK_FORMATS.put(1_20_04, new PackVersion(22, 0)); + PACK_FORMATS.put(1_20_05, new PackVersion(32, 0)); + PACK_FORMATS.put(1_20_06, new PackVersion(32, 0)); + PACK_FORMATS.put(1_21_00, new PackVersion(34, 0)); + PACK_FORMATS.put(1_21_01, new PackVersion(34, 0)); + PACK_FORMATS.put(1_21_02, new PackVersion(42, 0)); + PACK_FORMATS.put(1_21_03, new PackVersion(42, 0)); + PACK_FORMATS.put(1_21_04, new PackVersion(46, 0)); + PACK_FORMATS.put(1_21_05, new PackVersion(55, 0)); + PACK_FORMATS.put(1_21_06, new PackVersion(63, 0)); + PACK_FORMATS.put(1_21_07, new PackVersion(64, 0)); + PACK_FORMATS.put(1_21_08, new PackVersion(64, 0)); + PACK_FORMATS.put(1_21_09, new PackVersion(69, 0)); + PACK_FORMATS.put(1_21_10, new PackVersion(69, 0)); + PACK_FORMATS.put(1_21_11, new PackVersion(75, 0)); + PACK_FORMATS.put(99_99_99, new PackVersion(1000, 0)); } private final int version; private final String versionString; - private final int packFormat; + private final PackVersion packFormat; public static MinecraftVersion parse(final String version) { return new MinecraftVersion(version); @@ -40,7 +42,7 @@ public final class MinecraftVersion implements Comparable { return versionString; } - public int packFormat() { + public PackVersion packFormat() { return packFormat; } diff --git a/gradle.properties b/gradle.properties index 396afb318..baa9346aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ org.gradle.jvmargs=-Xmx4G # Project settings -project_version=0.0.66.4 -config_version=62 +project_version=0.0.66.5 +config_version=63 lang_version=45 project_group=net.momirealms latest_supported_version=1.21.11 @@ -38,7 +38,7 @@ zstd_version=1.5.7-6 commons_io_version=2.21.0 commons_lang3_version=3.20.0 sparrow_nbt_version=0.10.9 -sparrow_util_version=0.74 +sparrow_util_version=0.75 fastutil_version=8.5.18 netty_version=4.1.128.Final joml_version=1.10.8