Ordered converted ore hosts

- The worldgen catalog (format 2) records the members of every block tag referenced by a captured placed feature, so ore targets using mod block tags convert instead of passing through
- Ore conversion supports noise_based_count and noise_threshold_count placement counts
- Converted ores that produce another converted ore's host block are written ahead of it, so an ore whose host comes from a sibling ore finds that host during the deposit stage
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-14 22:37:28 -04:00
1 parent d5796b275e
commit 965e19801d
20 files changed
+445 -45

No files matched your search

@@ -28,6 +28,7 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
@@ -53,7 +54,7 @@ public class CanopyEstimateTest {
JsonParser.parseString("[{\"type\": \"minecraft:count\", \"count\": 10}, {\"type\": \"minecraft:in_square\"},"
+ "{\"type\": \"minecraft:surface_water_depth_filter\", \"max_water_depth\": 0}, {\"type\": \"minecraft:biome\"}]")));
return new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1L, "canopy", List.of(new CatalogNamespace("mod", "Mod", "1")),
biomes, features, List.of(), List.of(), List.of());
biomes, features, List.of(), List.of(), List.of(), Map.of());
}
private static CatalogBiome biome(String key, List<String> vegetal) {
@@ -26,6 +26,7 @@ import com.google.gson.JsonParser;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
@@ -65,7 +66,7 @@ public class FeatureConverterFormatTest {
{"type": "minecraft:biome"}]
""");
List<JsonObject> deposits = OreDepositConverter.convert(iron, OVERWORLD, -64);
List<JsonObject> deposits = OreDepositConverter.convert(iron, OVERWORLD, -64, Map.of());
assertEquals(2, deposits.size());
assertEquals("minecraft:iron_ore", deposits.get(0).getAsJsonArray("palette").get(0).getAsJsonObject().get("block").getAsString());
@@ -88,7 +89,7 @@ public class FeatureConverterFormatTest {
{"type": "minecraft:biome"}]
"""));
List<JsonObject> deposits = OreDepositConverter.convert(clay, OVERWORLD, -64);
List<JsonObject> deposits = OreDepositConverter.convert(clay, OVERWORLD, -64, Map.of());
assertEquals(1, deposits.size());
assertEquals("FULL_HEIGHT", deposits.getFirst().get("placementScope").getAsString());
@@ -101,7 +102,7 @@ public class FeatureConverterFormatTest {
"{\"size\": 4, \"targets\": [{\"state\": \"minecraft:stone\", \"target\": {\"predicate_type\": \"minecraft:always_true\"}}]}",
"[{\"type\": \"minecraft:environment_scan\", \"direction_of_search\": \"down\", \"max_steps\": 16, \"target_condition\": {\"type\": \"minecraft:solid\"}}]");
IngestConversionException failure = assertThrows(IngestConversionException.class, () -> OreDepositConverter.convert(buried, OVERWORLD, -64));
IngestConversionException failure = assertThrows(IngestConversionException.class, () -> OreDepositConverter.convert(buried, OVERWORLD, -64, Map.of()));
assertTrue(failure.getMessage(), failure.getMessage().contains("minecraft:environment_scan"));
}
@@ -178,6 +179,6 @@ public class FeatureConverterFormatTest {
private static WorldgenCatalogIndex index() {
return WorldgenCatalogIndex.of(new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 0L, "",
List.of(), List.of(), List.of(), List.of(), List.of(), List.of()));
List.of(), List.of(), List.of(), List.of(), List.of(), List.of(), Map.of()));
}
}
@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -129,7 +130,7 @@ public class IngestBiomePlacementTest {
}
return new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1000L, "fingerprint-placement",
List.of(new CatalogNamespace(MOD, "Mod Pack", "1.0.0")), biomes, List.of(), List.of(),
List.of(new CatalogDimensionType("minecraft:overworld", -64, 384, 384, null)), List.of());
List.of(new CatalogDimensionType("minecraft:overworld", -64, 384, 384, null)), List.of(), Map.of());
}
private static CatalogBiome biome(String key, float temperature, float downfall, String categoryTag) {
@@ -52,7 +52,7 @@ public class IngestHeightBoundsTest {
types.add(new CatalogDimensionType("examplemod:skyrealm", 0, 256, 256, null));
WorldgenCatalog catalog = new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(),
fixture.capturedAtEpochMillis(), fixture.contentFingerprint(), fixture.namespaces(), fixture.biomes(),
fixture.placedFeatures(), fixture.structures(), types, fixture.levelStems());
fixture.placedFeatures(), fixture.structures(), types, fixture.levelStems(), fixture.blockTags());
JsonObject stone = tinDeposit("\"dimensionHeight\": {\"min\": 0, \"max\": 192}, \"borrowedDimensionType\": \"examplemod:skyrealm\"",
catalog);
@@ -0,0 +1,250 @@
/*
* 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.generation.biome.IrisBiome;
import art.arcane.iris.generation.block.B;
import art.arcane.iris.generation.block.IrisBlockData;
import art.arcane.iris.generation.concurrent.MultiBurst;
import art.arcane.iris.generation.context.ChunkContext;
import art.arcane.iris.generation.context.ChunkedDataCache;
import art.arcane.iris.generation.decoration.IrisDepositGenerator;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.stage.IrisDepositModifier;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisRegion;
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 art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.testsupport.KeyedBlockState;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.hunk.Hunk;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.stream.ProceduralStream;
import art.arcane.volmlib.util.stream.interpolation.Interpolated;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.MockedStatic;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
public class IngestOreHostOrderTest {
@ClassRule
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
private static final String MOD = ModIngestFixture.MOD;
private static final int PACK_MIN_Y = -128;
private static final int HEIGHT = 448;
private static final int CHUNKS = 2;
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private final Map<String, NativeBlockState> states = new HashMap<>();
@Test
public void hostProducingOreIsConvertedAndRunsBeforeTheOreThatConsumesItsHost() throws Exception {
JsonArray deposits = ingestedDeposits();
assertEquals(2, deposits.size());
assertEquals("minecraft:sand", paletteBlock(deposits.get(0)));
assertEquals("minecraft:clay", paletteBlock(deposits.get(1)));
assertTrue(placed(List.of(deposits.get(0), deposits.get(1)), "minecraft:clay") > 0);
}
@Test
public void consumerRunningBeforeItsHostProducerFindsNoHost() throws Exception {
JsonArray deposits = ingestedDeposits();
assertEquals(0, placed(List.of(deposits.get(1), deposits.get(0)), "minecraft:clay"));
}
private JsonArray ingestedDeposits() throws Exception {
Path root = folder.newFolder().toPath();
Path source = ModIngestFixture.writeSourcePack(root);
Path target = root.resolve("packs").resolve("isles");
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog(), Set.of(MOD), Set.of(), false));
assertEquals(ModIngestTier.NATIVE, ModIngestFixture.item(report, MOD + ":isles/sand_beaches").tier());
assertEquals(ModIngestTier.NATIVE, ModIngestFixture.item(report, MOD + ":isles/clay_patch").tier());
return ModIngestFixture.readJson(target.resolve("biomes/ingest/" + MOD + "/sand_isles.json")).getAsJsonArray("deposits");
}
private static String paletteBlock(JsonElement deposit) {
return deposit.getAsJsonObject().getAsJsonArray("palette").get(0).getAsJsonObject().get("block").getAsString();
}
@SuppressWarnings("unchecked")
private int placed(List<JsonElement> depositJson, String block) {
Gson gson = new Gson();
IrisBiome surface = new IrisBiome();
for (JsonElement json : depositJson) {
ResolvedDeposit deposit = gson.fromJson(json, ResolvedDeposit.class);
deposit.bind(states);
surface.getDeposits().add(deposit);
}
Engine engine = mock(Engine.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
ChunkContext context = mock(ChunkContext.class, withSettings().stubOnly().defaultAnswer(RETURNS_DEEP_STUBS));
IrisRegion region = new IrisRegion();
when(engine.getHeight()).thenReturn(HEIGHT);
when(engine.getMinHeight()).thenReturn(PACK_MIN_Y);
when(engine.getSeedManager().getDeposit()).thenReturn(11L);
when(engine.getDimension()).thenReturn(new IrisDimension());
when(context.getRoundedHeight(anyInt(), anyInt())).thenReturn(61 - PACK_MIN_Y);
when(context.getBiome()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> surface, Interpolated.of(value -> 0D, value -> null)), 0, 0));
when(context.getRegion()).thenReturn(new ChunkedDataCache<>(
ProceduralStream.of((x, z) -> region, Interpolated.of(value -> 0D, value -> null)), 0, 0));
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
Mantle<Matter> mantle = engine.getMantle().getMantle();
doReturn(chunk).when(mantle).useChunk(anyInt(), anyInt());
when(chunk.get(anyInt(), anyInt(), anyInt(), any())).thenReturn(null);
MultiBurst burst = new MultiBurst("Ingested deposit order", () -> 2);
when(engine.burst()).thenReturn(burst);
int count = 0;
try (MockedStatic<B> blocks = mockStatic(B.class, withSettings().stubOnly().defaultAnswer(CALLS_REAL_METHODS))) {
blocks.when(() -> B.getStateOrNull(anyString(), anyBoolean())).thenAnswer(invocation -> state(invocation.getArgument(0)));
blocks.when(() -> B.toDeepSlateOre(any(), any())).thenAnswer(invocation -> invocation.getArgument(1));
IrisDepositModifier modifier = new IrisDepositModifier(engine);
for (int chunkX = 0; chunkX < CHUNKS; chunkX++) {
Hunk<NativeBlockState> terrain = islandChunk();
modifier.generateDeposits(terrain, chunkX, 0, false, context);
count += count(terrain, block);
}
} finally {
burst.close();
}
return count;
}
private Hunk<NativeBlockState> islandChunk() {
Hunk<NativeBlockState> terrain = Hunk.newArrayHunk(16, HEIGHT, 16);
for (int y = 0; y < HEIGHT; y++) {
int worldY = y + PACK_MIN_Y;
String key = worldY < 55 ? "minecraft:stone" : worldY < 61 ? "minecraft:dirt" : worldY == 61 ? "minecraft:grass_block" : "minecraft:air";
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
terrain.set(x, y, z, state(key));
}
}
}
return terrain;
}
private static int count(Hunk<NativeBlockState> terrain, String block) {
int count = 0;
for (int y = 0; y < HEIGHT; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
if (terrain.get(x, y, z).key().equals(block)) {
count++;
}
}
}
}
return count;
}
private NativeBlockState state(String block) {
String key = block.contains(":") ? block : "minecraft:" + block;
return states.computeIfAbsent(key, KeyedBlockState::new);
}
private static WorldgenCatalog catalog() {
WorldgenCatalog fixture = ModIngestFixture.catalog();
List<CatalogBiome> biomes = new ArrayList<>(fixture.biomes());
List<List<String>> steps = new ArrayList<>(11);
for (int index = 0; index < 11; index++) {
steps.add(index == 5 ? List.of(MOD + ":isles/clay_patch") : index == 7 ? List.of(MOD + ":isles/sand_beaches") : List.of());
}
biomes.add(new CatalogBiome(MOD + ":sand_isles", 0.7F, 0.8F, true, List.of("minecraft:is_overworld"), steps, List.of(), null));
List<CatalogPlacedFeature> features = new ArrayList<>(fixture.placedFeatures());
features.add(feature("isles/clay_patch", """
{"targets": [{"target": {"block": "minecraft:sand", "predicate_type": "minecraft:block_match"}, "state": "minecraft:clay"}],
"size": 50, "discard_chance_on_air_exposure": 0.0}
""", """
[{"count": 2, "type": "minecraft:count"}, {"type": "minecraft:in_square"},
{"height": {"min_inclusive": {"absolute": 57}, "max_inclusive": {"absolute": 62}, "type": "minecraft:uniform"}, "type": "minecraft:height_range"},
{"type": "minecraft:biome"}]
"""));
features.add(feature("isles/sand_beaches", """
{"targets": [{"target": {"tag": "examplemod:soil_and_gravel", "predicate_type": "minecraft:tag_match"}, "state": "minecraft:sand"}],
"size": 50, "discard_chance_on_air_exposure": 0.0}
""", """
[{"count": 6, "type": "minecraft:count"}, {"count": 5, "type": "minecraft:count"}, {"type": "minecraft:in_square"},
{"noise_to_count_ratio": 1, "noise_factor": 275.0, "noise_offset": 0.2, "type": "minecraft:noise_based_count"},
{"height": {"min_inclusive": {"absolute": 50}, "max_inclusive": {"absolute": 61}, "type": "minecraft:uniform"}, "type": "minecraft:height_range"},
{"type": "minecraft:biome"}]
"""));
Map<String, List<String>> tags = Map.of(MOD + ":soil_and_gravel", List.of("minecraft:coarse_dirt", "minecraft:dirt", "minecraft:grass_block", "minecraft:gravel"));
return new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(), fixture.capturedAtEpochMillis(),
fixture.contentFingerprint(), fixture.namespaces(), biomes, features, fixture.structures(), fixture.dimensionTypes(),
fixture.levelStems(), tags);
}
private static CatalogPlacedFeature feature(String path, String config, String placement) {
String key = MOD + ":" + path;
return new CatalogPlacedFeature(key, key, "minecraft:ore", JsonParser.parseString(config), JsonParser.parseString(placement));
}
private static final class ResolvedDeposit extends IrisDepositGenerator {
private transient KList<NativeBlockState> resolved;
private void bind(Map<String, NativeBlockState> states) {
resolved = new KList<>();
for (IrisBlockData block : getPalette()) {
resolved.add(states.computeIfAbsent(block.getBlock(), KeyedBlockState::new));
}
}
@Override
public KList<NativeBlockState> getBlockData(IrisData data) {
return resolved;
}
}
}
@@ -75,7 +75,7 @@ public class IngestReservedNamespaceTest {
biomes.add(biome("underworld:ash_fields"));
return new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(),
fixture.capturedAtEpochMillis(), fixture.contentFingerprint(), fixture.namespaces(), biomes,
fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems());
fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems(), fixture.blockTags());
}
private static CatalogBiome biome(String key) {
@@ -107,7 +107,7 @@ public class IngestTreeScopeTest {
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());
fixture.dimensionTypes(), fixture.levelStems(), fixture.blockTags());
}
private static CatalogPlacedFeature feature(String path, String type, String config) {
@@ -35,6 +35,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class ModIngestFixture {
@@ -316,7 +317,7 @@ final class ModIngestFixture {
List.of(new CatalogNamespace(MOD, "Example Mod", "1.0.0"), new CatalogNamespace(OTHER_MOD, "Other Mod", "2.0.0")),
biomes, features, structures,
List.of(new CatalogDimensionType("minecraft:overworld", -64, 384, 384, null)),
List.of());
List.of(), Map.of());
}
static ModIngestRequest request(Path source, Path target, Set<String> namespaces, boolean replace) {
@@ -216,7 +216,7 @@ public class ModIngesterPackTest {
namespaces.add(new CatalogNamespace("quietmod", "Quiet Mod", "1.0.0"));
WorldgenCatalog catalog = new WorldgenCatalog(fixture.format(), fixture.minecraftVersion(), fixture.platform(),
fixture.capturedAtEpochMillis(), fixture.contentFingerprint(), namespaces, fixture.biomes(),
fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems());
fixture.placedFeatures(), fixture.structures(), fixture.dimensionTypes(), fixture.levelStems(), fixture.blockTags());
ModIngestReport report = ModIngester.ingest(new ModIngestRequest(source, target, "overworld", catalog, Set.of(), Set.of(), false));
@@ -24,6 +24,7 @@ import com.google.gson.JsonParser;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.assertEquals;
@@ -93,20 +94,23 @@ public class VanillaFeatureParsersTest {
{"predicate_type": "minecraft:all_of", "rules": [
{"predicate_type": "minecraft:not", "rule": {"predicate_type": "minecraft:tag_match", "tag": "minecraft:height_specific_ore_replaceables"}},
{"predicate_type": "minecraft:tag_match", "tag": "minecraft:stone_ore_replaceables"}]}]}
"""));
"""), Map.of());
assertFalse(stone.any());
assertEquals(Set.of("minecraft:tuff", "minecraft:stone", "minecraft:granite", "minecraft:diorite", "minecraft:andesite"), stone.blocks());
assertEquals(Set.of("minecraft:netherrack"),
VanillaRuleTests.hosts(json("{\"predicate_type\": \"minecraft:block_match\", \"block\": \"minecraft:netherrack\"}")).blocks());
VanillaRuleTests.hosts(json("{\"predicate_type\": \"minecraft:block_match\", \"block\": \"minecraft:netherrack\"}"), Map.of()).blocks());
assertEquals(Set.of("minecraft:end_stone"), VanillaRuleTests.hosts(json(
"{\"predicate_type\": \"minecraft:blockstate_match\", \"block_state\": {\"Name\": \"minecraft:end_stone\"}}")).blocks());
assertTrue(VanillaRuleTests.hosts(json("{\"predicate_type\": \"minecraft:always_true\"}")).any());
"{\"predicate_type\": \"minecraft:blockstate_match\", \"block_state\": {\"Name\": \"minecraft:end_stone\"}}"), Map.of()).blocks());
assertTrue(VanillaRuleTests.hosts(json("{\"predicate_type\": \"minecraft:always_true\"}"), Map.of()).any());
assertThrows(IngestConversionException.class, () -> VanillaRuleTests.hosts(json(
"{\"predicate_type\": \"minecraft:random_block_match\", \"block\": \"minecraft:stone\", \"probability\": 0.5}")));
"{\"predicate_type\": \"minecraft:random_block_match\", \"block\": \"minecraft:stone\", \"probability\": 0.5}"), Map.of()));
IngestConversionException unknownTag = assertThrows(IngestConversionException.class, () -> VanillaRuleTests.hosts(json(
"{\"predicate_type\": \"minecraft:tag_match\", \"tag\": \"examplemod:moon_rock\"}")));
"{\"predicate_type\": \"minecraft:tag_match\", \"tag\": \"examplemod:moon_rock\"}"), Map.of()));
assertTrue(unknownTag.getMessage().contains("examplemod:moon_rock"));
assertEquals(Set.of("examplemod:moon_stone"), VanillaRuleTests.hosts(json(
"{\"predicate_type\": \"minecraft:tag_match\", \"tag\": \"examplemod:moon_rock\"}"),
Map.of("examplemod:moon_rock", List.of("examplemod:moon_stone"))).blocks());
}
@Test
@@ -29,6 +29,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -55,7 +56,7 @@ public class WorldgenCatalogStoreTest {
List.of(new CatalogStructure("examplemod:tower", "minecraft:jigsaw", "surface_structures", "none",
"examplemod:has_tower", List.of("examplemod:glade"), List.of("examplemod:towers"))),
List.of(new CatalogDimensionType("examplemod:sky", 0, 256, 256, new JsonObject())),
List.of(new CatalogLevelStem("examplemod:sky", "examplemod:sky", "minecraft:noise", "minecraft:multi_noise", null)));
List.of(new CatalogLevelStem("examplemod:sky", "examplemod:sky", "minecraft:noise", "minecraft:multi_noise", null)), Map.of());
Path file = folder.getRoot().toPath().resolve("nested").resolve(WorldgenCatalogStore.FILE_NAME);
WorldgenCatalogStore.write(file, catalog);
@@ -72,7 +73,7 @@ public class WorldgenCatalogStoreTest {
List.of(new CatalogBiome("examplemod:glade", 0.7F, 0.8F, true, List.of(), List.of(List.of("examplemod:flowers")), List.of(), null),
new CatalogBiome("minecraft:forest", 0.7F, 0.8F, true, List.of(), List.of(List.of("examplemod:flowers")), List.of(), null),
new CatalogBiome("minecraft:desert", 2.0F, 0.0F, false, List.of(), List.of(), List.of(), null)),
List.of(), List.of(), List.of(), List.of());
List.of(), List.of(), List.of(), List.of(), Map.of());
WorldgenCatalogIndex index = WorldgenCatalogIndex.of(catalog);
assertEquals(1, index.biomesIn("examplemod").size());