mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +00:00
Scoped mod trees per biome
- importedFeatures gains biomeRules, which deny placed feature families or exact keys only in registry biomes matched by key, namespace, or tag; Iris custom biomes match through their vanilla derivative - Mod ingest classifies tree-like passthrough features from the catalog feature JSON, including trees nested in random selectors, fallen trees, and huge mushrooms - Tree-like mod features that a mod injects into vanilla biomes the pack already uses are denied in minecraft biomes, so they no longer double the pack's own trees while adopted mod biomes keep them - Studio schema digest updated for the new rule shape
This commit is contained in:
1 parent
0bc235afb6
commit
7767d6d30a
10 files changed
+454
-6
No files matched your search
@@ -19,9 +19,11 @@
|
||||
package art.arcane.iris.pack.ingest;
|
||||
|
||||
import art.arcane.iris.generation.decoration.IrisDecorationStep;
|
||||
import art.arcane.iris.structure.nativegen.IrisImportedFeatureBiomeRule;
|
||||
import art.arcane.iris.structure.nativegen.IrisImportedFeatureControl;
|
||||
import art.arcane.iris.structure.nativegen.NativeFeatureGenerationStatus;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@@ -37,7 +39,8 @@ final class ImportedFeatureWriter {
|
||||
private ImportedFeatureWriter() {
|
||||
}
|
||||
|
||||
static IrisImportedFeatureControl write(JsonObject dimension, Set<String> nativeKeys, Set<String> unselectedNamespaces) {
|
||||
static IrisImportedFeatureControl write(JsonObject dimension, Set<String> nativeKeys, Set<String> unselectedNamespaces,
|
||||
Set<String> vanillaScopedKeys) {
|
||||
JsonElement existing = dimension.get("importedFeatures");
|
||||
JsonObject imported = existing != null && existing.isJsonObject() ? existing.getAsJsonObject() : new JsonObject();
|
||||
Set<String> disabled = new LinkedHashSet<>(IngestJson.strings(imported.get("disabled")));
|
||||
@@ -52,11 +55,23 @@ final class ImportedFeatureWriter {
|
||||
imported.addProperty("enabled", true);
|
||||
imported.add("disabled", IngestJson.stringArray(disabled));
|
||||
imported.add("disabledExact", IngestJson.stringArray(disabledExact));
|
||||
JsonElement existingRules = imported.get("biomeRules");
|
||||
JsonArray rules = existingRules != null && existingRules.isJsonArray() ? existingRules.getAsJsonArray() : new JsonArray();
|
||||
if (!vanillaScopedKeys.isEmpty()) {
|
||||
JsonObject rule = new JsonObject();
|
||||
rule.add("biomes", IngestJson.stringArray(List.of(VANILLA_PREFIX)));
|
||||
rule.add("disabledExact", IngestJson.stringArray(vanillaScopedKeys));
|
||||
rules.add(rule);
|
||||
}
|
||||
if (!rules.isEmpty()) {
|
||||
imported.add("biomeRules", rules);
|
||||
}
|
||||
dimension.add("importedFeatures", imported);
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl();
|
||||
control.setEnabled(true);
|
||||
control.setDisabled(new KList<>(disabled));
|
||||
control.setDisabledExact(new KList<>(disabledExact));
|
||||
control.setBiomeRules(biomeRules(rules));
|
||||
control.setSteps(steps(imported.get("steps")));
|
||||
control.setDisabledSteps(steps(imported.get("disabledSteps")));
|
||||
return control;
|
||||
@@ -106,6 +121,21 @@ final class ImportedFeatureWriter {
|
||||
return "suppressed by importedFeatures";
|
||||
}
|
||||
|
||||
private static KList<IrisImportedFeatureBiomeRule> biomeRules(JsonArray rules) {
|
||||
KList<IrisImportedFeatureBiomeRule> parsed = new KList<>();
|
||||
for (JsonElement entry : rules) {
|
||||
if (!entry.isJsonObject()) {
|
||||
continue;
|
||||
}
|
||||
JsonObject rule = entry.getAsJsonObject();
|
||||
parsed.add(new IrisImportedFeatureBiomeRule()
|
||||
.setBiomes(new KList<>(IngestJson.strings(rule.get("biomes"))))
|
||||
.setDisabled(new KList<>(IngestJson.strings(rule.get("disabled"))))
|
||||
.setDisabledExact(new KList<>(IngestJson.strings(rule.get("disabledExact")))));
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private static boolean enabled(JsonObject imported) {
|
||||
JsonElement value = imported.get("enabled");
|
||||
return value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isBoolean() && value.getAsBoolean();
|
||||
|
||||
@@ -36,14 +36,19 @@ import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
final class ModFeatureIngester {
|
||||
private static final String VANILLA_NAMESPACE = "minecraft";
|
||||
|
||||
private final IngestPackModel model;
|
||||
private final WorldgenCatalogIndex index;
|
||||
private final TreeFeatures trees;
|
||||
private final Map<String, FeaturePlan> plans = new TreeMap<>();
|
||||
private final Set<String> nativeKeys = new TreeSet<>();
|
||||
private final Set<String> vanillaScopedKeys = new TreeSet<>();
|
||||
|
||||
ModFeatureIngester(IngestPackModel model, WorldgenCatalogIndex index, Set<String> namespaces) {
|
||||
this.model = model;
|
||||
this.index = index;
|
||||
this.trees = new TreeFeatures(index);
|
||||
Map<String, Set<String>> declaring = new TreeMap<>();
|
||||
Map<String, Set<Integer>> steps = new TreeMap<>();
|
||||
for (CatalogBiome biome : index.catalog().biomes()) {
|
||||
@@ -76,6 +81,10 @@ final class ModFeatureIngester {
|
||||
return nativeKeys;
|
||||
}
|
||||
|
||||
Set<String> vanillaScopedKeys() {
|
||||
return vanillaScopedKeys;
|
||||
}
|
||||
|
||||
Map<String, FeaturePlan> plans() {
|
||||
return plans;
|
||||
}
|
||||
@@ -106,11 +115,30 @@ final class ModFeatureIngester {
|
||||
nativeKeys.add(plan.key());
|
||||
continue;
|
||||
}
|
||||
if (treeInPackVanillaBiomes(plan)) {
|
||||
vanillaScopedKeys.add(plan.key());
|
||||
items.add(new ModIngestItem(namespace, plan.key(), "feature", ModIngestTier.PASSTHROUGH, "importedFeatures",
|
||||
plan.reason() + "; denied in minecraft biomes the pack already decorates, so its own trees are not doubled"));
|
||||
continue;
|
||||
}
|
||||
items.add(new ModIngestItem(namespace, plan.key(), "feature", ModIngestTier.PASSTHROUGH, "importedFeatures", plan.reason()));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private boolean treeInPackVanillaBiomes(FeaturePlan plan) {
|
||||
Optional<CatalogPlacedFeature> feature = index.placedFeature(plan.key());
|
||||
if (feature.isEmpty() || !trees.isTree(feature.get())) {
|
||||
return false;
|
||||
}
|
||||
for (String biome : plan.declaring()) {
|
||||
if (WorldgenCatalogIndex.namespaceOf(biome).equals(VANILLA_NAMESPACE) && model.reachableBiomes().containsValue(biome)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private FeaturePlan plan(String key, Set<String> declaring, Set<Integer> steps) {
|
||||
Optional<CatalogPlacedFeature> feature = index.placedFeature(key);
|
||||
if (feature.isEmpty()) {
|
||||
|
||||
@@ -89,7 +89,7 @@ public final class ModIngester {
|
||||
List<ModIngestItem> featureItems = features.apply(adopted);
|
||||
JsonObject dimension = pack.edit(model.dimensionPath());
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, features.nativeKeys(),
|
||||
unselectedFeatureNamespaces(request.catalog(), namespaces));
|
||||
unselectedFeatureNamespaces(request.catalog(), namespaces), features.vanillaScopedKeys());
|
||||
featureItems = ImportedFeatureWriter.applySuppression(featureItems, features.plans(), control);
|
||||
Set<String> hostBiomes = new TreeSet<>(adopted.keySet());
|
||||
hostBiomes.addAll(model.reachableBiomes().values());
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.pack.ingest;
|
||||
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
|
||||
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogIndex;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
final class TreeFeatures {
|
||||
private static final Set<String> TREE_TYPES = Set.of("minecraft:tree", "minecraft:fallen_tree", "minecraft:huge_brown_mushroom",
|
||||
"minecraft:huge_red_mushroom", "minecraft:huge_fungus");
|
||||
private static final List<String> NESTED_FIELDS = List.of("feature", "features", "default", "feature_true", "feature_false");
|
||||
private static final String TAG_PREFIX = "#";
|
||||
private static final int MAX_DEPTH = 8;
|
||||
|
||||
private final WorldgenCatalogIndex index;
|
||||
private final Map<String, CatalogPlacedFeature> byConfigured;
|
||||
|
||||
TreeFeatures(WorldgenCatalogIndex index) {
|
||||
this.index = index;
|
||||
this.byConfigured = new HashMap<>();
|
||||
for (CatalogPlacedFeature feature : index.catalog().placedFeatures()) {
|
||||
if (feature.configuredFeature() != null) {
|
||||
byConfigured.putIfAbsent(VanillaJson.namespaced(feature.configuredFeature()), feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isTree(CatalogPlacedFeature feature) {
|
||||
return configured(feature.featureType(), feature.config(), 0);
|
||||
}
|
||||
|
||||
static boolean treeType(String type) {
|
||||
String namespaced = VanillaJson.namespaced(type);
|
||||
String path = namespaced.substring(namespaced.indexOf(':') + 1);
|
||||
return TREE_TYPES.contains(namespaced) || path.endsWith("tree") || path.startsWith("huge_");
|
||||
}
|
||||
|
||||
private boolean configured(String type, JsonElement config, int depth) {
|
||||
if (type != null && treeType(type)) {
|
||||
return true;
|
||||
}
|
||||
if (depth >= MAX_DEPTH || config == null || !config.isJsonObject()) {
|
||||
return false;
|
||||
}
|
||||
JsonObject object = config.getAsJsonObject();
|
||||
for (String field : NESTED_FIELDS) {
|
||||
if (placed(object.get(field), depth + 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean placed(JsonElement holder, int depth) {
|
||||
if (holder == null || holder.isJsonNull() || depth > MAX_DEPTH) {
|
||||
return false;
|
||||
}
|
||||
if (holder.isJsonArray()) {
|
||||
for (JsonElement entry : holder.getAsJsonArray()) {
|
||||
if (placed(entry, depth)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (holder.isJsonPrimitive()) {
|
||||
String key = holder.getAsString();
|
||||
if (key.startsWith(TAG_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
Optional<CatalogPlacedFeature> feature = index.placedFeature(VanillaJson.namespaced(key));
|
||||
return feature.isPresent() && configured(feature.get().featureType(), feature.get().config(), depth);
|
||||
}
|
||||
JsonObject object = holder.getAsJsonObject();
|
||||
if (object.has("chance")) {
|
||||
return placed(object.get("feature"), depth);
|
||||
}
|
||||
return configuredHolder(object.get("feature"), depth);
|
||||
}
|
||||
|
||||
private boolean configuredHolder(JsonElement holder, int depth) {
|
||||
if (holder == null || holder.isJsonNull()) {
|
||||
return false;
|
||||
}
|
||||
if (holder.isJsonPrimitive()) {
|
||||
CatalogPlacedFeature feature = byConfigured.get(VanillaJson.namespaced(holder.getAsString()));
|
||||
return feature != null && configured(feature.featureType(), feature.config(), depth);
|
||||
}
|
||||
if (!holder.isJsonObject()) {
|
||||
return false;
|
||||
}
|
||||
JsonObject object = holder.getAsJsonObject();
|
||||
JsonElement type = object.get("type");
|
||||
return configured(type != null && type.isJsonPrimitive() ? type.getAsString() : null, object.get("config"), depth);
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.structure.nativegen;
|
||||
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Description("Denies native placed features in specific registry biomes only. Iris custom biomes match through the vanilla derivative that supplies their features, so a rule for 'minecraft:forest' also covers Iris biomes derived from it.")
|
||||
@Data
|
||||
public class IrisImportedFeatureBiomeRule {
|
||||
private static final String TAG_PREFIX = "#";
|
||||
|
||||
@ArrayType(type = String.class, min = 1)
|
||||
@Description("Registry biomes this rule applies to: an exact key such as 'minecraft:forest', a namespace such as 'minecraft:', or a biome tag such as '#minecraft:is_forest'.")
|
||||
private KList<String> biomes = new KList<>();
|
||||
|
||||
@ArrayType(type = String.class, min = 1)
|
||||
@Description("Placed feature families denied in the matched biomes, matched like importedFeatures.disabled on namespace, slash, or underscore boundaries.")
|
||||
private KList<String> disabled = new KList<>();
|
||||
|
||||
@ArrayType(type = String.class, min = 1)
|
||||
@Description("Exact placed feature keys denied in the matched biomes after trimming and case normalization.")
|
||||
private KList<String> disabledExact = new KList<>();
|
||||
|
||||
public boolean appliesTo(String biomeKey, Set<String> biomeTags) {
|
||||
String normalizedBiome = normalize(biomeKey);
|
||||
for (String entry : Objects.requireNonNull(biomes, "importedFeatures.biomeRules.biomes must not be null")) {
|
||||
String normalizedEntry = normalize(entry);
|
||||
if (normalizedEntry.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (normalizedEntry.startsWith(TAG_PREFIX)) {
|
||||
if (biomeTags.contains(normalizedEntry.substring(TAG_PREFIX.length()))) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (normalizedEntry.endsWith(":") ? normalizedBiome.startsWith(normalizedEntry) : normalizedBiome.equals(normalizedEntry)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean denies(String featureKey) {
|
||||
for (String entry : Objects.requireNonNull(disabled, "importedFeatures.biomeRules.disabled must not be null")) {
|
||||
if (IrisImportedStructureControl.matchesKey(entry, featureKey)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
String normalizedFeature = normalize(featureKey);
|
||||
for (String entry : Objects.requireNonNull(disabledExact, "importedFeatures.biomeRules.disabledExact must not be null")) {
|
||||
if (!normalize(entry).isEmpty() && normalize(entry).equals(normalizedFeature)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
return value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
+21
@@ -31,6 +31,7 @@ import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@@ -49,6 +50,10 @@ public class IrisImportedFeatureControl implements NativeImportedFeatureControl
|
||||
@Description("Exact placed feature keys to deny after trimming and case normalization. Unlike 'disabled', entries never match key families, so 'minecraft:ore_iron' does not disable 'minecraft:ore_iron_small'.")
|
||||
private KList<String> disabledExact = new KList<>();
|
||||
|
||||
@ArrayType(type = IrisImportedFeatureBiomeRule.class, min = 1)
|
||||
@Description("Per-biome denials applied on top of 'disabled' and 'disabledExact'. Each rule names registry biomes and the placed features it denies in those biomes only, so a mod tree injected into vanilla forests can be denied there while the mod's own biomes keep it.")
|
||||
private KList<IrisImportedFeatureBiomeRule> biomeRules = new KList<>();
|
||||
|
||||
@ArrayType(type = IrisDecorationStep.class, min = 1)
|
||||
@Description("Restrict feature generation to these decoration steps only. Empty (the default) means every step is eligible. Use this to import ores without importing vegetation: set it to UNDERGROUND_ORES.")
|
||||
private KList<IrisDecorationStep> steps = new KList<>();
|
||||
@@ -124,6 +129,22 @@ public class IrisImportedFeatureControl implements NativeImportedFeatureControl
|
||||
return shouldGenerateStep(IrisDecorationStep.byOrdinal(step));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasBiomeFeatureRules() {
|
||||
return biomeRules != null && !biomeRules.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldGenerateInBiome(String biomeKey, Set<String> biomeTags, String featureKey) {
|
||||
for (IrisImportedFeatureBiomeRule rule : Objects.requireNonNull(
|
||||
biomeRules, "importedFeatures.biomeRules must not be null")) {
|
||||
if (rule != null && rule.appliesTo(biomeKey, biomeTags) && rule.denies(featureKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFeatureFilter() {
|
||||
return (disabled != null && !disabled.isEmpty()) || (disabledExact != null && !disabledExact.isEmpty());
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ImportedFeatureWriterTest {
|
||||
public void sourceThatAlreadyRanImportedFeaturesKeepsVanillaAndOtherModsGenerating() {
|
||||
JsonObject dimension = dimension("{\"importedFeatures\": {\"enabled\": true, \"disabled\": [\"minecraft:ore_diamond\"]}}");
|
||||
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"));
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"), Set.of());
|
||||
|
||||
JsonObject imported = dimension.getAsJsonObject("importedFeatures");
|
||||
assertEquals(List.of("minecraft:ore_diamond"), ModIngestFixture.strings(imported.get("disabled")));
|
||||
@@ -50,7 +50,7 @@ public class ImportedFeatureWriterTest {
|
||||
public void sourceWithoutImportedFeaturesOnlyRunsTheSelectedModsPassthrough() {
|
||||
JsonObject dimension = dimension("{}");
|
||||
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"));
|
||||
IrisImportedFeatureControl control = ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of("othermod"), Set.of());
|
||||
|
||||
JsonObject imported = dimension.getAsJsonObject("importedFeatures");
|
||||
assertTrue(imported.get("enabled").getAsBoolean());
|
||||
@@ -66,7 +66,7 @@ public class ImportedFeatureWriterTest {
|
||||
public void existingExactEntriesAreKept() {
|
||||
JsonObject dimension = dimension("{\"importedFeatures\": {\"enabled\": false, \"disabledExact\": [\"examplemod:old\"]}}");
|
||||
|
||||
ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of());
|
||||
ImportedFeatureWriter.write(dimension, Set.of("examplemod:ore_tin"), Set.of(), Set.of());
|
||||
|
||||
assertEquals(List.of("examplemod:old", "examplemod:ore_tin"),
|
||||
ModIngestFixture.strings(dimension.getAsJsonObject("importedFeatures").get("disabledExact")));
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Bukkit Servers
|
||||
* Copyright (c) 2022 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.pack.ingest;
|
||||
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogBiome;
|
||||
import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature;
|
||||
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IngestTreeScopeTest {
|
||||
private static final int VEGETAL_STEP = ModIngestFixture.VEGETAL_STEP;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
private ModIngestReport report;
|
||||
private JsonObject imported;
|
||||
|
||||
@Before
|
||||
public void ingest() throws Exception {
|
||||
Path root = folder.getRoot().toPath();
|
||||
Path source = ModIngestFixture.writeSourcePack(root);
|
||||
Path target = root.resolve("packs").resolve("trees");
|
||||
report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(ModIngestFixture.MOD), Set.of(), false));
|
||||
imported = ModIngestFixture.readJson(target.resolve("dimensions/trees.json")).getAsJsonObject("importedFeatures");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modTreesInjectedIntoThePacksVanillaBiomesAreDeniedThereOnly() {
|
||||
JsonArray rules = imported.getAsJsonArray("biomeRules");
|
||||
|
||||
assertEquals(1, rules.size());
|
||||
JsonObject rule = rules.get(0).getAsJsonObject();
|
||||
assertEquals(List.of("minecraft:"), ModIngestFixture.strings(rule.get("biomes")));
|
||||
assertEquals(List.of("examplemod:inline_trees", "examplemod:oak_bushes", "examplemod:simple_trees"),
|
||||
ModIngestFixture.strings(rule.get("disabledExact")));
|
||||
ModIngestItem bushes = ModIngestFixture.item(report, "examplemod:oak_bushes");
|
||||
assertEquals(ModIngestTier.PASSTHROUGH, bushes.tier());
|
||||
assertTrue(bushes.reason(), bushes.reason().contains("minecraft biomes"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonTreeDecorationAndModOnlyTreesKeepRunningEverywhere() {
|
||||
List<String> denied = ModIngestFixture.strings(imported.getAsJsonArray("biomeRules").get(0).getAsJsonObject().get("disabledExact"));
|
||||
|
||||
assertEquals(ModIngestTier.PASSTHROUGH, ModIngestFixture.item(report, "examplemod:mossy_rocks").tier());
|
||||
assertTrue(denied.stream().noneMatch(key -> key.equals("examplemod:mossy_rocks")));
|
||||
assertTrue(denied.stream().noneMatch(key -> key.equals("examplemod:maple_trees")));
|
||||
}
|
||||
|
||||
private static WorldgenCatalog catalog() {
|
||||
WorldgenCatalog fixture = ModIngestFixture.catalog();
|
||||
List<String> injected = List.of(ModIngestFixture.MOD + ":oak_bushes", ModIngestFixture.MOD + ":simple_trees",
|
||||
ModIngestFixture.MOD + ":inline_trees", ModIngestFixture.MOD + ":mossy_rocks");
|
||||
List<CatalogBiome> biomes = new ArrayList<>();
|
||||
for (CatalogBiome biome : fixture.biomes()) {
|
||||
if (!biome.key().equals("minecraft:forest") && !biome.key().equals(ModIngestFixture.MOD + ":maple_woods")) {
|
||||
biomes.add(biome);
|
||||
continue;
|
||||
}
|
||||
List<List<String>> steps = new ArrayList<>(biome.featureSteps());
|
||||
List<String> vegetal = new ArrayList<>(steps.get(VEGETAL_STEP));
|
||||
vegetal.addAll(injected);
|
||||
steps.set(VEGETAL_STEP, vegetal);
|
||||
biomes.add(new CatalogBiome(biome.key(), biome.temperature(), biome.downfall(), true, biome.tags(), steps, biome.spawns(), null));
|
||||
}
|
||||
List<CatalogPlacedFeature> features = new ArrayList<>(fixture.placedFeatures());
|
||||
features.add(feature("oak_bushes", "minecraft:random_selector", """
|
||||
{"features": [{"chance": 0.3, "feature": "examplemod:maple_trees"}], "default": "examplemod:mossy_rocks"}
|
||||
"""));
|
||||
features.add(feature("simple_trees", "minecraft:simple_random_selector", """
|
||||
{"features": ["examplemod:mossy_rocks", "examplemod:maple_trees"]}
|
||||
"""));
|
||||
features.add(feature("inline_trees", "minecraft:random_boolean_selector", """
|
||||
{"feature_true": {"feature": {"type": "minecraft:tree", "config": {}}, "placement": []}, "feature_false": "examplemod:mossy_rocks"}
|
||||
"""));
|
||||
features.add(feature("mossy_rocks", "minecraft:forest_rock", "{\"state\": \"minecraft:mossy_cobblestone\"}"));
|
||||
return new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(), fixture.capturedAtEpochMillis(),
|
||||
fixture.contentFingerprint(), fixture.namespaces(), biomes, features, fixture.structures(),
|
||||
fixture.dimensionTypes(), fixture.levelStems());
|
||||
}
|
||||
|
||||
private static CatalogPlacedFeature feature(String path, String type, String config) {
|
||||
String key = ModIngestFixture.MOD + ":" + path;
|
||||
return new CatalogPlacedFeature(key, key, type, JsonParser.parseString(config), JsonParser.parseString("[]"));
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ public class SchemaBuilderIdentityTest {
|
||||
@ClassRule
|
||||
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
|
||||
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "5841ff4a1ad2813e4ad45f941fd40e5083a6b1b29a61c522df9efa12eb04f8c0";
|
||||
private static final String EXPECTED_SCHEMA_DIGEST = "c99b0db94bfd6f316738beb87401edde5b52eed11bfa419148bb2e8c08b0d7a9";
|
||||
|
||||
private static final List<Class<?>> SCHEMA_ROOTS = List.of(
|
||||
IrisDimension.class,
|
||||
|
||||
+41
@@ -23,6 +23,8 @@ import art.arcane.iris.generation.decoration.IrisDecorationStep;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
@@ -111,6 +113,45 @@ public class IrisImportedFeatureControlTest {
|
||||
assertThrows(NullPointerException.class, () -> control.shouldGenerate("examplemod:ore_tin"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biomeRulesDenyFeaturesOnlyInMatchingBiomes() {
|
||||
IrisImportedFeatureBiomeRule rule = new IrisImportedFeatureBiomeRule()
|
||||
.setBiomes(keys("minecraft:"))
|
||||
.setDisabledExact(keys("terralith:trees/oak"))
|
||||
.setDisabled(keys("terralith:bush"));
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
.setEnabled(true)
|
||||
.setBiomeRules(new KList<>(rule));
|
||||
|
||||
assertTrue(control.hasBiomeFeatureRules());
|
||||
assertFalse(control.shouldGenerateInBiome("minecraft:forest", Set.of(), "terralith:trees/oak"));
|
||||
assertFalse(control.shouldGenerateInBiome("minecraft:forest", Set.of(), "terralith:bush/small"));
|
||||
assertTrue(control.shouldGenerateInBiome("minecraft:forest", Set.of(), "terralith:trees/oak_large"));
|
||||
assertTrue(control.shouldGenerateInBiome("minecraft:forest", Set.of(), "terralith:flowers/lavender"));
|
||||
assertTrue(control.shouldGenerateInBiome("terralith:lavender_forest", Set.of(), "terralith:trees/oak"));
|
||||
assertTrue(control.shouldGenerate("terralith:trees/oak"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biomeRulesMatchExactKeysAndTags() {
|
||||
IrisImportedFeatureBiomeRule rule = new IrisImportedFeatureBiomeRule()
|
||||
.setBiomes(keys("minecraft:forest", "#minecraft:is_taiga"))
|
||||
.setDisabledExact(keys("terralith:trees/oak"));
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
.setEnabled(true)
|
||||
.setBiomeRules(new KList<>(rule));
|
||||
|
||||
assertFalse(control.shouldGenerateInBiome("minecraft:forest", Set.of(), "terralith:trees/oak"));
|
||||
assertTrue(control.shouldGenerateInBiome("minecraft:forest_hills", Set.of(), "terralith:trees/oak"));
|
||||
assertFalse(control.shouldGenerateInBiome("minecraft:taiga", Set.of("minecraft:is_taiga"), "terralith:trees/oak"));
|
||||
assertTrue(control.shouldGenerateInBiome("minecraft:plains", Set.of("minecraft:is_overworld"), "terralith:trees/oak"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controlWithoutBiomeRulesReportsNone() {
|
||||
assertFalse(new IrisImportedFeatureControl().setEnabled(true).hasBiomeFeatureRules());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stepAllowListNarrowsGenerationToListedStepsOnly() {
|
||||
IrisImportedFeatureControl control = new IrisImportedFeatureControl()
|
||||
|
||||
Reference in new issue
Block a user