Keep procedural snippets in one folder

snippet/procedural/<type>/, listed by path.
This commit is contained in:
piexel committed 2026-10-20 13:17:04 +02:00
1 parent dc7615219b
commit 9d7211378d
22 files changed
+100 -57

No files matched your search

@@ -19,6 +19,7 @@ package com.volmit.iris.core.gui;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.engine.object.IrisObject;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.procedural.ResolvedProceduralPlacement;
import java.util.LinkedHashMap;
@@ -26,16 +27,16 @@ import java.util.List;
import java.util.Map;
/**
* The objects the pack's procedural settings generate, one entry per definition in a folder per type.
* The objects the pack's procedural settings generate, one entry per definition, in the folders the pack keeps them in.
*/
final class ProceduralObjectSource implements ObjectSource {
private static final Map<String, String> FOLDER_BY_KIND = Map.of(
"tree", "Trees",
"formation", "Formations",
"crystal", "Crystals",
private static final Map<String, String> FOLDER_LABEL_BY_KIND = Map.of(
"trees", "Trees",
"formations", "Formations",
"crystals", "Crystals",
"coral", "Coral",
"fungus", "Fungi",
"ruin", "Ruins");
"fungi", "Fungi",
"ruins", "Ruins");
private Map<String, ResolvedProceduralPlacement> definitionsByKey = Map.of();
@@ -44,11 +45,14 @@ final class ProceduralObjectSource implements ObjectSource {
return "procedural object";
}
// The type folder gets its label; the rest of the path stays as the pack has it.
@Override
public List<String> keys(IrisData data) {
Map<String, ResolvedProceduralPlacement> byKey = new LinkedHashMap<>();
for (ResolvedProceduralPlacement definition : data.getProceduralObjects().definitions()) {
byKey.put(FOLDER_BY_KIND.getOrDefault(definition.kind(), definition.kind()) + "/" + definition.name(), definition);
for (ProceduralObjectCatalog.Definition definition : data.getProceduralObjects().definitions()) {
String kind = definition.placement().kind();
String key = FOLDER_LABEL_BY_KIND.getOrDefault(kind, kind) + definition.path().substring(kind.length());
byKey.put(key, definition.placement());
}
definitionsByKey = byKey;
return List.copyOf(byKey.keySet());
@@ -20,7 +20,8 @@ package com.volmit.iris.engine.object.procedural;
import com.google.gson.Gson;
import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.engine.object.IrisRegion;
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;
@@ -40,18 +41,29 @@ import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
/**
* The procedural objects of one pack: those its regions and biomes declare, and those saved on their own as snippets,
* such as {@code snippet/procedural-tree/oak.json}. A definition several places share is baked once.
* The procedural objects of one pack: those its regions and biomes declare, and those saved on their own as snippets
* in a folder per type under {@code snippet/procedural}, such as {@code snippet/procedural/trees/oak.json}. A definition several places share is baked once.
*/
public final class ProceduralObjectCatalog {
private static final Gson DEFINITION_JSON = new Gson();
private static final List<Class<?>> DEFINITION_TYPES = List.of(Tree.class, Ruin.class, Formation.class, Coral.class, Fungus.class, Crystal.class);
/** The snippet folder every procedural setting shares, so all of them live under {@code snippet/procedural}. */
public static final String SNIPPET_FOLDER = "procedural";
private static final Map<String, Class<?>> DEFINITION_TYPE_BY_FOLDER = Map.of(
"trees", Tree.class,
"ruins", Ruin.class,
"formations", Formation.class,
"coral", Coral.class,
"fungi", Fungus.class,
"crystals", Crystal.class);
private final IrisData data;
private final KMap<String, ResolvedProceduralPlacement> byDefinition = new KMap<>();
@@ -87,47 +99,58 @@ public final class ProceduralObjectCatalog {
}
/**
* Every procedural object the pack defines, in snippets or in regions and biomes, whether or not its dimensions
* reach them. Where two definitions share a type and name, the first one found stands for both.
* Every procedural object the pack defines, whether or not its dimensions reach them. A snippet is found at its file's
* path under {@code snippet/procedural}, like {@code trees/estranged/oak}; a definition only written in a region or
* biome at its type and name, like {@code trees/oak}.
*/
public List<ResolvedProceduralPlacement> definitions() {
Map<String, ResolvedProceduralPlacement> byTypeAndName = new LinkedHashMap<>();
for (Class<?> type : DEFINITION_TYPES) {
snippetDefinitions(type).forEach(definition -> addDefinition(byTypeAndName, placementOf(definition)));
}
data.getRegionLoader().streamAll().forEach(region -> addDefinitions(byTypeAndName, region.getProceduralObjects()));
data.getBiomeLoader().streamAll().forEach(biome -> addDefinitions(byTypeAndName, biome.getProceduralObjects()));
return List.copyOf(byTypeAndName.values());
public List<Definition> definitions() {
Map<ResolvedProceduralPlacement, String> pathByPlacement = new LinkedHashMap<>();
Set<String> takenPaths = new HashSet<>();
DEFINITION_TYPE_BY_FOLDER.forEach((folder, type) -> snippetDefinitions(folder, type)
.forEach((path, definition) -> addDefinition(pathByPlacement, takenPaths, placementOf(definition), path)));
Stream.concat(data.getRegionLoader().streamAll().map(IrisRegion::getProceduralObjects),
data.getBiomeLoader().streamAll().map(IrisBiome::getProceduralObjects))
.filter(Objects::nonNull)
.flatMap(settings -> settings.resolved(data).stream())
.forEach(placement -> addDefinition(pathByPlacement, takenPaths, placement, placement.kind() + "/" + placement.name()));
List<Definition> definitions = new ArrayList<>();
pathByPlacement.forEach((placement, path) -> definitions.add(new Definition(path, placement)));
return definitions;
}
private void addDefinitions(Map<String, ResolvedProceduralPlacement> byTypeAndName, ProceduralObjectSettings settings) {
if (settings == null) {
// The same definition found twice keeps its first path; two different ones never share a path.
private static void addDefinition(Map<ResolvedProceduralPlacement, String> pathByPlacement, Set<String> takenPaths,
ResolvedProceduralPlacement placement, String path) {
if (pathByPlacement.containsKey(placement)) {
return;
}
settings.resolved(data).forEach(placement -> addDefinition(byTypeAndName, placement));
String uniquePath = path;
for (int copy = 2; !takenPaths.add(uniquePath); copy++) {
uniquePath = path + " (" + copy + ")";
}
pathByPlacement.put(placement, uniquePath);
}
private static void addDefinition(Map<String, ResolvedProceduralPlacement> byTypeAndName, ResolvedProceduralPlacement placement) {
byTypeAndName.putIfAbsent(placement.kind() + "/" + placement.name(), placement);
}
private List<Object> snippetDefinitions(Class<?> type) {
Path folder = data.getDataFolder().toPath().resolve("snippet").resolve(type.getAnnotation(Snippet.class).value());
/** The definitions in the snippet folder of one type, by their path under {@code snippet/procedural} without the extension. */
private Map<String, Object> snippetDefinitions(String typeFolder, Class<?> type) {
Path proceduralFolder = data.getDataFolder().toPath().resolve("snippet").resolve(SNIPPET_FOLDER);
Path folder = proceduralFolder.resolve(typeFolder);
if (!Files.isDirectory(folder)) {
return List.of();
return Map.of();
}
try (Stream<Path> files = Files.walk(folder)) {
List<Object> definitions = new ArrayList<>();
Map<String, Object> definitions = new LinkedHashMap<>();
files.filter(file -> file.toString().endsWith(".json")).sorted().forEach(file -> {
Object definition = readDefinition(file, type);
if (definition != null) {
definitions.add(definition);
String path = proceduralFolder.relativize(file).toString().replace('\\', '/');
definitions.put(path.substring(0, path.length() - ".json".length()), definition);
}
});
return definitions;
} catch (IOException error) {
Iris.warn("Could not list the procedural objects in " + folder + ": " + error.getMessage());
return List.of();
return Map.of();
}
}
@@ -140,4 +163,7 @@ public final class ProceduralObjectCatalog {
}
}
/** A procedural object and where the pack keeps it, as a path under {@code snippet/procedural}. */
public record Definition(String path, ResolvedProceduralPlacement placement) {
}
}
@@ -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("procedural-objects")
@Snippet(ProceduralObjectCatalog.SNIPPET_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<List<ResolvedProceduralPlacement>> resolved = new AtomicCache<>();
@@ -68,7 +68,7 @@ public abstract class ResolvedProceduralPlacement {
return shape.name();
}
/** The type of object, as the variant keys name it: tree, ruin, formation, coral, fungus or crystal. */
/** The type of object, named like its snippet folder: trees, ruins, formations, coral, fungi or crystals. */
public String kind() {
return shape.kind();
}
@@ -116,7 +116,7 @@ public abstract class ResolvedProceduralPlacement {
return keys;
}
/** The key variant {@code index} is loaded under, like {@code procedural/tree/oak#0}. */
/** The key variant {@code index} is loaded under, like {@code procedural/trees/oak#0}. */
public String variantKey(int index) {
return "procedural/" + shape.kind() + "/" + shape.name() + "#" + index;
}
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.IrisObjectLimit;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -34,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("coral")
@Snippet(ProceduralObjectCatalog.SNIPPET_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.")
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.IrisObjectLimit;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -34,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("crystal")
@Snippet(ProceduralObjectCatalog.SNIPPET_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.")
@@ -29,7 +29,7 @@ public final class ResolvedCrystal extends ResolvedProceduralPlacement {
private final Crystal settings;
public ResolvedCrystal(Crystal settings, IrisData data) {
super(new Shape("crystal", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
super(new Shape("crystals", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
settings.rotation(), settings.clamp(), settings.carvingSupport(), settings.underwater(), settings.translate(),
settings.stiltSettings().orElse(null)), data);
this.settings = settings;
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.IrisObjectLimit;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -34,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("formation")
@Snippet(ProceduralObjectCatalog.SNIPPET_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.")
@@ -29,7 +29,7 @@ public final class ResolvedFormation extends ResolvedProceduralPlacement {
private final Formation settings;
public ResolvedFormation(Formation settings, IrisData data) {
super(new Shape("formation", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
super(new Shape("formations", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
settings.rotation(), settings.clamp(), settings.carvingSupport(), settings.underwater(), settings.translate(),
settings.stiltSettings().orElse(null)), data);
this.settings = settings;
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.IrisObjectLimit;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -34,7 +35,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("fungus")
@Snippet(ProceduralObjectCatalog.SNIPPET_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.")
@@ -33,7 +33,7 @@ public final class ResolvedFungus extends ResolvedProceduralPlacement {
private final Fungus settings;
public ResolvedFungus(Fungus settings, IrisData data) {
super(new Shape("fungus", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
super(new Shape("fungi", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
settings.rotation(), settings.clamp(), settings.carvingSupport(), settings.underwater(), settings.translate(),
settings.stiltSettings().orElse(null)), data);
this.settings = settings;
@@ -29,7 +29,7 @@ public final class ResolvedRuin extends ResolvedProceduralPlacement {
private final Ruin settings;
public ResolvedRuin(Ruin settings, IrisData data) {
super(new Shape("ruin", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
super(new Shape("ruins", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
settings.rotation(), settings.clamp(), settings.carvingSupport(), settings.underwater(), settings.translate(),
settings.stiltSettings().orElse(null)), data);
this.settings = settings;
@@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.IrisObjectLimit;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.ArrayType;
import com.volmit.iris.engine.object.annotations.MinNumber;
@@ -36,7 +37,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Snippet("ruin")
@Snippet(ProceduralObjectCatalog.SNIPPET_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/<name>#<index>).")
@@ -21,6 +21,7 @@ package com.volmit.iris.engine.object.procedural.ruin;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -28,7 +29,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("ruin-decorator")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@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.")
@@ -37,7 +37,7 @@ public final class ResolvedTree extends ResolvedProceduralPlacement {
private final Tree settings;
public ResolvedTree(Tree settings, IrisData data) {
super(new Shape("tree", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
super(new Shape("trees", settings.name(), settings.chance(), settings.density(), settings.seed(), settings.variants(), settings.mode(),
settings.rotation(), settings.clamp(), settings.carvingSupport(), settings.underwater(), settings.translate(),
settings.stiltSettings().orElse(null)), data);
this.settings = settings;
@@ -32,11 +32,12 @@ import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
import com.volmit.iris.engine.object.annotations.Required;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Snippet("procedural-tree")
@Snippet(ProceduralObjectCatalog.SNIPPET_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.")
@@ -20,12 +20,13 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import java.util.Optional;
@Snippet("tree-branches")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@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.")
@@ -20,6 +20,7 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.ArrayType;
import com.volmit.iris.engine.object.annotations.MinNumber;
@@ -27,7 +28,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Snippet("tree-canopy")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@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)
@@ -21,6 +21,7 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.IrisMaterialPalette;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
import com.volmit.iris.engine.object.annotations.MaxNumber;
import com.volmit.iris.engine.object.annotations.MinNumber;
import com.volmit.iris.engine.object.annotations.RegistryListBlockType;
@@ -28,7 +29,7 @@ import com.volmit.iris.engine.object.annotations.Required;
import java.util.Optional;
@Snippet("tree-decorator")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@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.")
@@ -20,9 +20,10 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.annotations.Desc;
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("tree-layer")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@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.")
@@ -20,11 +20,12 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.ProceduralObjectCatalog;
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("tree-secondary-leaf")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@Desc("A weighted accent leaf or foliage block scattered through the canopy (blossoms, shroomlight, berries).")
public final class TreeSecondaryLeaf {
@Required
@@ -20,10 +20,11 @@ package com.volmit.iris.engine.object.procedural.tree;
import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.engine.object.annotations.Snippet;
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("tree-sub-branches")
@Snippet(ProceduralObjectCatalog.SNIPPET_FOLDER)
@Desc("One recursive level of sub-branches sprouting from each primary branch tip.")
public final class TreeSubBranches {
@MinNumber(1)