From d7496338296066196e8314aabf0a7ac32b75c4f9 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 14 Oct 2026 22:38:42 -0400 Subject: [PATCH] Routed cave biome ores - Ores declared only by adopted cave biomes are written to the deposits of the region each cave biome joined, scoped to those cave biomes, instead of to the cave biome itself where the deposit stage never reads them - Ores of adopted surface biomes still go to the biome --- .../iris/pack/ingest/ModBiomeAdopter.java | 4 +- .../iris/pack/ingest/ModFeatureIngester.java | 45 ++++++--- .../iris/pack/ingest/IngestCaveOreTest.java | 99 +++++++++++++++++++ 3 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 core/src/test/java/art/arcane/iris/pack/ingest/IngestCaveOreTest.java diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/ModBiomeAdopter.java b/core/src/main/java/art/arcane/iris/pack/ingest/ModBiomeAdopter.java index f0f4d91af..8edf1a4c2 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/ModBiomeAdopter.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/ModBiomeAdopter.java @@ -85,7 +85,7 @@ final class ModBiomeAdopter { transform(json, biome, ownsVegetation(biome, namespace)); model.pack().create(IngestPack.biomePath(irisKey), json); appendToPool(region.key(), role, irisKey); - adopted.put(biome.key(), new AdoptedBiome(biome.key(), irisKey, role)); + adopted.put(biome.key(), new AdoptedBiome(biome.key(), irisKey, role, region.key())); items.add(new ModIngestItem(namespace, biome.key(), "biome", ModIngestTier.ADOPTED, irisKey, role.label() + " biome in region " + region.key() + " from template " + template)); } @@ -196,7 +196,7 @@ final class ModBiomeAdopter { return name.isEmpty() ? key : name.toString(); } - record AdoptedBiome(String modKey, String irisKey, BiomeRole role) { + record AdoptedBiome(String modKey, String irisKey, BiomeRole role, String regionKey) { } private record Placement(IngestPackModel.RegionProfile region, String template) { diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/ModFeatureIngester.java b/core/src/main/java/art/arcane/iris/pack/ingest/ModFeatureIngester.java index 7628b1b7d..7ff8c5a9a 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/ModFeatureIngester.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/ModFeatureIngester.java @@ -221,36 +221,53 @@ final class ModFeatureIngester { } private String applyDeposits(FeaturePlan plan, Map adopted, Set universe, Set caveKeys) throws IOException { - List adoptedTargets = new ArrayList<>(); + List adoptedTargets = new ArrayList<>(); boolean vanillaDeclared = false; for (String biome : plan.declaring()) { vanillaDeclared |= WorldgenCatalogIndex.namespaceOf(biome).equals("minecraft"); ModBiomeAdopter.AdoptedBiome adoptedBiome = adopted.get(biome); if (adoptedBiome != null) { - adoptedTargets.add(adoptedBiome.irisKey()); + adoptedTargets.add(adoptedBiome); } } if (!vanillaDeclared && !adoptedTargets.isEmpty()) { - for (String irisKey : adoptedTargets) { - appendAll(model.pack().require(IngestPack.biomePath(irisKey)), "deposits", plan.deposits()); + List targets = new ArrayList<>(adoptedTargets.size()); + Map> caveBiomesByRegion = new TreeMap<>(); + for (ModBiomeAdopter.AdoptedBiome target : adoptedTargets) { + if (target.role() == BiomeRole.CAVE) { + caveBiomesByRegion.computeIfAbsent(target.regionKey(), ignored -> new ArrayList<>()).add(target.irisKey()); + continue; + } + appendAll(model.pack().require(IngestPack.biomePath(target.irisKey())), "deposits", plan.deposits()); + targets.add(target.irisKey()); } - return String.join(",", adoptedTargets); + for (Map.Entry> region : caveBiomesByRegion.entrySet()) { + appendAll(model.pack().edit(IngestPack.regionPath(region.getKey())), "deposits", + scoped(plan.deposits(), region.getValue(), "CAVE")); + targets.add("region " + region.getKey()); + } + return String.join(",", targets); } Set included = new TreeSet<>(plan.declaring()); included.retainAll(universe); - List deposits = new ArrayList<>(plan.deposits().size()); - for (JsonObject deposit : plan.deposits()) { - JsonObject copy = deposit.deepCopy(); - if (!included.containsAll(universe)) { - copy.add("includedBiomes", IngestJson.stringArray(included)); - copy.addProperty("biomeScope", caveKeys.containsAll(included) ? "CAVE" : "SURFACE"); - } - deposits.add(copy); - } + List deposits = included.containsAll(universe) + ? plan.deposits() + : scoped(plan.deposits(), List.copyOf(included), caveKeys.containsAll(included) ? "CAVE" : "SURFACE"); appendAll(model.pack().edit(model.dimensionPath()), "deposits", deposits); return "dimension"; } + private static List scoped(List deposits, List includedBiomes, String biomeScope) { + List scoped = new ArrayList<>(deposits.size()); + for (JsonObject deposit : deposits) { + JsonObject copy = deposit.deepCopy(); + copy.add("includedBiomes", IngestJson.stringArray(includedBiomes)); + copy.addProperty("biomeScope", biomeScope); + scoped.add(copy); + } + return scoped; + } + private String applyDecorator(FeaturePlan plan, Map adopted) throws IOException { List targets = new ArrayList<>(); for (String biome : plan.declaring()) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestCaveOreTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestCaveOreTest.java new file mode 100644 index 000000000..922e6f908 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestCaveOreTest.java @@ -0,0 +1,99 @@ +/* + * 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 . + */ + +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.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +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.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class IngestCaveOreTest { + private static final String MOD = ModIngestFixture.MOD; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void oresOfAdoptedCaveBiomesRunFromTheRegionScopedToThoseCaves() throws Exception { + Path root = folder.getRoot().toPath(); + Path source = ModIngestFixture.writeSourcePack(root); + Path target = root.resolve("packs").resolve("caves"); + + ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(MOD), Set.of(), false)); + + JsonArray regionDeposits = ModIngestFixture.readJson(target.resolve("regions/hot.json")).getAsJsonArray("deposits"); + assertNotNull(regionDeposits); + JsonObject geode = ModIngestFixture.findByPaletteBlock(regionDeposits, MOD + ":geode_ore"); + assertNotNull(geode); + assertEquals(List.of("ingest/" + MOD + "/crystal_caves"), ModIngestFixture.strings(geode.get("includedBiomes"))); + assertEquals("CAVE", geode.get("biomeScope").getAsString()); + JsonElement caveDeposits = ModIngestFixture.readJson(target.resolve("biomes/ingest/" + MOD + "/crystal_caves.json")).get("deposits"); + assertTrue(caveDeposits == null || ModIngestFixture.findByPaletteBlock(caveDeposits.getAsJsonArray(), MOD + ":geode_ore") == null); + assertNull(ModIngestFixture.findByPaletteBlock(ModIngestFixture.readJson(target.resolve("dimensions/caves.json")).getAsJsonArray("deposits"), MOD + ":geode_ore")); + ModIngestItem item = ModIngestFixture.item(report, MOD + ":geode_ore"); + assertEquals(ModIngestTier.NATIVE, item.tier()); + assertTrue(item.target(), item.target().contains("region hot")); + assertFalse(ModIngestFixture.readJson(target.resolve("regions/temperate.json")).has("deposits")); + } + + private static WorldgenCatalog catalog() { + WorldgenCatalog fixture = ModIngestFixture.catalog(); + List biomes = new ArrayList<>(); + for (CatalogBiome biome : fixture.biomes()) { + if (!biome.key().equals(MOD + ":crystal_caves")) { + biomes.add(biome); + continue; + } + List> steps = new ArrayList<>(biome.featureSteps()); + List ores = new ArrayList<>(steps.get(ModIngestFixture.ORE_STEP)); + ores.add(MOD + ":geode_ore"); + steps.set(ModIngestFixture.ORE_STEP, ores); + biomes.add(new CatalogBiome(biome.key(), biome.temperature(), biome.downfall(), true, biome.tags(), steps, biome.spawns(), null)); + } + List features = new ArrayList<>(fixture.placedFeatures()); + String key = MOD + ":geode_ore"; + features.add(new CatalogPlacedFeature(key, key, "minecraft:ore", JsonParser.parseString(""" + {"size": 6, "targets": [{"state": "examplemod:geode_ore", "target": {"predicate_type": "minecraft:block_match", "block": "minecraft:stone"}}]} + """), JsonParser.parseString(""" + [{"type": "minecraft:count", "count": 3}, {"type": "minecraft:in_square"}, + {"type": "minecraft:height_range", "height": {"type": "minecraft:uniform", "min_inclusive": {"absolute": -40}, "max_inclusive": {"absolute": 20}}}, + {"type": "minecraft:biome"}] + """))); + return new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(), fixture.capturedAtEpochMillis(), + fixture.contentFingerprint(), fixture.namespaces(), biomes, features, fixture.structures(), fixture.dimensionTypes(), + fixture.levelStems(), fixture.blockTags()); + } +}