From 26598370077d3912a9f96b9c9b1c87806349e78c Mon Sep 17 00:00:00 2001 From: piexel Date: Tue, 20 Oct 2026 13:31:31 +0200 Subject: [PATCH] Give each procedural type its own schema Shared folder mixed tree and coral schemas. --- .../iris/core/project/SchemaBuilder.java | 10 ++++-- .../procedural/ProceduralObjectCatalog.java | 31 ++++++++++++------- .../procedural/ProceduralObjectSettings.java | 2 +- .../engine/object/procedural/coral/Coral.java | 2 +- .../object/procedural/crystal/Crystal.java | 2 +- .../procedural/formation/Formation.java | 2 +- .../object/procedural/fungi/Fungus.java | 2 +- .../engine/object/procedural/ruin/Ruin.java | 2 +- .../object/procedural/ruin/RuinDecorator.java | 2 +- .../engine/object/procedural/tree/Tree.java | 2 +- .../object/procedural/tree/TreeBranches.java | 2 +- .../object/procedural/tree/TreeCanopy.java | 2 +- .../object/procedural/tree/TreeDecorator.java | 2 +- .../object/procedural/tree/TreeLayer.java | 2 +- .../procedural/tree/TreeSecondaryLeaf.java | 2 +- .../procedural/tree/TreeSubBranches.java | 2 +- 16 files changed, 40 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java index 5c4373452..3da4f6eb6 100644 --- a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java +++ b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java @@ -638,13 +638,17 @@ public class SchemaBuilder { JSONArray arr = new JSONArray(); JSONObject str = new JSONObject(); str.put("type", "string"); - String key = "enum-snippet-" + snippet.value(); + String key = "enum-snippet-" + snippet.value().replace('/', '-'); str.put("$ref", "#/definitions/" + key); if (!definitions.containsKey(key)) { JSONObject j = new JSONObject(); JSONArray snl = new JSONArray(); - data.getPossibleSnippets(snippet.value()).forEach(snl::put); + String folderReferencePrefix = "snippet/" + snippet.value() + "/"; + data.getPossibleSnippets(snippet.value()).forEach(reference -> { + snl.put(folderReferencePrefix + reference.substring("snippet/".length())); + snl.put(reference); + }); j.put("enum", snl); definitions.put(key, j); } @@ -724,7 +728,7 @@ public class SchemaBuilder { return "string"; } - if (c.equals(KList.class)) { + if (List.class.isAssignableFrom(c)) { return "array"; } diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectCatalog.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectCatalog.java index 1f3258d6b..4aa81cdec 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectCatalog.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectCatalog.java @@ -22,6 +22,7 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisBiome; import com.volmit.iris.engine.object.IrisRegion; +import com.volmit.iris.engine.object.annotations.Snippet; import com.volmit.iris.engine.object.procedural.coral.Coral; import com.volmit.iris.engine.object.procedural.coral.ResolvedCoral; import com.volmit.iris.engine.object.procedural.crystal.Crystal; @@ -55,15 +56,20 @@ import java.util.stream.Stream; */ public final class ProceduralObjectCatalog { private static final Gson DEFINITION_JSON = new Gson(); - /** The snippet folder every procedural setting shares, so all of them live under {@code snippet/procedural}. */ + /** + * The snippet folder all procedural settings live under. Each type keeps its own subfolder because the editor + * validates a snippet folder against a single schema. + */ public static final String SNIPPET_FOLDER = "procedural"; - private static final Map> DEFINITION_TYPE_BY_FOLDER = Map.of( - "trees", Tree.class, - "ruins", Ruin.class, - "formations", Formation.class, - "coral", Coral.class, - "fungi", Fungus.class, - "crystals", Crystal.class); + public static final String TREE_FOLDER = SNIPPET_FOLDER + "/trees"; + public static final String RUIN_FOLDER = SNIPPET_FOLDER + "/ruins"; + public static final String FORMATION_FOLDER = SNIPPET_FOLDER + "/formations"; + public static final String CORAL_FOLDER = SNIPPET_FOLDER + "/coral"; + public static final String FUNGUS_FOLDER = SNIPPET_FOLDER + "/fungi"; + public static final String CRYSTAL_FOLDER = SNIPPET_FOLDER + "/crystals"; + public static final String SET_FOLDER = SNIPPET_FOLDER + "/sets"; + public static final String PART_FOLDER = SNIPPET_FOLDER + "/parts"; + private static final List> DEFINITION_TYPES = List.of(Tree.class, Ruin.class, Formation.class, Coral.class, Fungus.class, Crystal.class); private final IrisData data; private final KMap byDefinition = new KMap<>(); @@ -106,7 +112,7 @@ public final class ProceduralObjectCatalog { public List definitions() { Map pathByPlacement = new LinkedHashMap<>(); Set takenPaths = new HashSet<>(); - DEFINITION_TYPE_BY_FOLDER.forEach((folder, type) -> snippetDefinitions(folder, type) + DEFINITION_TYPES.forEach(type -> snippetDefinitions(type) .forEach((path, definition) -> addDefinition(pathByPlacement, takenPaths, placementOf(definition), path))); Stream.concat(data.getRegionLoader().streamAll().map(IrisRegion::getProceduralObjects), data.getBiomeLoader().streamAll().map(IrisBiome::getProceduralObjects)) @@ -132,9 +138,10 @@ public final class ProceduralObjectCatalog { } /** The definitions in the snippet folder of one type, by their path under {@code snippet/procedural} without the extension. */ - private Map snippetDefinitions(String typeFolder, Class type) { - Path proceduralFolder = data.getDataFolder().toPath().resolve("snippet").resolve(SNIPPET_FOLDER); - Path folder = proceduralFolder.resolve(typeFolder); + private Map snippetDefinitions(Class type) { + Path snippetFolder = data.getDataFolder().toPath().resolve("snippet"); + Path proceduralFolder = snippetFolder.resolve(SNIPPET_FOLDER); + Path folder = snippetFolder.resolve(type.getAnnotation(Snippet.class).value()); if (!Files.isDirectory(folder)) { return Map.of(); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectSettings.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectSettings.java index 496759ba9..25a7dc0fb 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectSettings.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/ProceduralObjectSettings.java @@ -37,7 +37,7 @@ import java.util.List; * The procedural objects a region or biome declares, written as {@code "proceduralObjects": {"trees": [...], ...}}: things * Iris generates from scratch at world-gen time and places like object placements. */ -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.SET_FOLDER) @Desc("Procedurally generated objects placed in a biome or region. Unlike the objects block (which loads iob files), these are generated from scratch at world-gen time.") public final class ProceduralObjectSettings { private final transient AtomicCache> resolved = new AtomicCache<>(); diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/coral/Coral.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/coral/Coral.java index cd236b4fd..5797d1fc2 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/coral/Coral.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/coral/Coral.java @@ -35,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.CORAL_FOLDER) @Desc("A single procedurally generated coral reef structure. Iris bakes a pool of deterministic variants from these settings and scatters them across the seafloor at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file. Mirrors the procedural tree system but tuned for underwater reefs: structural blocks are waterlogged so the coral stays alive, and placement defaults to anchoring on the terrain beneath the water surface.") public final class Coral { @Desc("A human readable name used in logs and as the variant load key.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/crystal/Crystal.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/crystal/Crystal.java index 61b020409..873c2811f 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/crystal/Crystal.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/crystal/Crystal.java @@ -35,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.CRYSTAL_FOLDER) @Desc("A single procedurally generated crystal cluster: a budding base blob with faceted, tapered shards radiating from it (geode / amethyst-style). Iris bakes a pool of deterministic variants from these settings and scatters them at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file. Built for caves.") public final class Crystal { @Desc("A human readable name used in logs and as the variant load key.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/formation/Formation.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/formation/Formation.java index 5d8d8028c..1c21ad3a8 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/formation/Formation.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/formation/Formation.java @@ -35,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.FORMATION_FOLDER) @Desc("A single procedurally generated natural formation, including rock landmarks and magical glacial silhouettes. Iris bakes a pool of deterministic variants from these settings and scatters them at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file.") public final class Formation { @Desc("A human readable name used in logs and as the variant load key.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/fungi/Fungus.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/fungi/Fungus.java index ee182ded8..3cd7ccf32 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/fungi/Fungus.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/fungi/Fungus.java @@ -35,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.FUNGUS_FOLDER) @Desc("A single procedurally generated fungus (giant mushroom or shelf fungus). Iris bakes a pool of deterministic variants from these settings and scatters them at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file. The fungus is built as a stem column with a cap dome grown on top, mirroring the trunk-and-canopy model used by procedural trees.") public final class Fungus { @Desc("A human readable name used in logs and as the variant load key.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/Ruin.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/Ruin.java index 2622f82b7..cf6d49f68 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/Ruin.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/Ruin.java @@ -37,7 +37,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.RUIN_FOLDER) @Desc("A single procedurally generated ruin: a crumbling man-made structure (pillar, wall, arch, foundation slab, or rubble pile). Iris bakes a pool of deterministic variants from these settings and scatters them at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file. Weathering swaps blocks for mossy or cracked variants by noise, erosion deletes blocks so the shape reads as collapsed, and a buried fraction sinks the ruin into the terrain.") public final class Ruin { @Desc("A human readable name used in logs and as the variant load key (procedural/#).") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/RuinDecorator.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/RuinDecorator.java index 3147e0ed7..cf687876f 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/RuinDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/ruin/RuinDecorator.java @@ -29,7 +29,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/ruin-decorators") @Desc("An accent block applied to a generated ruin after it is built and eroded, such as moss carpets on broken tops, vines on the faces, or rubble scattered around the base.") public final class RuinDecorator { @Desc("Where on the ruin this accent is placed. TOP sits on the highest block of each column, SURFACE clings to air-facing vertical faces, BASE_SCATTER rings the ground footprint around the base.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/Tree.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/Tree.java index 845b74d39..285fc41f2 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/Tree.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/Tree.java @@ -37,7 +37,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.TREE_FOLDER) @Desc("A single procedurally generated tree. Iris bakes a pool of deterministic variants from these settings and scatters them at world-gen time, exactly like an object placement but generated from scratch instead of loaded from an iob file.") public final class Tree { @Desc("A human readable name used in logs and as the variant load key.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeBranches.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeBranches.java index 25205be18..3a37a6926 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeBranches.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeBranches.java @@ -26,7 +26,7 @@ import com.volmit.iris.engine.object.annotations.MinNumber; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-branches") @Desc("A branch system that throws limbs out of the trunk and places a leaf ball at each tip. When present, branches drive the canopy and only the topmost crown disc of the profile is still placed.") public final class TreeBranches { @Desc("How branch spawn chance varies over trunk height.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeCanopy.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeCanopy.java index 2aefdaa71..18557cb54 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeCanopy.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeCanopy.java @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-canopies") @Desc("The leaf crown of a procedural tree. Built from stacked dome-shaped discs (driven by the profile or by explicit layers) and optionally a branch system.") public final class TreeCanopy { @MinNumber(0) diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeDecorator.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeDecorator.java index 72c228550..1c2af21d7 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeDecorator.java @@ -29,7 +29,7 @@ import com.volmit.iris.engine.object.annotations.Required; import java.util.Optional; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-decorators") @Desc("An accent block applied to a generated tree such as fruit on branch tips, vines on the trunk, snow on the crown, or a glowing underside.") public final class TreeDecorator { @Desc("Where this decorator is placed on the tree.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeLayer.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeLayer.java index aa08b993e..9122a81b3 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeLayer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeLayer.java @@ -23,7 +23,7 @@ import com.volmit.iris.engine.object.annotations.Snippet; import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog; import com.volmit.iris.engine.object.annotations.MinNumber; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-layers") @Desc("An explicit canopy disc at a given vertical offset and radius. Define a list of these to bypass the profile and sculpt the crown by hand.") public final class TreeLayer { @Desc("The vertical offset of this disc, measured in blocks above the trunk base.") diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSecondaryLeaf.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSecondaryLeaf.java index cb0fb1b98..b9e518a65 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSecondaryLeaf.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSecondaryLeaf.java @@ -25,7 +25,7 @@ import com.volmit.iris.engine.object.annotations.MinNumber; import com.volmit.iris.engine.object.annotations.RegistryListBlockType; import com.volmit.iris.engine.object.annotations.Required; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-secondary-leaves") @Desc("A weighted accent leaf or foliage block scattered through the canopy (blossoms, shroomlight, berries).") public final class TreeSecondaryLeaf { @Required diff --git a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSubBranches.java b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSubBranches.java index 172a8f0c1..d1d290724 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSubBranches.java +++ b/core/src/main/java/com/volmit/iris/engine/object/procedural/tree/TreeSubBranches.java @@ -24,7 +24,7 @@ import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog; import com.volmit.iris.engine.object.annotations.MaxNumber; import com.volmit.iris.engine.object.annotations.MinNumber; -@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER) +@Snippet(ProceduralObjectCatalog.PART_FOLDER + "/tree-sub-branches") @Desc("One recursive level of sub-branches sprouting from each primary branch tip.") public final class TreeSubBranches { @MinNumber(1)