package com.xfestudio.xfeservermanager.core.content;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Declarative content only: no executable bytecode, arbitrary NBT, or host object references. */
public record ContentDefinition(String id, Kind kind, String name, String description,
String archetype, Map<String, String> properties, List<Behavior> behaviors) {
public enum Kind { ITEM, BLOCK, ENTITY }
public static final int MAX_BEHAVIORS = 32;
public ContentDefinition {
id = text(id, "content id", 1, 80);
if (!id.matches("xfesmcontent:[a-z][a-z0-9_]{0,63}")) {
throw new IllegalArgumentException("content id must be xfesmcontent:<lowercase_identifier>");
}
Objects.requireNonNull(kind, "content kind");
name = text(name, "content name", 1, 96);
description = text(description, "content description", 0, 2048);
archetype = text(archetype, "content archetype", 1, 32);
properties = safeMap(properties, "content properties", 48);
behaviors = behaviors == null ? List.of() : List.copyOf(behaviors);
if (behaviors.size() > MAX_BEHAVIORS) throw new IllegalArgumentException("too many content behaviors");
var ids = new HashSet<String>();
for (Behavior behavior : behaviors) {
if (!ids.add(behavior.id())) throw new IllegalArgumentException("duplicate behavior id: " + behavior.id());
}
ContentCatalog.validate(kind, archetype, properties, behaviors);
}
public record Behavior(String id, String event, String action, Map<String, String> parameters,
int cooldownTicks, boolean cancelVanilla) {
public Behavior {
id = text(id, "behavior id", 1, 64);
if (!id.matches("[A-Za-z_][A-Za-z0-9_-]{0,63}")) throw new IllegalArgumentException("invalid behavior id");
event = text(event, "behavior event", 1, 32);
action = text(action, "behavior action", 1, 32);
parameters = safeMap(parameters, "behavior parameters", 16);
if (cooldownTicks < 0 || cooldownTicks > 1_728_000) {
throw new IllegalArgumentException("cooldownTicks must be between 0 and 1728000");
}
}
}
static Map<String, String> safeMap(Map<String, String> values, String name, int maximum) {
if (values == null) return Map.of();
if (values.size() > maximum) throw new IllegalArgumentException(name + " has too many entries");
values.forEach((key, value) -> {
text(key, name + " key", 1, 64);
text(value, name + " value", 0, 1024);
});
return Map.copyOf(values);
}
static String text(String value, String name, int minimum, int maximum) {
String safe = Objects.toString(value, "");
if (safe.length() < minimum || safe.length() > maximum || safe.codePoints().anyMatch(Character::isISOControl)) {
throw new IllegalArgumentException(name + " must contain " + minimum + "-" + maximum + " printable characters");
}
return safe;
}
}
package com.xfestudio.xfeservermanager.core.content;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Declarative content only: no executable bytecode, arbitrary NBT, or host object references. */
public record ContentDefinition(String id, Kind kind, String name, String description,
String archetype, Map<String, String> properties, List<Behavior> behaviors) {
public enum Kind { ITEM, BLOCK, ENTITY }
public static final int MAX_BEHAVIORS = 32;
public ContentDefinition {
id = text(id, "content id", 1, 80);
if (!id.matches("xfesmcontent:[a-z][a-z0-9_]{0,63}")) {
throw new IllegalArgumentException("content id must be xfesmcontent:<lowercase_identifier>");
}
Objects.requireNonNull(kind, "content kind");
name = text(name, "content name", 1, 96);
description = text(description, "content description", 0, 2048);
archetype = text(archetype, "content archetype", 1, 32);
properties = safeMap(properties, "content properties", 48);
behaviors = behaviors == null ? List.of() : List.copyOf(behaviors);
if (behaviors.size() > MAX_BEHAVIORS) throw new IllegalArgumentException("too many content behaviors");
var ids = new HashSet<String>();
for (Behavior behavior : behaviors) {
if (!ids.add(behavior.id())) throw new IllegalArgumentException("duplicate behavior id: " + behavior.id());
}
ContentCatalog.validate(kind, archetype, properties, behaviors);
}
public record Behavior(String id, String event, String action, Map<String, String> parameters,
int cooldownTicks, boolean cancelVanilla) {
public Behavior {
id = text(id, "behavior id", 1, 64);
if (!id.matches("[A-Za-z_][A-Za-z0-9_-]{0,63}")) throw new IllegalArgumentException("invalid behavior id");
event = text(event, "behavior event", 1, 32);
action = text(action, "behavior action", 1, 32);
parameters = safeMap(parameters, "behavior parameters", 16);
if (cooldownTicks < 0 || cooldownTicks > 1_728_000) {
throw new IllegalArgumentException("cooldownTicks must be between 0 and 1728000");
}
}
}
static Map<String, String> safeMap(Map<String, String> values, String name, int maximum) {
if (values == null) return Map.of();
if (values.size() > maximum) throw new IllegalArgumentException(name + " has too many entries");
values.forEach((key, value) -> {
text(key, name + " key", 1, 64);
text(value, name + " value", 0, 1024);
});
return Map.copyOf(values);
}
static String text(String value, String name, int minimum, int maximum) {
String safe = Objects.toString(value, "");
if (safe.length() < minimum || safe.length() > maximum || safe.codePoints().anyMatch(Character::isISOControl)) {
throw new IllegalArgumentException(name + " must contain " + minimum + "-" + maximum + " printable characters");
}
return safe;
}
}