Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/main/java/net/tfminecraft/armourshop/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public class Cache {
/** GunsAndGadgets skins.yml (Step 15). */
public static String gunsSkinsYmlPath = "";

/**
* RPCharacters custom mask registry. Blank config falls back to this path
* so an existing live config.yml still registers masks.
*/
public static String masksYmlPath = "plugins/RPCharacters/custom-masks.yml";

/** Daily force pull+reload at HH:mm server local; blank disables. */
public static String forceReloadTime = "06:00";
/** Seconds between iareload and iazip. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void load(File configFile) {
String gunsSkins = config.getString("pack-apply.guns-skins-yml", "");
Cache.gunsSkinsYmlPath = gunsSkins == null ? "" : gunsSkins.trim();

String masksYml = config.getString("pack-apply.masks-yml", "");
if (masksYml == null || masksYml.isBlank()) {
masksYml = "plugins/RPCharacters/custom-masks.yml";
}
Cache.masksYmlPath = masksYml.trim();

String forceTime = config.getString("pack-apply.force-reload-time", "06:00");
Cache.forceReloadTime = forceTime == null ? "" : forceTime.trim();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.tfminecraft.armourshop.pack.writer.flat.FlatItemWriter;
import net.tfminecraft.armourshop.pack.writer.gun.GunWriter;
import net.tfminecraft.armourshop.pack.writer.large.LargeHandheldWriter;
import net.tfminecraft.armourshop.pack.writer.mask.MasksYml;
import net.tfminecraft.armourshop.pack.writer.model3d.Item3dWriter;
import net.tfminecraft.armourshop.pack.writer.model3d.ShieldWriter;
import java.nio.file.Path;
Expand Down Expand Up @@ -479,6 +480,15 @@ private static void writeKind(
ns
);
return;
case "mask":
requireModel3d(packFiles);
Item3dWriter.writeMask(
contentsRoot,
new PackSubmission(slug, display, PackKind.MASK, packFiles),
ns
);
MasksYml.upsert(requireMasksYml(), slug, ns);
return;
case "gun":
requireGun(packFiles);
GunWriter.write(
Expand Down Expand Up @@ -562,6 +572,16 @@ private static void requireGun(Map<String, byte[]> files) {
}
}

static Path requireMasksYml() {
String masks = Cache.masksYmlPath;
if (masks == null || masks.isBlank()) {
throw new IllegalStateException(
"pack-apply.masks-yml is not set in config.yml"
);
}
return Path.of(masks.trim());
}

static Path requireGunsSkinsYml() {
String skins = Cache.gunsSkinsYmlPath;
if (skins == null || skins.isBlank()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.tfminecraft.armourshop.pack.model.PackPaths;
import net.tfminecraft.armourshop.pack.writer.flat.BookWriter;
import net.tfminecraft.armourshop.pack.writer.gun.GunWriter;
import net.tfminecraft.armourshop.pack.writer.mask.MasksYml;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -80,6 +81,15 @@ public static List<Path> remove(
removeArmorPack(contentsRoot, ns, s, removed);
} else if ("gun".equals(k)) {
removed.addAll(GunWriter.remove(contentsRoot, ns, s, gunsSkinsYmlOrNull()));
} else if ("mask".equals(k)) {
removeNonArmorPack(contentsRoot, ns, s, removed);
try {
MasksYml.remove(masksYmlOrNull(), s);
} catch (RuntimeException e) {
if (log != null) {
log.warning("[pack-delete] mask registry remove failed: " + e.getMessage());
}
}
} else if ("book".equals(k)) {
removed.addAll(BookWriter.remove(contentsRoot, ns, s));
} else {
Expand Down Expand Up @@ -156,6 +166,14 @@ private static void deleteQuiet(Path path, List<Path> removed) throws IOExceptio
}
}

private static Path masksYmlOrNull() {
String masks = Cache.masksYmlPath;
if (masks == null || masks.isBlank()) {
return null;
}
return Path.of(masks.trim());
}

private static Path gunsSkinsYmlOrNull() {
String skins = Cache.gunsSkinsYmlPath;
if (skins == null || skins.isBlank()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum PackKind {
ITEM_3D,
SHIELD,
HELMET_3D,
MASK,
GUN,
BOOK
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ private static boolean isItemKind(String kind) {
|| "item_3d".equals(kind)
|| "shield".equals(kind)
|| "helmet_3d".equals(kind)
|| "mask".equals(kind)
|| "gun".equals(kind)
|| "book".equals(kind);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package net.tfminecraft.armourshop.pack.writer.mask;

import net.tfminecraft.armourshop.pack.util.YamlUtil;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Upsert / remove RPCharacters {@code custom-masks.yml} entries (Bukkit-free).
* Each entry is the ItemsAdder path of an approved mask skin.
*/
public final class MasksYml {

private MasksYml() {}

public static void upsert(Path masksYml, String slug, String namespace)
throws IOException
{
if (masksYml == null) {
throw new IllegalArgumentException("masksYml is null");
}
String key = requireSlug(slug);
String item = "ia." + requireNamespace(namespace) + ":" + key;
Path parent = masksYml.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Map<String, String> entries = read(masksYml);
entries.put(key, item);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
write(masksYml, entries);
}

public static boolean remove(Path masksYml, String slug) throws IOException {
if (masksYml == null || !Files.isRegularFile(masksYml)) {
return false;
}
String key = requireSlug(slug);
Map<String, String> entries = read(masksYml);
if (entries.remove(key) == null) {
return false;
}
write(masksYml, entries);
return true;
}

static Map<String, String> read(Path masksYml) throws IOException {
Map<String, String> entries = new LinkedHashMap<>();
if (masksYml == null || !Files.isRegularFile(masksYml)) {
return entries;
}
String current = null;
for (String raw : Files.readString(masksYml, StandardCharsets.UTF_8).split("\\R")) {
String line = raw.stripTrailing();
String trimmed = line.trim();
if (trimmed.isEmpty() || trimmed.startsWith("#")) {
continue;
}
if ("masks:".equals(trimmed)) {
current = null;
continue;
}
if (trimmed.endsWith(":") && !trimmed.contains(" ") && !trimmed.contains("\t")) {
current = trimmed.substring(0, trimmed.length() - 1);
continue;
}
if (current != null && trimmed.startsWith("item:")) {
String item = trimmed.substring("item:".length()).trim();
if (item.isEmpty()) {
throw new IOException("mask '" + current + "' is missing an item path");
}
entries.put(current, item);
current = null;
continue;
}
throw new IOException("unrecognized custom-masks.yml line: " + trimmed);
}
return entries;
}

private static void write(Path masksYml, Map<String, String> entries) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("# Custom RP masks written by ArmourShop. Loaded by RPCharacters with masks.yml.\n");
sb.append("masks:\n");
for (Map.Entry<String, String> entry : entries.entrySet()) {
sb.append(" ").append(entry.getKey()).append(":\n");
sb.append(" item: ").append(entry.getValue()).append('\n');
}
Path parent = masksYml.getParent();
Path tmp = parent == null
? Path.of(masksYml.getFileName().toString() + ".tmp")
: parent.resolve(masksYml.getFileName().toString() + ".tmp");
Files.writeString(tmp, sb.toString(), StandardCharsets.UTF_8);
try {
Files.move(
tmp,
masksYml,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING
);
} catch (AtomicMoveNotSupportedException e) {
Files.move(tmp, masksYml, StandardCopyOption.REPLACE_EXISTING);
}
}

private static String requireSlug(String slug) {
if (slug == null || slug.isBlank()) {
throw new IllegalArgumentException("missing slug");
}
String key = slug.trim();
YamlUtil.validateSlug(key);
return key;
}

private static String requireNamespace(String namespace) {
if (namespace == null || namespace.isBlank()) {
throw new IllegalArgumentException("missing namespace");
}
String ns = namespace.trim();
for (int i = 0; i < ns.length(); i++) {
char c = ns.charAt(i);
boolean ok = (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9')
|| c == '_'
|| c == '-';
if (!ok) {
throw new IllegalArgumentException("invalid namespace: " + namespace);
}
}
return ns;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,30 @@ public static List<Path> writeHelmet3d(
);
}

/**
* Writes mask skins as an unplaceable carved pumpkin hat, same pack shape as helmet_3d.
* RPCharacters identity comes from the mask registry, not from this YAML.
*/
public static List<Path> writeMask(
Path contentsRoot,
PackSubmission submission,
String namespace
) throws IOException {
if (submission.kind() != PackKind.MASK) {
throw new IllegalArgumentException(
"writeMask requires MASK, got " + submission.kind()
);
}
return writeModelItem(
contentsRoot,
submission,
"CARVED_PUMPKIN",
null,
true,
namespace
);
}

/**
* Shared pack write for standalone 3D items (item_3d / helmet_3d).
*
Expand Down
13 changes: 13 additions & 0 deletions src/main/resources/base-sets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,19 @@ helmets:
- helmets.infantry_mythril_helmet
- helmets.infantry_custom_helmet

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

rifles:
item:
- guns.rifle_template
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/categories.yml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ i_helmets:
item: m.helmets.shako
is-item: true

i_masks:
name: "Masks"
colour: "#7f7d80"
item: m.masks.ghost_mask
is-item: true

i_guns:
name: "Guns"
colour: "#7f7d80"
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pack-apply:
categories-path: "plugins/ArmourShop/Categories"
# GunsAndGadgets skins.yml (Step 15 — IA ids; no CMD dual-write)
guns-skins-yml: "plugins/GunsAndGadgets/skins.yml"
# RPCharacters custom mask registry (mask skin kind)
masks-yml: "plugins/RPCharacters/custom-masks.yml"
# Daily force pull + IA refresh at this server-local time (HH:mm). Blank disables.
force-reload-time: "06:00"
# Seconds to wait after /iareload before /iazip
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/permission-groups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ groups:
display-name: "&#8939EE&lA&#9033DD&ls&#972DCC&lc&#9E27BB&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, gun]
skin-kinds: [item_3d, shield, helmet_3d, mask, gun]
allow-armor-3d-helmet: true
# Uncomment to raise the combined texture+model budget for this rank:
# max-3d-pair-bytes: 40960
Expand Down
Loading