From 3a96d52fe2e09439f4f65598d4d2b26131225279 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Tue, 20 Oct 2026 08:43:21 +0000 Subject: [PATCH 1/2] Add 3D bows and crossbows, animated textures, and tool/rod/instrument base sets - bow_3d / crossbow_3d: one shared texture plus a player model per draw state, written as ItemsAdder BOW / CROSSBOW with generate: false. IA 4.0.18 reads model_path + _0/_1/_2 for bows and also _charged/_firework for crossbows; the firework model is a copy of the charged one. - .png.mcmeta animations: every writer copies a texture's animation next to its PNG (armour layers excepted), drops a stale one on re-apply, and the remover deletes them. Animations are never namespace-rewritten. - base-sets.yml now matches the live file, plus pickaxes, shovels, fishing rods and instruments. - Ascended skin-kinds include bow_3d and crossbow_3d. Co-Authored-By: Claude Opus 5.5 --- .../pack/apply/PackApplyService.java | 67 + .../pack/delete/PackSubmissionRemover.java | 15 +- .../armourshop/pack/model/PackKind.java | 2 + .../pack/shop/ShopSubmissionWriter.java | 2 + .../armourshop/pack/util/TextureFiles.java | 75 + .../pack/writer/armor/ArmorSetWriter.java | 11 +- .../pack/writer/bow/Bow3dWriter.java | 129 ++ .../armourshop/pack/writer/bow/BowWriter.java | 8 +- .../pack/writer/bow/LargeBowWriter.java | 5 +- .../pack/writer/flat/BookWriter.java | 11 +- .../pack/writer/flat/FlatItemWriter.java | 4 +- .../armourshop/pack/writer/gun/GunWriter.java | 6 +- .../writer/large/LargeHandheldWriter.java | 4 +- .../pack/writer/model3d/Item3dWriter.java | 4 +- .../pack/writer/model3d/ShieldWriter.java | 4 +- src/main/resources/base-sets.yml | 1367 ++++++++++------- src/main/resources/permission-groups.yml | 2 +- .../pack/apply/PackApplyServiceTest.java | 43 +- .../pack/delete/PackDeletionTest.java | 20 +- .../pack/writer/PackWritersTest.java | 34 +- 20 files changed, 1222 insertions(+), 591 deletions(-) create mode 100644 src/main/java/net/tfminecraft/armourshop/pack/util/TextureFiles.java create mode 100644 src/main/java/net/tfminecraft/armourshop/pack/writer/bow/Bow3dWriter.java diff --git a/src/main/java/net/tfminecraft/armourshop/pack/apply/PackApplyService.java b/src/main/java/net/tfminecraft/armourshop/pack/apply/PackApplyService.java index 86657d2..b1e86eb 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/apply/PackApplyService.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/apply/PackApplyService.java @@ -8,7 +8,9 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.writer.armor.ArmorSetWriter; +import net.tfminecraft.armourshop.pack.writer.bow.Bow3dWriter; import net.tfminecraft.armourshop.pack.writer.bow.BowWriter; import net.tfminecraft.armourshop.pack.writer.bow.LargeBowWriter; import net.tfminecraft.armourshop.pack.writer.flat.BookWriter; @@ -205,6 +207,7 @@ private static Map> downloadArmorFiles( } tierFiles.put(Model3dUtil.HELMET_MODEL_STEM, modelDl.data); tierFiles.put(Model3dUtil.HELMET_TEXTURE_STEM, texDl.data); + putOptionalMcmeta(sub, available, texName, Model3dUtil.HELMET_TEXTURE_STEM, tierFiles); } else { String filename = tier + "_helmet.png"; if (!available.contains(filename)) { @@ -217,6 +220,7 @@ private static Map> downloadArmorFiles( throw new IllegalStateException("download " + filename + ": " + dl.error); } tierFiles.put("helmet", dl.data); + putOptionalMcmeta(sub, available, filename, "helmet", tierFiles); } for (String stem : ARMOR_BODY_STEMS) { String filename = tier + "_" + stem + ".png"; @@ -228,12 +232,34 @@ private static Map> downloadArmorFiles( throw new IllegalStateException("download " + filename + ": " + dl.error); } tierFiles.put(stem, dl.data); + if (!stem.startsWith("layer_")) { + putOptionalMcmeta(sub, available, filename, stem, tierFiles); + } } out.put(tier, tierFiles); } return out; } + /** Downloads {@code pngFilename.mcmeta} into {@code stem.mcmeta} when the website lists one. */ + private static void putOptionalMcmeta( + ApprovedSubmission sub, + Set available, + String pngFilename, + String stem, + Map tierFiles + ) { + String metaName = pngFilename + TextureFiles.MCMETA_SUFFIX; + if (!available.contains(metaName)) { + return; + } + DownloadResult dl = ProvinceSystemClient.downloadSubmissionFile(sub.id, metaName); + if (!dl.ok) { + throw new IllegalStateException("download " + metaName + ": " + dl.error); + } + tierFiles.put(TextureFiles.mcmetaStem(stem), dl.data); + } + /** Writes one tier's armor set as its own pack slug ({@code slug_tier}). */ private static void writeArmorTier( Path contentsRoot, @@ -313,6 +339,30 @@ static String stemFromFilename(String kind, String slug, String filename) { return null; } String name = filename.trim(); + // {png}.mcmeta animates that PNG: stem is the PNG's stem + ".mcmeta" + if (name.endsWith(".png" + TextureFiles.MCMETA_SUFFIX)) { + String pngStem = stemFromFilename( + kind, slug, name.substring(0, name.length() - TextureFiles.MCMETA_SUFFIX.length()) + ); + return pngStem == null ? null : TextureFiles.mcmetaStem(pngStem); + } + if ("bow_3d".equals(kind) || "crossbow_3d".equals(kind)) { + if (name.equals(slug + ".png")) { + return Model3dUtil.TEXTURE_STEM; + } + if (name.equals(slug + ".json")) { + return Model3dUtil.MODEL_STEM; + } + String prefix = slug + "_"; + if (name.startsWith(prefix) && name.endsWith(".json")) { + String suffix = name.substring(prefix.length(), name.length() - 5); + if ("0".equals(suffix) || "1".equals(suffix) || "2".equals(suffix) + || ("crossbow_3d".equals(kind) && "charged".equals(suffix))) { + return Model3dUtil.MODEL_STEM + "_" + suffix; + } + } + return null; + } if ("book".equals(kind)) { String prefix = slug + "_"; if (name.startsWith(prefix) && name.endsWith(".png")) { @@ -459,6 +509,20 @@ private static void writeKind( ns ); return; + case "bow_3d": + case "crossbow_3d": { + PackKind packKind = "bow_3d".equals(kind) ? PackKind.BOW_3D : PackKind.CROSSBOW_3D; + if (!packFiles.containsKey(Model3dUtil.TEXTURE_STEM)) { + throw new IllegalStateException("missing texture"); + } + requireStems(packFiles, Bow3dWriter.requiredModelStems(packKind)); + Bow3dWriter.write( + contentsRoot, + new PackSubmission(slug, display, packKind, packFiles), + ns + ); + return; + } case "item_3d": requireModel3d(packFiles); Item3dWriter.write( @@ -554,6 +618,9 @@ private static boolean isModelStem(String stem) { return false; } String s = stem.trim().toLowerCase(Locale.ROOT); + if (TextureFiles.isMcmetaStem(s)) { + return false; + } return Model3dUtil.MODEL_STEM.equals(s) || Model3dUtil.HELMET_MODEL_STEM.equals(s) || ShieldWriter.MODEL_BLOCKING_STEM.equals(s) diff --git a/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java b/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java index d943402..74540b0 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java @@ -3,6 +3,7 @@ import net.tfminecraft.armourshop.pack.model.BowFrames; import net.tfminecraft.armourshop.pack.model.PackPaths; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.writer.flat.BookWriter; import net.tfminecraft.armourshop.pack.writer.gun.GunWriter; import net.tfminecraft.armourshop.pack.writer.mask.MasksYml; @@ -125,13 +126,13 @@ private static void removeArmorPack( deleteQuiet(configs.resolve(packSlug + ".yml"), removed); for (String stem : ARMOR_ICON_STEMS) { - deleteQuiet(icons.resolve(packSlug + "_" + stem + ".png"), removed); + TextureFiles.deleteWithMcmeta(icons.resolve(packSlug + "_" + stem + ".png"), removed); } deleteQuiet(layers.resolve(packSlug + "_layer_1.png"), removed); deleteQuiet(layers.resolve(packSlug + "_layer_2.png"), removed); // 3D helmet assets (when present) String helmetId = packSlug + "_helmet"; - deleteQuiet( + TextureFiles.deleteWithMcmeta( PackPaths.itemTexturesDir(contentsRoot, namespace).resolve(helmetId + ".png"), removed ); @@ -152,21 +153,23 @@ private static void removeNonArmorPack( Path models = PackPaths.itemModelsDir(contentsRoot, namespace); deleteQuiet(configs.resolve(s + ".yml"), removed); - deleteQuiet(itemTex.resolve(s + ".png"), removed); + TextureFiles.deleteWithMcmeta(itemTex.resolve(s + ".png"), removed); deleteQuiet(models.resolve(s + ".json"), removed); deleteQuiet(models.resolve(s + "_blocking.json"), removed); for (String stem : BowFrames.CROSSBOW_STEMS) { - deleteQuiet( + TextureFiles.deleteWithMcmeta( itemTex.resolve(BowFrames.textureFileName(s, stem)), removed ); - // large_bow frame models (and any stray pull models) + // large_bow / bow_3d / crossbow_3d frame models (and any stray pull models) deleteQuiet( models.resolve(s + BowFrames.fileSuffix(stem) + ".json"), removed ); } - deleteQuiet(itemTex.resolve(s + "_arrow.png"), removed); + // crossbow_3d firework model is a copy of the charged one + deleteQuiet(models.resolve(s + "_firework.json"), removed); + TextureFiles.deleteWithMcmeta(itemTex.resolve(s + "_arrow.png"), removed); } private static void deleteQuiet(Path path, List removed) throws IOException { diff --git a/src/main/java/net/tfminecraft/armourshop/pack/model/PackKind.java b/src/main/java/net/tfminecraft/armourshop/pack/model/PackKind.java index 29e14ab..4dc597f 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/model/PackKind.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/model/PackKind.java @@ -11,6 +11,8 @@ public enum PackKind { BOW, LARGE_BOW, CROSSBOW, + BOW_3D, + CROSSBOW_3D, ITEM_3D, SHIELD, HELMET_3D, diff --git a/src/main/java/net/tfminecraft/armourshop/pack/shop/ShopSubmissionWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/shop/ShopSubmissionWriter.java index 19842bc..e5719c7 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/shop/ShopSubmissionWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/shop/ShopSubmissionWriter.java @@ -215,6 +215,8 @@ private static boolean isItemKind(String kind) { || "bow".equals(kind) || "large_bow".equals(kind) || "crossbow".equals(kind) + || "bow_3d".equals(kind) + || "crossbow_3d".equals(kind) || "item_3d".equals(kind) || "shield".equals(kind) || "helmet_3d".equals(kind) diff --git a/src/main/java/net/tfminecraft/armourshop/pack/util/TextureFiles.java b/src/main/java/net/tfminecraft/armourshop/pack/util/TextureFiles.java new file mode 100644 index 0000000..716abe1 --- /dev/null +++ b/src/main/java/net/tfminecraft/armourshop/pack/util/TextureFiles.java @@ -0,0 +1,75 @@ +package net.tfminecraft.armourshop.pack.util; + +import net.tfminecraft.armourshop.pack.model.PackSubmission; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; + +/** + * Writes submission PNGs together with their optional {@code .png.mcmeta} animation. + * A texture stem {@code S} has its animation under stem {@code S.mcmeta}. + */ +public final class TextureFiles { + + public static final String MCMETA_SUFFIX = ".mcmeta"; + + private TextureFiles() {} + + public static String mcmetaStem(String stem) { + return stem + MCMETA_SUFFIX; + } + + public static boolean isMcmetaStem(String stem) { + return stem != null && stem.trim().toLowerCase(Locale.ROOT).endsWith(MCMETA_SUFFIX); + } + + /** {@code foo.png} → {@code foo.png.mcmeta} beside it. */ + public static Path mcmetaPath(Path png) { + return png.resolveSibling(png.getFileName().toString() + MCMETA_SUFFIX); + } + + /** Writes the stem's PNG to {@code png}, then its animation (if any). */ + public static void writePng( + PackSubmission submission, + String stem, + Path png, + List written + ) throws IOException { + Files.write(png, submission.requireFile(stem)); + written.add(png); + writeMcmeta(submission, stem, png, written); + } + + /** + * Writes {@code png.mcmeta} when the submission animates {@code stem}; otherwise + * removes a stale one left by an earlier apply of the same slug. + */ + public static void writeMcmeta( + PackSubmission submission, + String stem, + Path png, + List written + ) throws IOException { + Path meta = mcmetaPath(png); + byte[] data = submission.files().get(mcmetaStem(stem)); + if (data == null || data.length == 0) { + Files.deleteIfExists(meta); + return; + } + Files.write(meta, data); + written.add(meta); + } + + /** Deletes {@code png} and its {@code .mcmeta}, recording what was removed. */ + public static void deleteWithMcmeta(Path png, List removed) throws IOException { + if (Files.deleteIfExists(png)) { + removed.add(png); + } + Path meta = mcmetaPath(png); + if (Files.deleteIfExists(meta)) { + removed.add(meta); + } + } +} diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/armor/ArmorSetWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/armor/ArmorSetWriter.java index 5c16ae2..576d69d 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/armor/ArmorSetWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/armor/ArmorSetWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -74,24 +75,22 @@ public static List write( Files.createDirectories(itemTexDir); String helmetId = slug + "_helmet"; Path tex = itemTexDir.resolve(helmetId + ".png"); - Files.write(tex, submission.requireFile(Model3dUtil.HELMET_TEXTURE_STEM)); - written.add(tex); + TextureFiles.writePng(submission, Model3dUtil.HELMET_TEXTURE_STEM, tex, written); Path modelPath = modelsDir.resolve(helmetId + ".json"); Files.write(modelPath, submission.requireFile(Model3dUtil.HELMET_MODEL_STEM)); written.add(modelPath); } else { Path out = iconsDir.resolve(slug + "_helmet.png"); - Files.write(out, submission.requireFile("helmet")); - written.add(out); + TextureFiles.writePng(submission, "helmet", out, written); } for (String stem : BODY_ICON_STEMS) { Path out = iconsDir.resolve(slug + "_" + stem + ".png"); - Files.write(out, submission.requireFile(stem)); - written.add(out); + TextureFiles.writePng(submission, stem, out, written); } for (String stem : LAYER_STEMS) { Path out = layersDir.resolve(slug + "_" + stem + ".png"); + // Armour layers are not animated. Files.write(out, submission.requireFile(stem)); written.add(out); } diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/Bow3dWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/Bow3dWriter.java new file mode 100644 index 0000000..1256c09 --- /dev/null +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/Bow3dWriter.java @@ -0,0 +1,129 @@ +package net.tfminecraft.armourshop.pack.writer.bow; + +import net.tfminecraft.armourshop.pack.model.PackKind; +import net.tfminecraft.armourshop.pack.model.PackPaths; +import net.tfminecraft.armourshop.pack.model.PackSubmission; +import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; +import net.tfminecraft.armourshop.pack.util.YamlUtil; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Writes bow_3d / crossbow_3d skins: one shared texture and a player-made model per + * draw state (passthrough). ItemsAdder ({@code generate: false}) reads + * {@code model_path} plus {@code _0/_1/_2} for BOW, and also {@code _charged} and + * {@code _firework} for CROSSBOW. + */ +public final class Bow3dWriter { + + public static final String MODEL_0_STEM = Model3dUtil.MODEL_STEM + "_0"; + public static final String MODEL_1_STEM = Model3dUtil.MODEL_STEM + "_1"; + public static final String MODEL_2_STEM = Model3dUtil.MODEL_STEM + "_2"; + public static final String MODEL_CHARGED_STEM = Model3dUtil.MODEL_STEM + "_charged"; + + /** Model stem → pack file suffix after the slug, for bow_3d. */ + public static final String[][] BOW_MODELS = { + {Model3dUtil.MODEL_STEM, ""}, + {MODEL_0_STEM, "_0"}, + {MODEL_1_STEM, "_1"}, + {MODEL_2_STEM, "_2"}, + }; + + /** crossbow_3d adds the loaded model, written again as the firework model. */ + public static final String[][] CROSSBOW_MODELS = { + {Model3dUtil.MODEL_STEM, ""}, + {MODEL_0_STEM, "_0"}, + {MODEL_1_STEM, "_1"}, + {MODEL_2_STEM, "_2"}, + {MODEL_CHARGED_STEM, "_charged"}, + {MODEL_CHARGED_STEM, "_firework"}, + }; + + private Bow3dWriter() {} + + public static List write( + Path contentsRoot, + PackSubmission submission, + String namespace + ) throws IOException { + if (submission.kind() == PackKind.BOW_3D) { + return write(contentsRoot, submission, "BOW", BOW_MODELS, namespace); + } + if (submission.kind() == PackKind.CROSSBOW_3D) { + return write(contentsRoot, submission, "CROSSBOW", CROSSBOW_MODELS, namespace); + } + throw new IllegalArgumentException( + "Bow3dWriter requires BOW_3D or CROSSBOW_3D, got " + submission.kind() + ); + } + + /** Model stems the submission must carry for a kind. */ + public static String[] requiredModelStems(PackKind kind) { + String[][] models = kind == PackKind.CROSSBOW_3D ? CROSSBOW_MODELS : BOW_MODELS; + return Arrays.stream(models).map(m -> m[0]).distinct().toArray(String[]::new); + } + + private static List write( + Path contentsRoot, + PackSubmission submission, + String material, + String[][] models, + String namespace + ) throws IOException { + String slug = submission.slug(); + YamlUtil.validateSlug(slug); + String ns = requireNs(namespace); + + Path modelsDir = PackPaths.itemModelsDir(contentsRoot, ns); + Path itemTexDir = PackPaths.itemTexturesDir(contentsRoot, ns); + Path configsDir = PackPaths.configsDir(contentsRoot, ns); + Files.createDirectories(modelsDir); + Files.createDirectories(itemTexDir); + Files.createDirectories(configsDir); + + List written = new ArrayList<>(); + TextureFiles.writePng( + submission, Model3dUtil.TEXTURE_STEM, itemTexDir.resolve(slug + ".png"), written + ); + for (String[] model : models) { + Path modelPath = modelsDir.resolve(slug + model[1] + ".json"); + Files.write(modelPath, submission.requireFile(model[0])); + written.add(modelPath); + } + + Path yamlPath = configsDir.resolve(slug + ".yml"); + Files.writeString(yamlPath, buildYaml(submission, material, ns), StandardCharsets.UTF_8); + written.add(yamlPath); + return written; + } + + static String buildYaml(PackSubmission submission, String material, String namespace) { + String slug = submission.slug(); + String name = YamlUtil.escapeDoubleQuoted(submission.displayName()); + StringBuilder sb = new StringBuilder(); + sb.append("info:\n"); + sb.append(" namespace: ").append(requireNs(namespace)).append('\n'); + sb.append("items:\n"); + sb.append(" ").append(slug).append(":\n"); + sb.append(" display_name: \"").append(name).append("\"\n"); + sb.append(" permission: ").append(slug).append('\n'); + sb.append(" resource:\n"); + sb.append(" material: ").append(material).append('\n'); + sb.append(" generate: false\n"); + sb.append(" model_path: item/").append(slug).append('\n'); + return sb.toString(); + } + + private static String requireNs(String namespace) { + if (namespace == null || namespace.isBlank()) { + throw new IllegalArgumentException("namespace is required"); + } + return namespace.trim(); + } +} diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/BowWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/BowWriter.java index f0d2642..1e8a608 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/BowWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/BowWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackKind; import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -90,15 +91,12 @@ static List writeGenerateTrue( List written = new ArrayList<>(); for (String stem : stems) { - byte[] png = submission.requireFile(stem); Path pngPath = itemTexDir.resolve(BowFrames.textureFileName(slug, stem)); - Files.write(pngPath, png); - written.add(pngPath); + TextureFiles.writePng(submission, stem, pngPath, written); // IA CROSSBOW charged often keyed as *_arrow if (BowFrames.CHARGED.equals(stem)) { Path arrow = itemTexDir.resolve(slug + "_arrow.png"); - Files.write(arrow, png); - written.add(arrow); + TextureFiles.writePng(submission, stem, arrow, written); } } diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/LargeBowWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/LargeBowWriter.java index e6bb27a..508d92b 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/LargeBowWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/bow/LargeBowWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackKind; import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -52,10 +53,8 @@ public static List write( List written = new ArrayList<>(); for (String stem : BowFrames.BOW_STEMS) { - byte[] png = submission.requireFile(stem); Path pngPath = itemTexDir.resolve(BowFrames.textureFileName(slug, stem)); - Files.write(pngPath, png); - written.add(pngPath); + TextureFiles.writePng(submission, stem, pngPath, written); String modelStem = modelStemFor(stem); byte[] modelJson = submission.requireFile(modelStem); diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/BookWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/BookWriter.java index fed51f4..24ffc95 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/BookWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/BookWriter.java @@ -3,6 +3,7 @@ import net.tfminecraft.armourshop.pack.model.PackKind; import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -51,12 +52,10 @@ public static List write( List written = new ArrayList<>(); Path unsignedPng = itemTexDir.resolve(slug + "_unsigned.png"); - Files.write(unsignedPng, submission.requireFile(UNSIGNED_STEM)); - written.add(unsignedPng); + TextureFiles.writePng(submission, UNSIGNED_STEM, unsignedPng, written); Path signedPng = itemTexDir.resolve(slug + "_signed.png"); - Files.write(signedPng, submission.requireFile(SIGNED_STEM)); - written.add(signedPng); + TextureFiles.writePng(submission, SIGNED_STEM, signedPng, written); Path yamlPath = configsDir.resolve(slug + ".yml"); Files.writeString(yamlPath, buildYaml(submission, ns), StandardCharsets.UTF_8); @@ -80,8 +79,8 @@ public static List remove(Path contentsRoot, String namespace, String slug List removed = new ArrayList<>(); deleteQuiet(PackPaths.configsDir(contentsRoot, ns).resolve(s + ".yml"), removed); Path itemTex = PackPaths.itemTexturesDir(contentsRoot, ns); - deleteQuiet(itemTex.resolve(s + "_unsigned.png"), removed); - deleteQuiet(itemTex.resolve(s + "_signed.png"), removed); + TextureFiles.deleteWithMcmeta(itemTex.resolve(s + "_unsigned.png"), removed); + TextureFiles.deleteWithMcmeta(itemTex.resolve(s + "_signed.png"), removed); return removed; } diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/FlatItemWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/FlatItemWriter.java index 9a4371c..9382f90 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/FlatItemWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/flat/FlatItemWriter.java @@ -3,6 +3,7 @@ import net.tfminecraft.armourshop.pack.model.PackKind; import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -50,8 +51,7 @@ public static List write( List written = new ArrayList<>(); Path pngPath = itemTexDir.resolve(slug + ".png"); - Files.write(pngPath, submission.requireFile(TEXTURE_STEM)); - written.add(pngPath); + TextureFiles.writePng(submission, TEXTURE_STEM, pngPath, written); Path yamlPath = configsDir.resolve(slug + ".yml"); Files.writeString(yamlPath, buildYaml(submission, ns), StandardCharsets.UTF_8); diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/gun/GunWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/gun/GunWriter.java index e82eb1a..f346161 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/gun/GunWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/gun/GunWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -72,8 +73,7 @@ public static List write( List written = new ArrayList<>(); Path pngPath = itemTexDir.resolve(slug + ".png"); - Files.write(pngPath, submission.requireFile(TEXTURE_STEM)); - written.add(pngPath); + TextureFiles.writePng(submission, TEXTURE_STEM, pngPath, written); for (String stem : MODEL_STEMS) { Path modelPath = modelsDir.resolve(slug + "_" + stem + ".json"); @@ -125,7 +125,7 @@ public static List remove( PackPaths.configsDir(contentsRoot, ns).resolve(s + ".yml"), removed ); - deleteQuiet( + TextureFiles.deleteWithMcmeta( PackPaths.itemTexturesDir(contentsRoot, ns).resolve(s + ".png"), removed ); diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/large/LargeHandheldWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/large/LargeHandheldWriter.java index d87d1ae..a1d712e 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/large/LargeHandheldWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/large/LargeHandheldWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -52,8 +53,7 @@ public static List write( List written = new ArrayList<>(); Path pngPath = itemTexDir.resolve(slug + ".png"); - Files.write(pngPath, submission.requireFile(TEXTURE_STEM)); - written.add(pngPath); + TextureFiles.writePng(submission, TEXTURE_STEM, pngPath, written); Path thinModel = modelsDir.resolve(slug + ".json"); Files.write(thinModel, submission.requireFile(Model3dUtil.MODEL_STEM)); diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/Item3dWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/Item3dWriter.java index 8e3bc6d..a836893 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/Item3dWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/Item3dWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -123,8 +124,7 @@ static List writeModelItem( List written = new ArrayList<>(); Path pngPath = itemTexDir.resolve(slug + ".png"); - Files.write(pngPath, submission.requireFile(Model3dUtil.TEXTURE_STEM)); - written.add(pngPath); + TextureFiles.writePng(submission, Model3dUtil.TEXTURE_STEM, pngPath, written); Path modelPath = modelsDir.resolve(slug + ".json"); Files.write(modelPath, submission.requireFile(Model3dUtil.MODEL_STEM)); diff --git a/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/ShieldWriter.java b/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/ShieldWriter.java index 8d3dfa2..aec0db1 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/ShieldWriter.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/writer/model3d/ShieldWriter.java @@ -4,6 +4,7 @@ import net.tfminecraft.armourshop.pack.model.PackPaths; import net.tfminecraft.armourshop.pack.model.PackSubmission; import net.tfminecraft.armourshop.pack.util.Model3dUtil; +import net.tfminecraft.armourshop.pack.util.TextureFiles; import net.tfminecraft.armourshop.pack.util.YamlUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -52,8 +53,7 @@ public static List write( List written = new ArrayList<>(); Path pngPath = itemTexDir.resolve(slug + ".png"); - Files.write(pngPath, submission.requireFile(Model3dUtil.TEXTURE_STEM)); - written.add(pngPath); + TextureFiles.writePng(submission, Model3dUtil.TEXTURE_STEM, pngPath, written); Path idlePath = modelsDir.resolve(slug + ".json"); Files.write(idlePath, submission.requireFile(Model3dUtil.MODEL_STEM)); diff --git a/src/main/resources/base-sets.yml b/src/main/resources/base-sets.yml index 35954a7..03d4c30 100644 --- a/src/main/resources/base-sets.yml +++ b/src/main/resources/base-sets.yml @@ -1,722 +1,999 @@ leather: helmet: - - helmets.leather_helmet + - helmets.leather_helmet chestplate: - - armors.leather_chestplate + - armors.leather_chestplate leggings: - - armors.leather_leggings + - armors.leather_leggings boots: - - armors.leather_boots - + - armors.leather_boots iron: + metal: iron helmet: - - helmets.light_chainmail_helmet - - helmets.medium_chainmail_helmet - - helmets.heavy_chainmail_helmet - - helmets.light_custom_helmet - - helmets.medium_custom_helmet - - helmets.heavy_custom_helmet - chestplate: - - armors.light_chainmail_chestplate - - armors.medium_chainmail_chestplate - - armors.heavy_chainmail_chestplate - - armors.light_custom_chestplate - - armors.medium_custom_chestplate - - armors.heavy_custom_chestplate - leggings: - - armors.light_chainmail_leggings - - armors.medium_chainmail_leggings - - armors.heavy_chainmail_leggings - - armors.light_custom_leggings - - armors.medium_custom_leggings - - armors.heavy_custom_leggings - boots: - - armors.light_chainmail_boots - - armors.medium_chainmail_boots - - armors.heavy_chainmail_boots - - armors.light_custom_boots - - armors.medium_custom_boots - - armors.heavy_custom_boots - -steel: + - helmets.light_chainmail_helmet + - helmets.medium_chainmail_helmet + - helmets.heavy_chainmail_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.light_chainmail_chestplate + - armors.medium_chainmail_chestplate + - armors.heavy_chainmail_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.light_chainmail_leggings + - armors.medium_chainmail_leggings + - armors.heavy_chainmail_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings + boots: + - armors.light_chainmail_boots + - armors.medium_chainmail_boots + - armors.heavy_chainmail_boots + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots +light iron: + metal: iron helmet: - - helmets.light_steel_helmet - - helmets.medium_steel_helmet - - helmets.heavy_steel_helmet - - helmets.light_custom_helmet - - helmets.medium_custom_helmet - - helmets.heavy_custom_helmet - chestplate: - - armors.light_steel_chestplate - - armors.medium_steel_chestplate - - armors.heavy_steel_chestplate - - armors.light_custom_chestplate - - armors.medium_custom_chestplate - - armors.heavy_custom_chestplate - leggings: - - armors.light_steel_leggings - - armors.medium_steel_leggings - - armors.heavy_steel_leggings - - armors.light_custom_leggings - - armors.medium_custom_leggings - - armors.heavy_custom_leggings - boots: - - armors.light_steel_boots - - armors.medium_steel_boots - - armors.heavy_steel_boots - - armors.light_custom_boots - - armors.medium_custom_boots - - armors.heavy_custom_boots - -abyssalite: + - helmets.light_chainmail_helmet + - helmets.light_custom_helmet + chestplate: + - armors.light_chainmail_chestplate + - armors.light_custom_chestplate + leggings: + - armors.light_chainmail_leggings + - armors.light_custom_leggings + boots: + - armors.light_chainmail_boots + - armors.light_custom_boots +medium iron: + metal: iron helmet: - - helmets.light_abyssalite_helmet - - helmets.medium_abyssalite_helmet - - helmets.heavy_abyssalite_helmet - - helmets.light_custom_helmet - - helmets.medium_custom_helmet - - helmets.heavy_custom_helmet - chestplate: - - armors.light_abyssalite_chestplate - - armors.medium_abyssalite_chestplate - - armors.heavy_abyssalite_chestplate - - armors.light_custom_chestplate - - armors.medium_custom_chestplate - - armors.heavy_custom_chestplate - leggings: - - armors.light_abyssalite_leggings - - armors.medium_abyssalite_leggings - - armors.heavy_abyssalite_leggings - - armors.light_custom_leggings - - armors.medium_custom_leggings - - armors.heavy_custom_leggings - boots: - - armors.light_abyssalite_boots - - armors.medium_abyssalite_boots - - armors.heavy_abyssalite_boots - - armors.light_custom_boots - - armors.medium_custom_boots - - armors.heavy_custom_boots - -mythril: + - helmets.medium_chainmail_helmet + - helmets.medium_custom_helmet + chestplate: + - armors.medium_chainmail_chestplate + - armors.medium_custom_chestplate + leggings: + - armors.medium_chainmail_leggings + - armors.medium_custom_leggings + boots: + - armors.medium_chainmail_boots + - armors.medium_custom_boots +heavy iron: + metal: iron helmet: - - helmets.light_mythril_helmet - - helmets.medium_mythril_helmet - - helmets.heavy_mythril_helmet - - helmets.light_custom_helmet - - helmets.medium_custom_helmet - - helmets.heavy_custom_helmet - chestplate: - - armors.light_mythril_chestplate - - armors.medium_mythril_chestplate - - armors.heavy_mythril_chestplate - - armors.light_custom_chestplate - - armors.medium_custom_chestplate - - armors.heavy_custom_chestplate - leggings: - - armors.light_mythril_leggings - - armors.medium_mythril_leggings - - armors.heavy_mythril_leggings - - armors.light_custom_leggings - - armors.medium_custom_leggings - - armors.heavy_custom_leggings - boots: - - armors.light_mythril_boots - - armors.medium_mythril_boots - - armors.heavy_mythril_boots - - armors.light_custom_boots - - armors.medium_custom_boots - - armors.heavy_custom_boots - -light steel: + - helmets.heavy_chainmail_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.heavy_chainmail_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.heavy_chainmail_leggings + - armors.heavy_custom_leggings + boots: + - armors.heavy_chainmail_boots + - armors.heavy_custom_boots +infantry iron: + metal: iron helmet: - - helmets.light_steel_helmet - - helmets.light_custom_helmet + - helmets.infantry_iron_helmet + - helmets.infantry_custom_helmet chestplate: - - armors.light_steel_chestplate - - armors.light_custom_chestplate + - armors.infantry_iron_chestplate + - armors.infantry_custom_chestplate leggings: - - armors.light_steel_leggings - - armors.light_custom_leggings + - armors.infantry_iron_leggings + - armors.infantry_custom_leggings boots: - - armors.light_steel_boots - - armors.light_custom_boots - -light abyssalite: + - armors.infantry_iron_boots + - armors.infantry_custom_boots +mage iron: + metal: iron helmet: - - helmets.light_abyssalite_helmet - - helmets.light_custom_helmet + - mage_armors.mage_iron_helmet + - mage_armors.mage_custom_helmet chestplate: - - armors.light_abyssalite_chestplate - - armors.light_custom_chestplate + - mage_armors.mage_iron_chestplate + - mage_armors.mage_custom_chestplate leggings: - - armors.light_abyssalite_leggings - - armors.light_custom_leggings + - mage_armors.mage_iron_leggings + - mage_armors.mage_custom_leggings boots: - - armors.light_abyssalite_boots - - armors.light_custom_boots - -light mythril: + - mage_armors.mage_iron_boots + - mage_armors.mage_custom_boots +steel: + metal: steel helmet: - - helmets.light_mythril_helmet - - helmets.light_custom_helmet + - helmets.light_steel_helmet + - helmets.medium_steel_helmet + - helmets.heavy_steel_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet chestplate: - - armors.light_mythril_chestplate - - armors.light_custom_chestplate + - armors.light_steel_chestplate + - armors.medium_steel_chestplate + - armors.heavy_steel_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate leggings: - - armors.light_mythril_leggings - - armors.light_custom_leggings + - armors.light_steel_leggings + - armors.medium_steel_leggings + - armors.heavy_steel_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings boots: - - armors.light_mythril_boots - - armors.light_custom_boots - + - armors.light_steel_boots + - armors.medium_steel_boots + - armors.heavy_steel_boots + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots +light steel: + metal: steel + helmet: + - helmets.light_steel_helmet + - helmets.light_custom_helmet + chestplate: + - armors.light_steel_chestplate + - armors.light_custom_chestplate + leggings: + - armors.light_steel_leggings + - armors.light_custom_leggings + boots: + - armors.light_steel_boots + - armors.light_custom_boots medium steel: + metal: steel helmet: - - helmets.medium_steel_helmet - - helmets.medium_custom_helmet + - helmets.medium_steel_helmet + - helmets.medium_custom_helmet chestplate: - - armors.medium_steel_chestplate - - armors.medium_custom_chestplate + - armors.medium_steel_chestplate + - armors.medium_custom_chestplate leggings: - - armors.medium_steel_leggings - - armors.medium_custom_leggings + - armors.medium_steel_leggings + - armors.medium_custom_leggings boots: - - armors.medium_steel_boots - - armors.medium_custom_boots - -medium abyssalite: + - armors.medium_steel_boots + - armors.medium_custom_boots +heavy steel: + metal: steel helmet: - - helmets.medium_abyssalite_helmet - - helmets.medium_custom_helmet + - helmets.heavy_steel_helmet + - helmets.heavy_custom_helmet chestplate: - - armors.medium_abyssalite_chestplate - - armors.medium_custom_chestplate + - armors.heavy_steel_chestplate + - armors.heavy_custom_chestplate leggings: - - armors.medium_abyssalite_leggings - - armors.medium_custom_leggings + - armors.heavy_steel_leggings + - armors.heavy_custom_leggings boots: - - armors.medium_abyssalite_boots - - armors.medium_custom_boots - -medium mythril: + - armors.heavy_steel_boots + - armors.heavy_custom_boots +infantry steel: + metal: steel helmet: - - helmets.medium_mythril_helmet - - helmets.medium_custom_helmet + - helmets.infantry_steel_helmet + - helmets.infantry_custom_helmet chestplate: - - armors.medium_mythril_chestplate - - armors.medium_custom_chestplate + - armors.infantry_steel_chestplate + - armors.infantry_custom_chestplate leggings: - - armors.medium_mythril_leggings - - armors.medium_custom_leggings + - armors.infantry_steel_leggings + - armors.infantry_custom_leggings boots: - - armors.medium_mythril_boots - - armors.medium_custom_boots - -heavy steel: + - armors.infantry_steel_boots + - armors.infantry_custom_boots +mage steel: + metal: steel helmet: - - helmets.heavy_steel_helmet - - helmets.heavy_custom_helmet + - mage_armors.mage_steel_helmet + - mage_armors.mage_custom_helmet chestplate: - - armors.heavy_steel_chestplate - - armors.heavy_custom_chestplate + - mage_armors.mage_steel_chestplate + - mage_armors.mage_custom_chestplate leggings: - - armors.heavy_steel_leggings - - armors.heavy_custom_leggings + - mage_armors.mage_steel_leggings + - mage_armors.mage_custom_leggings boots: - - armors.heavy_steel_boots - - armors.heavy_custom_boots - -heavy abyssalite: + - mage_armors.mage_steel_boots + - mage_armors.mage_custom_boots +bronze: + metal: bronze helmet: - - helmets.heavy_abyssalite_helmet - - helmets.heavy_custom_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet chestplate: - - armors.heavy_abyssalite_chestplate - - armors.heavy_custom_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate leggings: - - armors.heavy_abyssalite_leggings - - armors.heavy_custom_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings boots: - - armors.heavy_abyssalite_boots - - armors.heavy_custom_boots - -heavy mythril: + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots +light bronze: + metal: bronze helmet: - - helmets.heavy_mythril_helmet - - helmets.heavy_custom_helmet + - helmets.light_custom_helmet chestplate: - - armors.heavy_mythril_chestplate - - armors.heavy_custom_chestplate + - armors.light_custom_chestplate leggings: - - armors.heavy_mythril_leggings - - armors.heavy_custom_leggings + - armors.light_custom_leggings boots: - - armors.heavy_mythril_boots - - armors.heavy_custom_boots - -infantry steel: + - armors.light_custom_boots +medium bronze: + metal: bronze + helmet: + - helmets.medium_custom_helmet + chestplate: + - armors.medium_custom_chestplate + leggings: + - armors.medium_custom_leggings + boots: + - armors.medium_custom_boots +heavy bronze: + metal: bronze + helmet: + - helmets.heavy_custom_helmet + chestplate: + - armors.heavy_custom_chestplate + leggings: + - armors.heavy_custom_leggings + boots: + - armors.heavy_custom_boots +infantry bronze: + metal: bronze + helmet: + - helmets.infantry_custom_helmet + chestplate: + - armors.infantry_custom_chestplate + leggings: + - armors.infantry_custom_leggings + boots: + - armors.infantry_custom_boots +mage bronze: + metal: bronze + helmet: + - mage_armors.mage_custom_helmet + chestplate: + - mage_armors.mage_custom_chestplate + leggings: + - mage_armors.mage_custom_leggings + boots: + - mage_armors.mage_custom_boots +abyssalite: + metal: abyssalite + helmet: + - helmets.light_abyssalite_helmet + - helmets.medium_abyssalite_helmet + - helmets.heavy_abyssalite_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.light_abyssalite_chestplate + - armors.medium_abyssalite_chestplate + - armors.heavy_abyssalite_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.light_abyssalite_leggings + - armors.medium_abyssalite_leggings + - armors.heavy_abyssalite_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings + boots: + - armors.light_abyssalite_boots + - armors.medium_abyssalite_boots + - armors.heavy_abyssalite_boots + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots +light abyssalite: + metal: abyssalite helmet: - - helmets.infantry_steel_helmet - - helmets.infantry_custom_helmet + - helmets.light_abyssalite_helmet + - helmets.light_custom_helmet chestplate: - - armors.infantry_steel_chestplate - - armors.infantry_custom_chestplate + - armors.light_abyssalite_chestplate + - armors.light_custom_chestplate leggings: - - armors.infantry_steel_leggings - - armors.infantry_custom_leggings + - armors.light_abyssalite_leggings + - armors.light_custom_leggings boots: - - armors.infantry_steel_boots - - armors.infantry_custom_boots - + - armors.light_abyssalite_boots + - armors.light_custom_boots +medium abyssalite: + metal: abyssalite + helmet: + - helmets.medium_abyssalite_helmet + - helmets.medium_custom_helmet + chestplate: + - armors.medium_abyssalite_chestplate + - armors.medium_custom_chestplate + leggings: + - armors.medium_abyssalite_leggings + - armors.medium_custom_leggings + boots: + - armors.medium_abyssalite_boots + - armors.medium_custom_boots +heavy abyssalite: + metal: abyssalite + helmet: + - helmets.heavy_abyssalite_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.heavy_abyssalite_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.heavy_abyssalite_leggings + - armors.heavy_custom_leggings + boots: + - armors.heavy_abyssalite_boots + - armors.heavy_custom_boots infantry abyssalite: + metal: abyssalite + helmet: + - helmets.infantry_abyssalite_helmet + - helmets.infantry_custom_helmet + chestplate: + - armors.infantry_abyssalite_chestplate + - armors.infantry_custom_chestplate + leggings: + - armors.infantry_abyssalite_leggings + - armors.infantry_custom_leggings + boots: + - armors.infantry_abyssalite_boots + - armors.infantry_custom_boots +mage abyssalite: + metal: abyssalite helmet: - - helmets.infantry_abyssalite_helmet - - helmets.infantry_custom_helmet + - mage_armors.mage_abyssalite_helmet + - mage_armors.mage_custom_helmet chestplate: - - armors.infantry_abyssalite_chestplate - - armors.infantry_custom_chestplate + - mage_armors.mage_abyssalite_chestplate + - mage_armors.mage_custom_chestplate leggings: - - armors.infantry_abyssalite_leggings - - armors.infantry_custom_leggings + - mage_armors.mage_abyssalite_leggings + - mage_armors.mage_custom_leggings boots: - - armors.infantry_abyssalite_boots - - armors.infantry_custom_boots - + - mage_armors.mage_abyssalite_boots + - mage_armors.mage_custom_boots +mythril: + metal: mythril + helmet: + - helmets.light_mythril_helmet + - helmets.medium_mythril_helmet + - helmets.heavy_mythril_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.light_mythril_chestplate + - armors.medium_mythril_chestplate + - armors.heavy_mythril_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.light_mythril_leggings + - armors.medium_mythril_leggings + - armors.heavy_mythril_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings + boots: + - armors.light_mythril_boots + - armors.medium_mythril_boots + - armors.heavy_mythril_boots + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots +light mythril: + metal: mythril + helmet: + - helmets.light_mythril_helmet + - helmets.light_custom_helmet + chestplate: + - armors.light_mythril_chestplate + - armors.light_custom_chestplate + leggings: + - armors.light_mythril_leggings + - armors.light_custom_leggings + boots: + - armors.light_mythril_boots + - armors.light_custom_boots +medium mythril: + metal: mythril + helmet: + - helmets.medium_mythril_helmet + - helmets.medium_custom_helmet + chestplate: + - armors.medium_mythril_chestplate + - armors.medium_custom_chestplate + leggings: + - armors.medium_mythril_leggings + - armors.medium_custom_leggings + boots: + - armors.medium_mythril_boots + - armors.medium_custom_boots +heavy mythril: + metal: mythril + helmet: + - helmets.heavy_mythril_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.heavy_mythril_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.heavy_mythril_leggings + - armors.heavy_custom_leggings + boots: + - armors.heavy_mythril_boots + - armors.heavy_custom_boots infantry mythril: + metal: mythril helmet: - - helmets.infantry_mythril_helmet - - helmets.infantry_custom_helmet + - helmets.infantry_mythril_helmet + - helmets.infantry_custom_helmet chestplate: - - armors.infantry_mythril_chestplate - - armors.infantry_custom_chestplate + - armors.infantry_mythril_chestplate + - armors.infantry_custom_chestplate leggings: - - armors.infantry_mythril_leggings - - armors.infantry_custom_leggings + - armors.infantry_mythril_leggings + - armors.infantry_custom_leggings boots: - - armors.infantry_mythril_boots - - armors.infantry_custom_boots - + - armors.infantry_mythril_boots + - armors.infantry_custom_boots +mage mythril: + metal: mythril + helmet: + - mage_armors.mage_mythril_helmet + - mage_armors.mage_custom_helmet + chestplate: + - mage_armors.mage_mythril_chestplate + - mage_armors.mage_custom_chestplate + leggings: + - mage_armors.mage_mythril_leggings + - mage_armors.mage_custom_leggings + boots: + - mage_armors.mage_mythril_boots + - mage_armors.mage_custom_boots any: helmet: - - helmets.light_chainmail_helmet - - helmets.medium_chainmail_helmet - - helmets.heavy_chainmail_helmet - - helmets.light_steel_helmet - - helmets.medium_steel_helmet - - helmets.heavy_steel_helmet - - helmets.light_abyssalite_helmet - - helmets.medium_abyssalite_helmet - - helmets.heavy_abyssalite_helmet - - helmets.light_mythril_helmet - - helmets.medium_mythril_helmet - - helmets.heavy_mythril_helmet - - helmets.light_custom_helmet - - helmets.medium_custom_helmet - - helmets.heavy_custom_helmet - chestplate: - - armors.light_chainmail_chestplate - - armors.medium_chainmail_chestplate - - armors.heavy_chainmail_chestplate - - armors.light_steel_chestplate - - armors.medium_steel_chestplate - - armors.heavy_steel_chestplate - - armors.light_abyssalite_chestplate - - armors.medium_abyssalite_chestplate - - armors.heavy_abyssalite_chestplate - - armors.light_mythril_chestplate - - armors.medium_mythril_chestplate - - armors.heavy_mythril_chestplate - - armors.light_custom_chestplate - - armors.medium_custom_chestplate - - armors.heavy_custom_chestplate - leggings: - - armors.light_chainmail_leggings - - armors.medium_chainmail_leggings - - armors.heavy_chainmail_leggings - - armors.light_steel_leggings - - armors.medium_steel_leggings - - armors.heavy_steel_leggings - - armors.light_abyssalite_leggings - - armors.medium_abyssalite_leggings - - armors.heavy_abyssalite_leggings - - armors.light_mythril_leggings - - armors.medium_mythril_leggings - - armors.heavy_mythril_leggings - - armors.light_custom_leggings - - armors.medium_custom_leggings - - armors.heavy_custom_leggings - boots: - - armors.light_chainmail_boots - - armors.medium_chainmail_boots - - armors.heavy_chainmail_boots - - armors.light_steel_boots - - armors.medium_steel_boots - - armors.heavy_steel_boots - - armors.light_abyssalite_boots - - armors.medium_abyssalite_boots - - armors.heavy_abyssalite_boots - - armors.light_mythril_boots - - armors.medium_mythril_boots - - armors.heavy_mythril_boots - - armors.light_custom_boots - - armors.medium_custom_boots - - armors.heavy_custom_boots - + - helmets.light_chainmail_helmet + - helmets.medium_chainmail_helmet + - helmets.heavy_chainmail_helmet + - helmets.light_steel_helmet + - helmets.medium_steel_helmet + - helmets.heavy_steel_helmet + - helmets.light_abyssalite_helmet + - helmets.medium_abyssalite_helmet + - helmets.heavy_abyssalite_helmet + - helmets.light_mythril_helmet + - helmets.medium_mythril_helmet + - helmets.heavy_mythril_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet + chestplate: + - armors.light_chainmail_chestplate + - armors.medium_chainmail_chestplate + - armors.heavy_chainmail_chestplate + - armors.light_steel_chestplate + - armors.medium_steel_chestplate + - armors.heavy_steel_chestplate + - armors.light_abyssalite_chestplate + - armors.medium_abyssalite_chestplate + - armors.heavy_abyssalite_chestplate + - armors.light_mythril_chestplate + - armors.medium_mythril_chestplate + - armors.heavy_mythril_chestplate + - armors.light_custom_chestplate + - armors.medium_custom_chestplate + - armors.heavy_custom_chestplate + leggings: + - armors.light_chainmail_leggings + - armors.medium_chainmail_leggings + - armors.heavy_chainmail_leggings + - armors.light_steel_leggings + - armors.medium_steel_leggings + - armors.heavy_steel_leggings + - armors.light_abyssalite_leggings + - armors.medium_abyssalite_leggings + - armors.heavy_abyssalite_leggings + - armors.light_mythril_leggings + - armors.medium_mythril_leggings + - armors.heavy_mythril_leggings + - armors.light_custom_leggings + - armors.medium_custom_leggings + - armors.heavy_custom_leggings + boots: + - armors.light_chainmail_boots + - armors.medium_chainmail_boots + - armors.heavy_chainmail_boots + - armors.light_steel_boots + - armors.medium_steel_boots + - armors.heavy_steel_boots + - armors.light_abyssalite_boots + - armors.medium_abyssalite_boots + - armors.heavy_abyssalite_boots + - armors.light_mythril_boots + - armors.medium_mythril_boots + - armors.heavy_mythril_boots + - armors.light_custom_boots + - armors.medium_custom_boots + - armors.heavy_custom_boots mage: helmet: - - mage_armors.mage_iron_helmet - - mage_armors.mage_steel_helmet - - mage_armors.mage_abyssalite_helmet - - mage_armors.mage_mythril_helmet - - mage_armors.mage_custom_helmet - chestplate: - - mage_armors.mage_iron_chestplate - - mage_armors.mage_steel_chestplate - - mage_armors.mage_abyssalite_chestplate - - mage_armors.mage_mythril_chestplate - - mage_armors.mage_custom_chestplate - leggings: - - mage_armors.mage_iron_leggings - - mage_armors.mage_steel_leggings - - mage_armors.mage_abyssalite_leggings - - mage_armors.mage_mythril_leggings - - mage_armors.mage_custom_leggings - boots: - - mage_armors.mage_iron_boots - - mage_armors.mage_steel_boots - - mage_armors.mage_abyssalite_boots - - mage_armors.mage_mythril_boots - - mage_armors.mage_custom_boots - + - mage_armors.mage_iron_helmet + - mage_armors.mage_steel_helmet + - mage_armors.mage_abyssalite_helmet + - mage_armors.mage_mythril_helmet + - mage_armors.mage_custom_helmet + chestplate: + - mage_armors.mage_iron_chestplate + - mage_armors.mage_steel_chestplate + - mage_armors.mage_abyssalite_chestplate + - mage_armors.mage_mythril_chestplate + - mage_armors.mage_custom_chestplate + leggings: + - mage_armors.mage_iron_leggings + - mage_armors.mage_steel_leggings + - mage_armors.mage_abyssalite_leggings + - mage_armors.mage_mythril_leggings + - mage_armors.mage_custom_leggings + boots: + - mage_armors.mage_iron_boots + - mage_armors.mage_steel_boots + - mage_armors.mage_abyssalite_boots + - mage_armors.mage_mythril_boots + - mage_armors.mage_custom_boots illusion: helmet: - - mage_armors.illusion_mage_helmet + - mage_armors.illusion_mage_helmet chestplate: - - mage_armors.illusion_mage_chestplate + - mage_armors.illusion_mage_chestplate leggings: - - mage_armors.illusion_mage_leggings + - mage_armors.illusion_mage_leggings boots: - - mage_armors.illusion_mage_boots - + - mage_armors.illusion_mage_boots spiritual: helmet: - - mage_armors.spiritual_mage_helmet + - mage_armors.spiritual_mage_helmet chestplate: - - mage_armors.spiritual_mage_chestplate + - mage_armors.spiritual_mage_chestplate leggings: - - mage_armors.spiritual_mage_leggings + - mage_armors.spiritual_mage_leggings boots: - - mage_armors.spiritual_mage_boots - + - mage_armors.spiritual_mage_boots bloodmagic: helmet: - - mage_armors.bloodmagic_mage_helmet + - mage_armors.bloodmagic_mage_helmet chestplate: - - mage_armors.bloodmagic_mage_chestplate + - mage_armors.bloodmagic_mage_chestplate leggings: - - mage_armors.bloodmagic_mage_leggings + - mage_armors.bloodmagic_mage_leggings boots: - - mage_armors.bloodmagic_mage_boots - + - mage_armors.bloodmagic_mage_boots necromancy: helmet: - - mage_armors.necromancy_mage_helmet + - mage_armors.necromancy_mage_helmet chestplate: - - mage_armors.necromancy_mage_chestplate + - mage_armors.necromancy_mage_chestplate leggings: - - mage_armors.necromancy_mage_leggings + - mage_armors.necromancy_mage_leggings boots: - - mage_armors.necromancy_mage_boots - + - mage_armors.necromancy_mage_boots arcanum: helmet: - - mage_armors.arcanum_mage_helmet + - mage_armors.arcanum_mage_helmet chestplate: - - mage_armors.arcanum_mage_chestplate + - mage_armors.arcanum_mage_chestplate leggings: - - mage_armors.arcanum_mage_leggings + - mage_armors.arcanum_mage_leggings boots: - - mage_armors.arcanum_mage_boots - + - mage_armors.arcanum_mage_boots shadowmancy: helmet: - - mage_armors.shadowmancy_mage_helmet + - mage_armors.shadowmancy_mage_helmet chestplate: - - mage_armors.shadowmancy_mage_chestplate + - mage_armors.shadowmancy_mage_chestplate leggings: - - mage_armors.shadowmancy_mage_leggings + - mage_armors.shadowmancy_mage_leggings boots: - - mage_armors.shadowmancy_mage_boots - + - mage_armors.shadowmancy_mage_boots wild mage: helmet: - - mage_armors.illusion_mage_helmet - - mage_armors.spiritual_mage_helmet - - mage_armors.bloodmagic_mage_helmet - - mage_armors.necromancy_mage_helmet - - mage_armors.arcanum_mage_helmet - - mage_armors.shadowmancy_mage_helmet - chestplate: - - mage_armors.illusion_mage_chestplate - - mage_armors.spiritual_mage_chestplate - - mage_armors.bloodmagic_mage_chestplate - - mage_armors.necromancy_mage_chestplate - - mage_armors.arcanum_mage_chestplate - - mage_armors.shadowmancy_mage_chestplate - leggings: - - mage_armors.illusion_mage_leggings - - mage_armors.spiritual_mage_leggings - - mage_armors.bloodmagic_mage_leggings - - mage_armors.necromancy_mage_leggings - - mage_armors.arcanum_mage_leggings - - mage_armors.shadowmancy_mage_leggings - boots: - - mage_armors.illusion_mage_boots - - mage_armors.spiritual_mage_boots - - mage_armors.bloodmagic_mage_boots - - mage_armors.necromancy_mage_boots - - mage_armors.arcanum_mage_boots - - mage_armors.shadowmancy_mage_boots - + - mage_armors.illusion_mage_helmet + - mage_armors.spiritual_mage_helmet + - mage_armors.bloodmagic_mage_helmet + - mage_armors.necromancy_mage_helmet + - mage_armors.arcanum_mage_helmet + - mage_armors.shadowmancy_mage_helmet + chestplate: + - mage_armors.illusion_mage_chestplate + - mage_armors.spiritual_mage_chestplate + - mage_armors.bloodmagic_mage_chestplate + - mage_armors.necromancy_mage_chestplate + - mage_armors.arcanum_mage_chestplate + - mage_armors.shadowmancy_mage_chestplate + leggings: + - mage_armors.illusion_mage_leggings + - mage_armors.spiritual_mage_leggings + - mage_armors.bloodmagic_mage_leggings + - mage_armors.necromancy_mage_leggings + - mage_armors.arcanum_mage_leggings + - mage_armors.shadowmancy_mage_leggings + boots: + - mage_armors.illusion_mage_boots + - mage_armors.spiritual_mage_boots + - mage_armors.bloodmagic_mage_boots + - mage_armors.necromancy_mage_boots + - mage_armors.arcanum_mage_boots + - mage_armors.shadowmancy_mage_boots light mage: helmet: - - mage_armors.illusion_mage_helmet - - mage_armors.spiritual_mage_helmet - - mage_armors.arcanum_mage_helmet + - mage_armors.illusion_mage_helmet + - mage_armors.spiritual_mage_helmet + - mage_armors.arcanum_mage_helmet chestplate: - - mage_armors.illusion_mage_chestplate - - mage_armors.spiritual_mage_chestplate - - mage_armors.arcanum_mage_chestplate + - mage_armors.illusion_mage_chestplate + - mage_armors.spiritual_mage_chestplate + - mage_armors.arcanum_mage_chestplate leggings: - - mage_armors.illusion_mage_leggings - - mage_armors.spiritual_mage_leggings - - mage_armors.arcanum_mage_leggings + - mage_armors.illusion_mage_leggings + - mage_armors.spiritual_mage_leggings + - mage_armors.arcanum_mage_leggings boots: - - mage_armors.illusion_mage_boots - - mage_armors.spiritual_mage_boots - - mage_armors.arcanum_mage_boots - + - mage_armors.illusion_mage_boots + - mage_armors.spiritual_mage_boots + - mage_armors.arcanum_mage_boots dark mage: helmet: - - mage_armors.bloodmagic_mage_helmet - - mage_armors.necromancy_mage_helmet - - mage_armors.shadowmancy_mage_helmet + - mage_armors.bloodmagic_mage_helmet + - mage_armors.necromancy_mage_helmet + - mage_armors.shadowmancy_mage_helmet chestplate: - - mage_armors.bloodmagic_mage_chestplate - - mage_armors.necromancy_mage_chestplate - - mage_armors.shadowmancy_mage_chestplate + - mage_armors.bloodmagic_mage_chestplate + - mage_armors.necromancy_mage_chestplate + - mage_armors.shadowmancy_mage_chestplate leggings: - - mage_armors.bloodmagic_mage_leggings - - mage_armors.necromancy_mage_leggings - - mage_armors.shadowmancy_mage_leggings + - mage_armors.bloodmagic_mage_leggings + - mage_armors.necromancy_mage_leggings + - mage_armors.shadowmancy_mage_leggings boots: - - mage_armors.bloodmagic_mage_boots - - mage_armors.necromancy_mage_boots - - mage_armors.shadowmancy_mage_boots - + - mage_armors.bloodmagic_mage_boots + - mage_armors.necromancy_mage_boots + - mage_armors.shadowmancy_mage_boots infantry: helmet: - - helmets.infantry_iron_helmet - - helmets.infantry_steel_helmet - - helmets.infantry_abyssalite_helmet - - helmets.infantry_mythril_helmet - - helmets.infantry_custom_helmet - chestplate: - - armors.infantry_iron_chestplate - - armors.infantry_steel_chestplate - - armors.infantry_abyssalite_chestplate - - armors.infantry_mythril_chestplate - - armors.infantry_custom_chestplate - leggings: - - armors.infantry_iron_leggings - - armors.infantry_steel_leggings - - armors.infantry_abyssalite_leggings - - armors.infantry_mythril_leggings - - armors.infantry_custom_leggings - boots: - - armors.infantry_iron_boots - - armors.infantry_steel_boots - - armors.infantry_abyssalite_boots - - armors.infantry_mythril_boots - - armors.infantry_custom_boots - + - helmets.infantry_iron_helmet + - helmets.infantry_steel_helmet + - helmets.infantry_abyssalite_helmet + - helmets.infantry_mythril_helmet + - helmets.infantry_custom_helmet + chestplate: + - armors.infantry_iron_chestplate + - armors.infantry_steel_chestplate + - armors.infantry_abyssalite_chestplate + - armors.infantry_mythril_chestplate + - armors.infantry_custom_chestplate + leggings: + - armors.infantry_iron_leggings + - armors.infantry_steel_leggings + - armors.infantry_abyssalite_leggings + - armors.infantry_mythril_leggings + - armors.infantry_custom_leggings + boots: + - armors.infantry_iron_boots + - armors.infantry_steel_boots + - armors.infantry_abyssalite_boots + - armors.infantry_mythril_boots + - armors.infantry_custom_boots exploration: helmet: - - helmets.cold_steel_helmet - - helmets.cold_abyssalite_helmet - - helmets.cold_mythril_helmet - chestplate: - - armors.cold_steel_chestplate - - armors.cold_abyssalite_chestplate - - armors.cold_mythril_chestplate - leggings: - - armors.cold_steel_leggings - - armors.cold_abyssalite_leggings - - armors.cold_mythril_leggings - boots: - - armors.cold_steel_boots - - armors.cold_abyssalite_boots - - armors.cold_mythril_boots - -##Items -# An entry with no item id (e.g. "shortswords") matches every MMOItems item of that -# type, so templates other plugins craft from (AdvancedCrafting, Magic) are covered. - + - helmets.cold_steel_helmet + - helmets.cold_abyssalite_helmet + - helmets.cold_mythril_helmet + chestplate: + - armors.cold_steel_chestplate + - armors.cold_abyssalite_chestplate + - armors.cold_mythril_chestplate + leggings: + - armors.cold_steel_leggings + - armors.cold_abyssalite_leggings + - armors.cold_mythril_leggings + boots: + - armors.cold_steel_boots + - armors.cold_abyssalite_boots + - armors.cold_mythril_boots swords: item: - - sword - + - sword.iron_sword + - sword.steel_sword + - sword.abyssalite_sword + - sword.mythril_sword + - sword.custom_sword + - sword battleaxes: item: - - battleaxes - + - battleaxes.iron_battleaxe + - battleaxes.steel_battleaxe + - battleaxes.abyssalite_battleaxe + - battleaxes.mythril_battleaxe + - battleaxes.custom_battleaxe + - battleaxes daggers: item: - - daggers - + - daggers.iron_dagger + - daggers.steel_dagger + - daggers.abyssalite_dagger + - daggers.mythril_dagger + - daggers.custom_dagger + - daggers warhammers: item: - - warhammers - + - warhammers.iron_warhammer + - warhammers.steel_warhammer + - warhammers.abyssalite_warhammer + - warhammers.mythril_warhammer + - warhammers.custom_warhammer + - warhammers spears: item: - - spears - + - spears.iron_spear + - spears.steel_spear + - spears.abyssalite_spear + - spears.mythril_spear + - spears.custom_spear + - spears polearms: item: - - polearms - + - polearms.iron_polearm + - polearms.steel_polearm + - polearms.abyssalite_polearm + - polearms.mythril_polearm + - polearms.custom_polearm + - polearms greathammers: item: - - greathammers - + - greathammers.iron_greathammer + - greathammers.steel_greathammer + - greathammers.abyssalite_greathammer + - greathammers.mythril_greathammer + - greathammers.custom_greathammer + - greathammers longswords: item: - - longswords - + - longswords.iron_longsword + - longswords.steel_longsword + - longswords.abyssalite_longsword + - longswords.mythril_longsword + - longswords.custom_longsword + - longswords greataxes: item: - - greataxes - + - greataxes.iron_greataxe + - greataxes.steel_greataxe + - greataxes.abyssalite_greataxe + - greataxes.mythril_greataxe + - greataxes.custom_greataxe + - greataxes shortbows: item: - - shortbows - + - shortbows.barkwood_shortbow + - shortbows.maplewood_shortbow + - shortbows.elderwood_shortbow + - shortbows.demonwood_shortbow + - shortbows.custom_shortbow + - shortbows longbows: item: - - longbows - + - longbows.barkwood_longbow + - longbows.maplewood_longbow + - longbows.elderwood_longbow + - longbows.demonwood_longbow + - longbows.custom_longbow + - longbows crossbows: item: - - crossbows - + - crossbows.barkwood_crossbow + - crossbows.maplewood_crossbow + - crossbows.elderwood_crossbow + - crossbows.demonwood_crossbow + - crossbows.custom_crossbow + - crossbows staffs: item: - - mage_staffs - + - mage_staffs.mage_custom_staff + - mage_staffs.mage_iron_staff + - mage_staffs.mage_steel_staff + - mage_staffs.mage_abyssalite_staff + - mage_staffs.mage_mythril_staff + - mage_staffs wands: item: - - mage_wands - + - mage_wands.mage_iron_wand + - mage_wands.mage_steel_wand + - mage_wands.mage_abyysalite_wand + - mage_wands.mage_mythril_wand + - mage_wands.mage_custom_wand + - mage_wands shortswords: item: - - shortswords -# - shortswords.custom_shortsword - + - shortswords.iron_shortsword + - shortswords.steel_shortsword + - shortswords.abyssalite_shortsword + - shortswords.mythril_shortsword + - shortswords.custom_shortsword + - shortswords lutes: item: - - lutes - + - lutes.custom_lute + - lutes.iron_lute + - lutes.steel_lute + - lutes.mythril_lute + - lutes.abyssalite_lute + - lutes shields: item: - - shields - + - shields.iron_shield + - shields.steel_shield + - shields.abyssalite_shield + - shields.mythril_shield + - shields.custom_shield + - shields battle standards: item: - - banners - + - banners.barkwood_banner + - banners.maplewood_banner + - banners.elderwood_banner + - banners.demonwood_banner + - banners.custom_banner + - banners helmets: item: - - helmets - - mage_armors.mage_iron_helmet - - mage_armors.mage_steel_helmet - - mage_armors.mage_abyssalite_helmet - - mage_armors.mage_mythril_helmet - - mage_armors.mage_custom_helmet - - mage_armors.illusion_mage_helmet - - mage_armors.spiritual_mage_helmet - - mage_armors.bloodmagic_mage_helmet - - mage_armors.necromancy_mage_helmet - - mage_armors.arcanum_mage_helmet - - mage_armors.shadowmancy_mage_helmet - + - helmets.light_chainmail_helmet + - helmets.medium_chainmail_helmet + - helmets.heavy_chainmail_helmet + - helmets.light_steel_helmet + - helmets.medium_steel_helmet + - helmets.heavy_steel_helmet + - helmets.light_abyssalite_helmet + - helmets.medium_abyssalite_helmet + - helmets.heavy_abyssalite_helmet + - helmets.light_mythril_helmet + - helmets.medium_mythril_helmet + - helmets.heavy_mythril_helmet + - helmets.light_custom_helmet + - helmets.medium_custom_helmet + - helmets.heavy_custom_helmet + - mage_armors.mage_iron_helmet + - mage_armors.mage_steel_helmet + - mage_armors.mage_abyssalite_helmet + - mage_armors.mage_mythril_helmet + - helmets.infantry_iron_helmet + - helmets.infantry_steel_helmet + - helmets.infantry_abyssalite_helmet + - helmets.infantry_mythril_helmet + - helmets.infantry_custom_helmet + - helmets.leather_helmet + - helmets.cold_steel_helmet + - helmets.cold_abyssalite_helmet + - helmets.cold_mythril_helmet + - mage_armors.mage_custom_helmet + - mage_armors.illusion_mage_helmet + - mage_armors.spiritual_mage_helmet + - mage_armors.bloodmagic_mage_helmet + - mage_armors.necromancy_mage_helmet + - mage_armors.arcanum_mage_helmet + - mage_armors.shadowmancy_mage_helmet + - helmets masks: item: - - masks.ghost_mask - - masks.monster_mask - - masks.oni_mask - - masks.urukhai_mask - - masks.samurai_mask - - masks.steampunk_mask - - masks.steampunk_robot_mask - - masks.skeleton_mask - - masks.skeleton_mask2 - - masks.zerratoris_mask - + - masks.ghost_mask + - masks.monster_mask + - masks.oni_mask + - masks.urukhai_mask + - masks.samurai_mask + - masks.steampunk_mask + - masks.steampunk_robot_mask + - masks.skeleton_mask + - masks.skeleton_mask2 + - masks.zerratoris_mask rifles: item: - - guns.rifle_template - + - guns.rifle_template pistols: item: - - guns.pistol_template - + - guns.pistol_template shotguns: item: - - guns.shotgun_template - + - guns.shotgun_template launchers: item: - - guns.launcher_template - + - guns.launcher_template mage blades: item: - - mage_swords - + - mage_swords.mage_custom_sword + - mage_swords.mage_iron_sword + - mage_swords.mage_steel_sword + - mage_swords.mage_abyysalite_sword + - mage_swords.mage_mythril_sword + - mage_swords hatchets: item: - - tools.iron_axe - - tools.steel_axe - - tools.abyssalite_axe - - tools.mythril_axe - + - tools.iron_axe + - tools.steel_axe + - tools.abyssalite_axe + - tools.mythril_axe hoes: item: - - tools.iron_hoe - - tools.steel_hoe - - tools.abyssalite_hoe - - tools.mythril_hoe - + - tools.iron_hoe + - tools.steel_hoe + - tools.abyssalite_hoe + - tools.mythril_hoe knives: item: - - tools.iron_hunting_knife - + - tools.iron_hunting_knife +pickaxes: + item: + - tools.iron_pickaxe + - tools.steel_pickaxe + - tools.abyssalite_pickaxe + - tools.mythril_pickaxe +shovels: + item: + - tools.iron_shovel + - tools.steel_shovel + - tools.abyssalite_shovel + - tools.mythril_shovel +fishing rods: + item: + - fishing_rods.fishing_rod + - fishing_rods.steel_rod + - fishing_rods.abyssalite_rod + - fishing_rods.mythril_rod +instruments: + item: + - instruments.accordion + - instruments.bagpipe + - instruments.celtic_harp + - instruments.dulcimer + - instruments.flute + - instruments.kalimba + - instruments.lute + - instruments.trumpet + - instruments.vielle books: item: - - books.writable_book - + - books.writable_book minor health: item: - - consumables.minor_health_potion - + - consumables.minor_health_potion major health: item: - - consumables.major_health_potion - + - consumables.major_health_potion minor mana: item: - - consumables.minor_mana_potion - + - consumables.minor_mana_potion major mana: item: - - consumables.major_mana_potion \ No newline at end of file + - consumables.major_mana_potion diff --git a/src/main/resources/permission-groups.yml b/src/main/resources/permission-groups.yml index 2c8bfa0..b6f7a35 100644 --- a/src/main/resources/permission-groups.yml +++ b/src/main/resources/permission-groups.yml @@ -37,7 +37,7 @@ groups: display-name: "⋫EE&lA⍉DD&lsόDCC&lc E27BB&le&#A620AB&ln&#AD1A9A&ld&#B41489&le&#BB0E78&ld&#C20867" name-colour-stops: 20 skin-token-cooldown-days: 14 - skin-kinds: [item_3d, shield, helmet_3d, mask, gun] + skin-kinds: [item_3d, shield, helmet_3d, mask, gun, bow_3d, crossbow_3d] allow-armor-3d-helmet: true # Uncomment to raise the combined texture+model budget for this rank: # max-3d-pair-bytes: 40960 diff --git a/src/test/java/net/tfminecraft/armourshop/pack/apply/PackApplyServiceTest.java b/src/test/java/net/tfminecraft/armourshop/pack/apply/PackApplyServiceTest.java index 05e2540..e63c75e 100644 --- a/src/test/java/net/tfminecraft/armourshop/pack/apply/PackApplyServiceTest.java +++ b/src/test/java/net/tfminecraft/armourshop/pack/apply/PackApplyServiceTest.java @@ -67,6 +67,8 @@ private static List filesFor(String kind) { case "bow" -> List.of("skin.png", "skin_0.png", "skin_1.png", "skin_2.png"); case "large_bow" -> List.of("skin.png", "skin_0.png", "skin_1.png", "skin_2.png", "skin.json", "skin_0.json", "skin_1.json", "skin_2.json"); case "crossbow" -> List.of("skin.png", "skin_0.png", "skin_1.png", "skin_2.png", "skin_charged.png"); + case "bow_3d" -> List.of("skin.png", "skin.png.mcmeta", "skin.json", "skin_0.json", "skin_1.json", "skin_2.json"); + case "crossbow_3d" -> List.of("skin.png", "skin.json", "skin_0.json", "skin_1.json", "skin_2.json", "skin_charged.json"); case "gun" -> List.of("skin.png", "skin_carry.json", "skin_reload.json", "skin_aim.json", "skin_aim_charged.json"); case "book" -> List.of("skin_unsigned.png", "skin_signed.png"); default -> List.of("skin.png"); @@ -99,7 +101,7 @@ private void assertFailure(Submission sub, String message) { } @Test void everyNonArmorKindWritesRealPackFilesAndRegistries() throws Exception { - for (String kind : List.of("handheld", "large_handheld", "bow", "large_bow", "crossbow", "item_3d", "shield", "helmet_3d", "mask", "gun", "book")) { + for (String kind : List.of("handheld", "large_handheld", "bow", "large_bow", "crossbow", "bow_3d", "crossbow_3d", "item_3d", "shield", "helmet_3d", "mask", "gun", "book")) { var sub = new Submission(); sub.kind = kind; sub.files = filesFor(kind); sub.namespace = "curated"; if (kind.equals("gun")) sub.base = "rifles"; if (kind.equals("handheld")) sub.display = null; @@ -108,6 +110,14 @@ private void assertFailure(Submission sub, String message) { assertEquals(1, result.writtenSubmissions.size()); assertEquals(sub.kind, result.writtenSubmissions.getFirst().kind); Path config = Path.of(Cache.iaContentsPath).resolve("curated/configs/skin.yml"); assertTrue(Files.isRegularFile(config), kind); assertTrue(Files.readString(config).contains("namespace: curated")); + if (kind.equals("crossbow_3d")) { + String firework = Files.readString(Path.of(Cache.iaContentsPath).resolve("curated/resourcepack/assets/curated/models/item/skin_firework.json")); + assertTrue(firework.contains("curated:item/skin")); assertTrue(Files.readString(config).contains("material: CROSSBOW")); + } + if (kind.equals("bow_3d")) { + // the animation is copied untouched, not namespace-rewritten like a model + assertArrayEquals(PNG, Files.readAllBytes(Path.of(Cache.iaContentsPath).resolve("curated/resourcepack/assets/curated/textures/item/skin.png.mcmeta"))); + } if (kind.equals("large_handheld")) { String model = Files.readString(Path.of(Cache.iaContentsPath).resolve("curated/resourcepack/assets/curated/models/item/skin.json")); assertTrue(model.contains("curated:item/skin")); assertFalse(model.contains("tfmc_submissions:")); @@ -129,9 +139,15 @@ private void assertFailure(Submission sub, String message) { @Test void flatAndThreeDimensionalArmorWriteEveryTier() throws Exception { var sub = new Submission(); sub.kind = "armor_set"; sub.tiers = List.of("iron", "steel"); sub.helmet3d = List.of("steel"); sub.namespace = "curated"; - var files = armorFiles("iron", false); files.addAll(armorFiles("steel", true)); sub.files = files; + var files = armorFiles("iron", false); files.addAll(armorFiles("steel", true)); + files.addAll(List.of("iron_helmet.png.mcmeta", "iron_boots.png.mcmeta", "iron_layer_1.png.mcmeta", "steel_helmet_texture.png.mcmeta")); sub.files = files; var summary = apply(sub); assertEquals(1, summary.written, summary.messages.toString()); Path root = Path.of(Cache.iaContentsPath).resolve("curated"); + assertTrue(Files.exists(root.resolve("resourcepack/assets/curated/textures/armor_icons/skin_iron_helmet.png.mcmeta"))); + assertTrue(Files.exists(root.resolve("resourcepack/assets/curated/textures/armor_icons/skin_iron_boots.png.mcmeta"))); + assertFalse(Files.exists(root.resolve("resourcepack/assets/curated/textures/armor_icons/skin_iron_chestplate.png.mcmeta"))); + assertTrue(Files.exists(root.resolve("resourcepack/assets/curated/textures/item/skin_steel_helmet.png.mcmeta"))); + client.verify(() -> ProvinceSystemClient.downloadSubmissionFile("skin", "iron_layer_1.png.mcmeta"), never()); assertTrue(Files.exists(root.resolve("configs/skin_iron.yml"))); assertTrue(Files.exists(root.resolve("configs/skin_steel.yml"))); assertArrayEquals(PNG, Files.readAllBytes(root.resolve("resourcepack/assets/curated/textures/armor_icons/skin_iron_helmet.png"))); assertTrue(Files.readString(root.resolve("resourcepack/assets/curated/models/item/skin_steel_helmet.json")).contains("curated:item/skin")); @@ -248,6 +264,10 @@ private static byte[] downloadedArmorModel(String submissionId, String tier) { client.when(() -> ProvinceSystemClient.downloadSubmissionFile("skin", "iron_helmet_model.json")).thenReturn(DownloadResult.success(MODEL)); client.when(() -> ProvinceSystemClient.downloadSubmissionFile("skin", "iron_helmet_texture.png")).thenReturn(DownloadResult.fail("offline")); assertFailure(sub, "download iron_helmet_texture.png: offline"); + sub.helmet3d = List.of(); sub.files = new ArrayList<>(armorFiles("iron", false)); sub.files.add("iron_chestplate.png.mcmeta"); + client.when(() -> ProvinceSystemClient.downloadSubmissionFile("skin", "iron_boots.png")).thenReturn(DownloadResult.success(PNG)); + client.when(() -> ProvinceSystemClient.downloadSubmissionFile("skin", "iron_chestplate.png.mcmeta")).thenReturn(DownloadResult.fail("offline")); + assertFailure(sub, "download iron_chestplate.png.mcmeta: offline"); } @Test void nonArmorFilesAndRequiredStemsAreValidated() { @@ -262,6 +282,9 @@ private static byte[] downloadedArmorModel(String submissionId, String tier) { sub.files = List.of("skin.png"); assertFailure(sub, "missing model"); sub.kind = "bow"; assertFailure(sub, "missing stem: pull_0"); sub.kind = "large_bow"; sub.files = filesFor("bow"); assertFailure(sub, "missing stem: model"); + sub.kind = "bow_3d"; sub.files = List.of("skin.json"); assertFailure(sub, "missing texture"); + sub.files = List.of("skin.png", "skin.json", "skin_0.json", "skin_1.json"); assertFailure(sub, "missing stem: model_2"); + sub.kind = "crossbow_3d"; sub.files = filesFor("bow_3d"); assertFailure(sub, "missing stem: model_charged"); sub.kind = "item_3d"; sub.files = List.of("skin.json"); assertFailure(sub, "missing texture"); sub.files = List.of("skin.png"); assertFailure(sub, "missing model"); sub.kind = "shield"; sub.files = filesFor("item_3d"); assertFailure(sub, "missing model_blocking"); @@ -310,6 +333,22 @@ private static byte[] downloadedArmorModel(String submissionId, String tier) { for (String frame : List.of("carry", "reload", "aim", "aim_charged")) assertEquals(frame, PackApplyService.stemFromFilename("gun", "skin", "skin_" + frame + ".json")); assertNull(PackApplyService.stemFromFilename("gun", "skin", "skin_other.json")); assertNull(PackApplyService.stemFromFilename("gun", "skin", "other.txt")); + for (String kind : List.of("bow_3d", "crossbow_3d")) { + assertEquals("texture", PackApplyService.stemFromFilename(kind, "skin", "skin.png")); + assertEquals("model", PackApplyService.stemFromFilename(kind, "skin", "skin.json")); + for (String frame : List.of("0", "1", "2")) assertEquals("model_" + frame, PackApplyService.stemFromFilename(kind, "skin", "skin_" + frame + ".json")); + assertNull(PackApplyService.stemFromFilename(kind, "skin", "skin_0.png")); + assertNull(PackApplyService.stemFromFilename(kind, "skin", "skin_firework.json")); + } + assertEquals("model_charged", PackApplyService.stemFromFilename("crossbow_3d", "skin", "skin_charged.json")); + assertNull(PackApplyService.stemFromFilename("bow_3d", "skin", "skin_charged.json")); + assertEquals("texture.mcmeta", PackApplyService.stemFromFilename("handheld", "skin", "skin.png.mcmeta")); + assertEquals("pull_1.mcmeta", PackApplyService.stemFromFilename("bow", "skin", "skin_1.png.mcmeta")); + assertEquals("charged.mcmeta", PackApplyService.stemFromFilename("crossbow", "skin", "skin_arrow.png.mcmeta")); + assertEquals("unsigned.mcmeta", PackApplyService.stemFromFilename("book", "skin", "skin_unsigned.png.mcmeta")); + assertEquals("texture.mcmeta", PackApplyService.stemFromFilename("crossbow_3d", "skin", "skin.png.mcmeta")); + assertNull(PackApplyService.stemFromFilename("handheld", "skin", "skin.json.mcmeta")); + assertNull(PackApplyService.stemFromFilename("handheld", "skin", "other.png.mcmeta")); assertEquals("model_blocking", PackApplyService.stemFromFilename("shield", "skin", "skin_blocking.json")); assertEquals("model", PackApplyService.stemFromFilename("item_3d", "skin", "skin.json")); assertEquals("texture", PackApplyService.stemFromFilename("item_3d", "skin", "skin.png")); diff --git a/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java b/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java index 9a23a14..71575dc 100644 --- a/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java +++ b/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java @@ -260,7 +260,10 @@ private void invalidationDuringFetch(boolean staff, boolean fail) throws Excepti List expected = new ArrayList<>(); for (String slug : List.of("skin_iron", "skin")) { expected.add(put(PackPaths.configsDir(temp, "custom").resolve(slug + ".yml"))); - for (String stem : List.of("helmet", "chestplate", "leggings", "boots")) expected.add(put(PackPaths.armorIconsDir(temp, "custom").resolve(slug + "_" + stem + ".png"))); + for (String stem : List.of("helmet", "chestplate", "leggings", "boots")) { + expected.add(put(PackPaths.armorIconsDir(temp, "custom").resolve(slug + "_" + stem + ".png"))); + expected.add(put(PackPaths.armorIconsDir(temp, "custom").resolve(slug + "_" + stem + ".png.mcmeta"))); + } for (int layer : List.of(1, 2)) expected.add(put(PackPaths.armorLayersDir(temp, "custom").resolve(slug + "_layer_" + layer + ".png"))); expected.add(put(PackPaths.itemTexturesDir(temp, "custom").resolve(slug + "_helmet.png"))); expected.add(put(PackPaths.itemModelsDir(temp, "custom").resolve(slug + "_helmet.json"))); } @@ -270,15 +273,22 @@ private void invalidationDuringFetch(boolean staff, boolean fail) throws Excepti } @Test void removerDeletesNonArmorVariantsRegistriesAndToleratesMissingFiles() throws Exception { - for (String kind : Arrays.asList("bow", "book", "gun", "mask", null)) { + for (String kind : Arrays.asList("bow", "crossbow_3d", "book", "gun", "mask", null)) { String ns = PackPaths.playerNamespace(); List expected = new ArrayList<>(); expected.add(put(PackPaths.configsDir(temp).resolve("skin.yml"))); - if ("book".equals(kind)) for (String suffix : List.of("_unsigned", "_signed")) expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png"))); + if ("book".equals(kind)) for (String suffix : List.of("_unsigned", "_signed")) { + expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png"))); + expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png.mcmeta"))); + } else { expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin.png"))); + expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin.png.mcmeta"))); if ("gun".equals(kind)) for (String suffix : List.of("_carry", "_reload", "_aim", "_aim_charged")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); else { - for (String suffix : List.of("", "_0", "_1", "_2", "_charged", "_blocking")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); - for (String suffix : List.of("_0", "_1", "_2", "_charged", "_arrow")) expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png"))); + for (String suffix : List.of("", "_0", "_1", "_2", "_charged", "_firework", "_blocking")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); + for (String suffix : List.of("_0", "_1", "_2", "_charged", "_arrow")) { + expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png"))); + expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png.mcmeta"))); + } } } Cache.gunsSkinsYmlPath = temp.resolve("skins.yml").toString(); Files.writeString(Path.of(Cache.gunsSkinsYmlPath), "skin:\n types:\n - rifle\n"); diff --git a/src/test/java/net/tfminecraft/armourshop/pack/writer/PackWritersTest.java b/src/test/java/net/tfminecraft/armourshop/pack/writer/PackWritersTest.java index 695ba92..dce856e 100644 --- a/src/test/java/net/tfminecraft/armourshop/pack/writer/PackWritersTest.java +++ b/src/test/java/net/tfminecraft/armourshop/pack/writer/PackWritersTest.java @@ -50,6 +50,8 @@ private List specs() { new Spec(PackKind.BOW, 5, s -> BowWriter.write(temp,s), (s,n) -> BowWriter.write(temp,s,n)), new Spec(PackKind.LARGE_BOW, 9, s -> LargeBowWriter.write(temp,s), (s,n) -> LargeBowWriter.write(temp,s,n)), new Spec(PackKind.CROSSBOW, 7, s -> BowWriter.writeCrossbow(temp,s), (s,n) -> BowWriter.writeCrossbow(temp,s,n)), + new Spec(PackKind.BOW_3D, 6, s -> Bow3dWriter.write(temp,s,PackPaths.playerNamespace()), (s,n) -> Bow3dWriter.write(temp,s,n)), + new Spec(PackKind.CROSSBOW_3D, 8, s -> Bow3dWriter.write(temp,s,PackPaths.playerNamespace()), (s,n) -> Bow3dWriter.write(temp,s,n)), new Spec(PackKind.ITEM_3D, 3, s -> Item3dWriter.write(temp,s), (s,n) -> Item3dWriter.write(temp,s,n)), new Spec(PackKind.HELMET_3D, 3, s -> Item3dWriter.writeHelmet3d(temp,s), (s,n) -> Item3dWriter.writeHelmet3d(temp,s,n)), new Spec(PackKind.MASK, 3, s -> Item3dWriter.writeMask(temp,s,PackPaths.playerNamespace()), (s,n) -> Item3dWriter.writeMask(temp,s,n)), @@ -60,7 +62,7 @@ private List specs() { private Map files() { Map files = new LinkedHashMap<>(); for (String key : List.of("helmet","chestplate","leggings","boots","layer_1","layer_2","texture","pull_0","pull_1","pull_2","charged","unsigned","signed","helmet_texture")) files.put(key,png); - for (String key : List.of("model","model_0","model_1","model_2","model_blocking","carry","reload","aim","aim_charged")) files.put(key,MODEL); + for (String key : List.of("model","model_0","model_1","model_2","model_charged","model_blocking","carry","reload","aim","aim_charged")) files.put(key,MODEL); return files; } private PackSubmission submission(PackKind kind) {return new PackSubmission(kind.name().toLowerCase(Locale.ROOT), "Skin \"quoted\"", kind, files());} @@ -83,11 +85,41 @@ private Map files() { assertEquals("item/handheld",yaml(PackPaths.configsDir(temp).resolve("handheld.yml")).getString("items.handheld.resource.parent")); assertTrue(yaml(PackPaths.configsDir(temp).resolve("helmet_3d.yml")).getBoolean("items.helmet_3d.behaviours.hat")); assertEquals("WRITTEN_BOOK",yaml(PackPaths.configsDir(temp).resolve("book.yml")).getString("items.book_signed.resource.material")); + var bow3d=yaml(PackPaths.configsDir(temp).resolve("bow_3d.yml")); + assertEquals("BOW",bow3d.getString("items.bow_3d.resource.material")); assertFalse(bow3d.getBoolean("items.bow_3d.resource.generate")); + assertEquals("item/bow_3d",bow3d.getString("items.bow_3d.resource.model_path")); + var crossbow3d=yaml(PackPaths.configsDir(temp).resolve("crossbow_3d.yml")); + assertEquals("CROSSBOW",crossbow3d.getString("items.crossbow_3d.resource.material")); + for (String suffix : List.of("", "_0", "_1", "_2", "_charged", "_firework")) assertTrue(Files.isRegularFile(PackPaths.itemModelsDir(temp).resolve("crossbow_3d" + suffix + ".json")), suffix); + assertFalse(Files.exists(PackPaths.itemModelsDir(temp).resolve("bow_3d_charged.json"))); Map armor=files(); armor.put("helmet_model",MODEL); var written=ArmorSetWriter.write(temp,new PackSubmission("three_d","3D",PackKind.ARMOR_SET,armor)); assertEquals(8,written.size()); assertTrue(yaml(PackPaths.configsDir(temp).resolve("three_d.yml")).getBoolean("items.three_d_helmet.behaviours.hat")); } + @Test void animatedTexturesGetTheirMcmetaBesideEveryPngExceptArmourLayers() throws Exception { + byte[] meta = "{\"animation\":{\"frametime\":2}}".getBytes(StandardCharsets.UTF_8); + Map files = files(); + for (String stem : List.of("texture","pull_0","charged","unsigned","helmet","chestplate","layer_1")) files.put(stem + ".mcmeta", meta); + Path itemTex = PackPaths.itemTexturesDir(temp); + var crossbow = BowWriter.writeCrossbow(temp, new PackSubmission("xb", "XB", PackKind.CROSSBOW, files)); + for (String name : List.of("xb.png.mcmeta","xb_0.png.mcmeta","xb_charged.png.mcmeta","xb_arrow.png.mcmeta")) { + assertTrue(crossbow.contains(itemTex.resolve(name)), name); assertArrayEquals(meta, Files.readAllBytes(itemTex.resolve(name))); + } + assertFalse(Files.exists(itemTex.resolve("xb_1.png.mcmeta"))); + Bow3dWriter.write(temp, new PackSubmission("b3", "B3", PackKind.BOW_3D, files), PackPaths.playerNamespace()); + assertArrayEquals(meta, Files.readAllBytes(itemTex.resolve("b3.png.mcmeta"))); + BookWriter.write(temp, new PackSubmission("bk", "BK", PackKind.BOOK, files)); + assertTrue(Files.exists(itemTex.resolve("bk_unsigned.png.mcmeta"))); assertFalse(Files.exists(itemTex.resolve("bk_signed.png.mcmeta"))); + ArmorSetWriter.write(temp, new PackSubmission("ar", "AR", PackKind.ARMOR_SET, files)); + assertTrue(Files.exists(PackPaths.armorIconsDir(temp).resolve("ar_helmet.png.mcmeta"))); + assertTrue(Files.exists(PackPaths.armorIconsDir(temp).resolve("ar_chestplate.png.mcmeta"))); + assertFalse(Files.exists(PackPaths.armorLayersDir(temp).resolve("ar_layer_1.png.mcmeta"))); + // Re-applying without an animation removes the old one. + FlatItemWriter.write(temp, new PackSubmission("xb", "XB", PackKind.HANDHELD, files())); + assertFalse(Files.exists(itemTex.resolve("xb.png.mcmeta"))); + } + @Test void writersRejectWrongKindNamespaceAndMissingRequiredAssets() throws Exception { for (Spec spec:specs()) { var wrong=submission(spec.kind==PackKind.ARMOR_SET ? PackKind.ITEM : PackKind.ARMOR_SET); From 210f39dda2581b3a3b92b46289304f986af009a5 Mon Sep 17 00:00:00 2001 From: Drefvelin <54400154+Drefvelin@users.noreply.github.com> Date: Tue, 20 Oct 2026 08:56:42 +0000 Subject: [PATCH 2/2] Delete the firework model only for 3D crossbows Co-Authored-By: Claude Opus 5.5 --- .../armourshop/pack/delete/PackSubmissionRemover.java | 9 ++++++--- .../armourshop/pack/delete/PackDeletionTest.java | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java b/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java index 74540b0..cdfe361 100644 --- a/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java +++ b/src/main/java/net/tfminecraft/armourshop/pack/delete/PackSubmissionRemover.java @@ -92,7 +92,7 @@ public static List remove( } else if ("gun".equals(k)) { removed.addAll(GunWriter.remove(contentsRoot, ns, s, gunsSkinsYmlOrNull())); } else if ("mask".equals(k)) { - removeNonArmorPack(contentsRoot, ns, s, removed); + removeNonArmorPack(contentsRoot, ns, s, k, removed); try { MasksYml.remove(masksYmlOrNull(), s); } catch (RuntimeException e) { @@ -103,7 +103,7 @@ public static List remove( } else if ("book".equals(k)) { removed.addAll(BookWriter.remove(contentsRoot, ns, s)); } else { - removeNonArmorPack(contentsRoot, ns, s, removed); + removeNonArmorPack(contentsRoot, ns, s, k, removed); } if (log != null) { @@ -146,6 +146,7 @@ private static void removeNonArmorPack( Path contentsRoot, String namespace, String s, + String kind, List removed ) throws IOException { Path configs = PackPaths.configsDir(contentsRoot, namespace); @@ -168,7 +169,9 @@ private static void removeNonArmorPack( ); } // crossbow_3d firework model is a copy of the charged one - deleteQuiet(models.resolve(s + "_firework.json"), removed); + if ("crossbow_3d".equals(kind)) { + deleteQuiet(models.resolve(s + "_firework.json"), removed); + } TextureFiles.deleteWithMcmeta(itemTex.resolve(s + "_arrow.png"), removed); } diff --git a/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java b/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java index 71575dc..bbe3071 100644 --- a/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java +++ b/src/test/java/net/tfminecraft/armourshop/pack/delete/PackDeletionTest.java @@ -284,7 +284,9 @@ private void invalidationDuringFetch(boolean staff, boolean fail) throws Excepti expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin.png.mcmeta"))); if ("gun".equals(kind)) for (String suffix : List.of("_carry", "_reload", "_aim", "_aim_charged")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); else { - for (String suffix : List.of("", "_0", "_1", "_2", "_charged", "_firework", "_blocking")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); + for (String suffix : List.of("", "_0", "_1", "_2", "_charged", "_blocking")) expected.add(put(PackPaths.itemModelsDir(temp).resolve("skin" + suffix + ".json"))); + Path firework = put(PackPaths.itemModelsDir(temp).resolve("skin_firework.json")); + if ("crossbow_3d".equals(kind)) expected.add(firework); for (String suffix : List.of("_0", "_1", "_2", "_charged", "_arrow")) { expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png"))); expected.add(put(PackPaths.itemTexturesDir(temp).resolve("skin" + suffix + ".png.mcmeta")));