From 965e19801d82ed037dce058ae470ea667687cce2 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 14 Oct 2026 22:37:28 -0400 Subject: [PATCH] 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 --- .../catalog/WorldgenCatalogAssembler.java | 48 +++- .../catalog/WorldgenCatalogAssemblerTest.java | 22 +- .../catalog/WorldgenCatalogSummaryTest.java | 5 +- .../iris/pack/ingest/ModFeatureIngester.java | 60 ++++- .../iris/pack/ingest/OreDepositConverter.java | 15 +- .../pack/ingest/VanillaPlacementChain.java | 8 + .../iris/pack/ingest/VanillaRuleTests.java | 24 +- .../pack/ingest/catalog/WorldgenCatalog.java | 7 +- .../ingest/catalog/WorldgenCatalogIndex.java | 4 + .../iris/pack/ingest/CanopyEstimateTest.java | 3 +- .../ingest/FeatureConverterFormatTest.java | 9 +- .../pack/ingest/IngestBiomePlacementTest.java | 3 +- .../pack/ingest/IngestHeightBoundsTest.java | 2 +- .../pack/ingest/IngestOreHostOrderTest.java | 250 ++++++++++++++++++ .../ingest/IngestReservedNamespaceTest.java | 2 +- .../iris/pack/ingest/IngestTreeScopeTest.java | 2 +- .../iris/pack/ingest/ModIngestFixture.java | 3 +- .../iris/pack/ingest/ModIngesterPackTest.java | 2 +- .../ingest/VanillaFeatureParsersTest.java | 16 +- .../catalog/WorldgenCatalogStoreTest.java | 5 +- 20 files changed, 445 insertions(+), 45 deletions(-) create mode 100644 core/src/test/java/art/arcane/iris/pack/ingest/IngestOreHostOrderTest.java diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java index e04c78b57..f96402494 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssembler.java @@ -37,12 +37,17 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot. import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenSpawn; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenStructure; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; public final class WorldgenCatalogAssembler { private static final String MINECRAFT = "minecraft"; @@ -58,16 +63,57 @@ public final class WorldgenCatalogAssembler { List structures = structures(snapshot.structures()); List dimensionTypes = dimensionTypes(snapshot.dimensionTypes()); List levelStems = levelStems(snapshot.levelStems()); + Map> blockTags = referencedBlockTags(placedFeatures, snapshot.blockTags()); Map> registryKeys = new LinkedHashMap<>(); registryKeys.put("biome", biomes.stream().map(CatalogBiome::key).toList()); registryKeys.put("placed_feature", placedFeatures.stream().map(CatalogPlacedFeature::key).toList()); registryKeys.put("structure", structures.stream().map(CatalogStructure::key).toList()); registryKeys.put("dimension_type", dimensionTypes.stream().map(CatalogDimensionType::key).toList()); registryKeys.put("level_stem", levelStems.stream().map(CatalogLevelStem::key).toList()); + registryKeys.put("block_tag", List.copyOf(blockTags.keySet())); List namespaces = namespaces(registryKeys, context); return new WorldgenCatalog(WorldgenCatalog.FORMAT, context.minecraftVersion(), context.platform(), context.capturedAtEpochMillis(), WorldgenCatalogFingerprint.of(namespaces, registryKeys), namespaces, - biomes, placedFeatures, structures, dimensionTypes, levelStems); + biomes, placedFeatures, structures, dimensionTypes, levelStems, blockTags); + } + + private static Map> referencedBlockTags(List features, + Map> available) { + Set referenced = new TreeSet<>(); + for (CatalogPlacedFeature feature : features) { + collectTagMatches(feature.config(), referenced); + } + Map> tags = new TreeMap<>(); + for (String tag : referenced) { + List members = available.get(tag); + if (members != null) { + tags.put(tag, members); + } + } + return tags; + } + + private static void collectTagMatches(JsonElement element, Set tags) { + if (element == null || element.isJsonNull() || element.isJsonPrimitive()) { + return; + } + if (element.isJsonArray()) { + for (JsonElement entry : element.getAsJsonArray()) { + collectTagMatches(entry, tags); + } + return; + } + JsonObject object = element.getAsJsonObject(); + JsonElement predicate = object.get("predicate_type"); + JsonElement tag = object.get("tag"); + if (predicate != null && predicate.isJsonPrimitive() && predicate.getAsString().endsWith("tag_match") + && tag != null && tag.isJsonPrimitive()) { + String key = tag.getAsString(); + tags.add(key.contains(":") ? key : MINECRAFT + ":" + key); + } + for (Map.Entry entry : object.entrySet()) { + collectTagMatches(entry.getValue(), tags); + } } private static List namespaces(Map> registryKeys, CatalogCaptureContext context) { diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java index 5641b0e42..e21538911 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogAssemblerTest.java @@ -23,6 +23,7 @@ import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem; import art.arcane.iris.pack.ingest.catalog.CatalogNamespace; import art.arcane.iris.pack.ingest.catalog.CatalogPlacedFeature; import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog; +import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogIndex; import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogFingerprint; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModMetadata; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot; @@ -34,6 +35,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot. import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenSpawn; import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenStructure; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import org.junit.Test; @@ -104,15 +106,27 @@ public class WorldgenCatalogAssemblerTest { "placed_feature", List.of("examplemod:ore_glimmer"), "structure", List.of("examplemod:tower"), "dimension_type", List.of("minecraft:overworld"), - "level_stem", List.of("minecraft:overworld"))); + "level_stem", List.of("minecraft:overworld"), + "block_tag", List.of("examplemod:glimmer_hosts"))); assertEquals(expected, catalog.contentFingerprint()); } + @Test + public void onlyBlockTagsReferencedByPlacedFeaturesAreKept() { + WorldgenCatalog catalog = WorldgenCatalogAssembler.assemble(snapshot(), CONTEXT); + + assertEquals(Map.of("examplemod:glimmer_hosts", List.of("minecraft:dirt", "minecraft:grass_block")), catalog.blockTags()); + assertEquals(List.of("minecraft:dirt", "minecraft:grass_block"), + WorldgenCatalogIndex.of(catalog).blockTag("examplemod:glimmer_hosts").orElseThrow()); + } + static NativeWorldgenSnapshot snapshot() { JsonObject definition = new JsonObject(); definition.add("downfall", new JsonPrimitive(0.4F)); - JsonObject config = new JsonObject(); - config.add("type", new JsonPrimitive("minecraft:ore")); + JsonObject config = JsonParser.parseString(""" + {"type": "minecraft:ore", "targets": [{"state": "examplemod:glimmer_ore", + "target": {"predicate_type": "minecraft:tag_match", "tag": "examplemod:glimmer_hosts"}}]} + """).getAsJsonObject(); return new NativeWorldgenSnapshot( List.of(new WorldgenBiome("examplemod:glade", 0.7F, 0.4F, true, List.of("minecraft:is_overworld"), List.of(List.of(), List.of("examplemod:ore_glimmer")), @@ -124,6 +138,8 @@ public class WorldgenCatalogAssemblerTest { List.of(new WorldgenDimensionType("minecraft:overworld", -64, 384, 384, null)), List.of(new WorldgenLevelStem("minecraft:overworld", "minecraft:overworld", "minecraft:noise", "minecraft:multi_noise", null)), + Map.of("examplemod:glimmer_hosts", List.of("minecraft:dirt", "minecraft:grass_block"), + "minecraft:unreferenced", List.of("minecraft:stone")), new EncodeFailures(0, List.of(), null)); } } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java index 583cbe162..f4fb9a1f7 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/catalog/WorldgenCatalogSummaryTest.java @@ -30,6 +30,7 @@ import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog; import org.junit.Test; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.junit.Assert.assertEquals; @@ -74,7 +75,7 @@ public class WorldgenCatalogSummaryTest { @Test public void keyNamespacesWithoutModMetadataStillSummarize() { WorldgenCatalog catalog = new WorldgenCatalog(WorldgenCatalog.FORMAT, "26.3", "fabric", 1L, "x", - List.of(), List.of(biome("datapack:dunes")), List.of(), List.of(), List.of(), List.of()); + List.of(), List.of(biome("datapack:dunes")), List.of(), List.of(), List.of(), List.of(), Map.of()); NamespaceSummary summary = WorldgenCatalogSummary.namespace(catalog, "datapack").orElseThrow(); @@ -104,7 +105,7 @@ public class WorldgenCatalogSummaryTest { List.of(new CatalogStructure("examplemod:tower", "minecraft:jigsaw", "surface_structures", "none", "", List.of(), List.of())), List.of(new CatalogDimensionType("examplemod:sky", 0, 256, 256, null)), - List.of(new CatalogLevelStem("examplemod:sky", "examplemod:sky", "minecraft:noise", "minecraft:fixed", null))); + List.of(new CatalogLevelStem("examplemod:sky", "examplemod:sky", "minecraft:noise", "minecraft:fixed", null)), Map.of()); } private static CatalogBiome biome(String key) { 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 5e656b795..7628b1b7d 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 @@ -27,6 +27,8 @@ import com.google.gson.JsonObject; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -37,6 +39,8 @@ import java.util.TreeSet; final class ModFeatureIngester { private static final String VANILLA_NAMESPACE = "minecraft"; + private static final String REPLACEABLE_BLOCKS = "replaceableBlocks"; + private static final String PALETTE = "palette"; private final IngestPackModel model; private final WorldgenCatalogIndex index; @@ -96,7 +100,7 @@ final class ModFeatureIngester { universe.addAll(model.reachableBiomes().values()); Set caveKeys = caveKeys(adopted); List items = new ArrayList<>(plans.size()); - for (FeaturePlan plan : plans.values()) { + for (FeaturePlan plan : hostProducersFirst(plans.values())) { String namespace = WorldgenCatalogIndex.namespaceOf(plan.key()); Set hosts = new TreeSet<>(plan.declaring()); hosts.retainAll(universe); @@ -128,6 +132,57 @@ final class ModFeatureIngester { return items; } + static List hostProducersFirst(Collection plans) { + List pending = new ArrayList<>(plans); + List ordered = new ArrayList<>(pending.size()); + while (!pending.isEmpty()) { + FeaturePlan next = pending.getFirst(); + for (FeaturePlan candidate : pending) { + if (!waitsForHostProducer(candidate, pending)) { + next = candidate; + break; + } + } + pending.remove(next); + ordered.add(next); + } + return ordered; + } + + private static boolean waitsForHostProducer(FeaturePlan consumer, List pending) { + Set hosts = depositBlocks(consumer, REPLACEABLE_BLOCKS); + if (hosts.isEmpty()) { + return false; + } + for (FeaturePlan producer : pending) { + if (producer != consumer && !Collections.disjoint(depositBlocks(producer, PALETTE), hosts)) { + return true; + } + } + return false; + } + + private static Set depositBlocks(FeaturePlan plan, String field) { + if (plan.deposits() == null) { + return Set.of(); + } + Set blocks = new TreeSet<>(); + for (JsonObject deposit : plan.deposits()) { + JsonElement values = deposit.get(field); + if (values == null || !values.isJsonArray()) { + continue; + } + for (JsonElement value : values.getAsJsonArray()) { + if (value.isJsonPrimitive()) { + blocks.add(value.getAsString()); + } else if (value.isJsonObject() && value.getAsJsonObject().has("block")) { + blocks.add(value.getAsJsonObject().get("block").getAsString()); + } + } + } + return blocks; + } + private boolean treeInPackVanillaBiomes(FeaturePlan plan) { Optional feature = index.placedFeature(plan.key()); if (feature.isEmpty() || !trees.isTree(feature.get())) { @@ -149,7 +204,8 @@ final class ModFeatureIngester { CatalogPlacedFeature placed = feature.get(); try { if (OreDepositConverter.handles(placed)) { - List deposits = OreDepositConverter.convert(placed, model.verticalBounds(), model.packMinY()); + List deposits = OreDepositConverter.convert(placed, model.verticalBounds(), model.packMinY(), + index.catalog().blockTags()); return new FeaturePlan(key, declaring, steps, deposits, null, ""); } if (PatchDecoratorConverter.handles(placed)) { diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/OreDepositConverter.java b/core/src/main/java/art/arcane/iris/pack/ingest/OreDepositConverter.java index 24bde812a..bd967f1a3 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/OreDepositConverter.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/OreDepositConverter.java @@ -25,6 +25,7 @@ import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -42,7 +43,8 @@ final class OreDepositConverter { return ORE_TYPES.contains(VanillaJson.namespaced(feature.featureType())); } - static List convert(CatalogPlacedFeature feature, VanillaHeights.VerticalBounds bounds, int packMinY) throws IngestConversionException { + static List convert(CatalogPlacedFeature feature, VanillaHeights.VerticalBounds bounds, int packMinY, + Map> blockTags) throws IngestConversionException { JsonObject config = VanillaJson.featureConfig(feature.config()); Placement placement = placement(VanillaJson.placement(feature.placement()), bounds); int size = VanillaJson.integer(config.get("size"), "ore size"); @@ -52,7 +54,7 @@ final class OreDepositConverter { double discard = VanillaJson.number(config, "discard_chance_on_air_exposure", 0D, "ore"); String shape = VanillaJson.namespaced(feature.featureType()).equals("minecraft:scattered_ore") ? "VANILLA_SCATTERED" : "VANILLA_ELLIPSOID"; List deposits = new ArrayList<>(); - for (Target target : targets(VanillaJson.array(config.get("targets"), "ore targets"))) { + for (Target target : targets(VanillaJson.array(config.get("targets"), "ore targets"), blockTags)) { JsonObject deposit = new JsonObject(); deposit.addProperty("minHeight", placement.band().min() - packMinY); deposit.addProperty("maxHeight", placement.band().max() - packMinY); @@ -84,13 +86,13 @@ final class OreDepositConverter { return deposits; } - private static List targets(JsonArray targets) throws IngestConversionException { + private static List targets(JsonArray targets, Map> blockTags) throws IngestConversionException { List result = new ArrayList<>(targets.size()); SortedSet claimed = new TreeSet<>(); for (JsonElement entry : targets) { JsonObject target = VanillaJson.object(entry, "ore target"); JsonObject block = VanillaBlockStates.irisBlock(target.get("state")); - VanillaRuleTests.HostSet hosts = VanillaRuleTests.hosts(target.get("target")); + VanillaRuleTests.HostSet hosts = VanillaRuleTests.hosts(target.get("target"), blockTags); if (hosts.any()) { if (!claimed.isEmpty()) { throw new IngestConversionException("ore target accepting any block follows restricted targets"); @@ -125,6 +127,11 @@ final class OreDepositConverter { } case "minecraft:rarity_filter" -> spawnChance /= Math.max(1D, VanillaJson.number(object.get("chance"), "rarity_filter.chance")); case "minecraft:random_chance" -> spawnChance *= Math.max(0D, Math.min(1D, VanillaJson.number(object.get("chance"), "random_chance.chance"))); + case "minecraft:noise_based_count", "minecraft:noise_threshold_count" -> { + double mean = VanillaPlacementChain.noiseCountMean(object); + minCount = Math.min(MAX_PER_CHUNK, Math.round(minCount * mean)); + maxCount = Math.min(MAX_PER_CHUNK, Math.round(maxCount * mean)); + } case "minecraft:in_square", "minecraft:biome" -> { } case "minecraft:height_range" -> band = VanillaHeights.band(object.get("height"), bounds); diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/VanillaPlacementChain.java b/core/src/main/java/art/arcane/iris/pack/ingest/VanillaPlacementChain.java index c239cf879..a0816f0e0 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/VanillaPlacementChain.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/VanillaPlacementChain.java @@ -166,6 +166,14 @@ final class VanillaPlacementChain { return values.get(0).getAsInt() == 0 && values.get(1).getAsInt() == -1 && values.get(2).getAsInt() == 0; } + static double noiseCountMean(JsonObject object) throws IngestConversionException { + if (VanillaJson.type(object, "type").equals("minecraft:noise_threshold_count")) { + return (VanillaJson.number(object.get("below_noise"), "noise_threshold_count.below_noise") + + VanillaJson.number(object.get("above_noise"), "noise_threshold_count.above_noise")) / 2D; + } + return noiseBasedMean(object); + } + private static double noiseBasedMean(JsonObject object) throws IngestConversionException { double ratio = VanillaJson.number(object.get("noise_to_count_ratio"), "noise_based_count.noise_to_count_ratio"); double offset = VanillaJson.number(object, "noise_offset", 0D, "noise_based_count"); diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/VanillaRuleTests.java b/core/src/main/java/art/arcane/iris/pack/ingest/VanillaRuleTests.java index 94b08683b..5eedfd336 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/VanillaRuleTests.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/VanillaRuleTests.java @@ -42,32 +42,32 @@ final class VanillaRuleTests { private VanillaRuleTests() { } - static HostSet hosts(JsonElement ruleTest) throws IngestConversionException { + static HostSet hosts(JsonElement ruleTest, Map> blockTags) throws IngestConversionException { JsonObject object = VanillaJson.object(ruleTest, "ore target predicate"); String type = VanillaJson.type(object, "predicate_type"); return switch (type) { case "minecraft:always_true" -> HostSet.ANY; - case "minecraft:tag_match" -> tag(VanillaJson.namespaced(VanillaJson.string(object.get("tag"), "tag_match.tag"))); + case "minecraft:tag_match" -> tag(VanillaJson.namespaced(VanillaJson.string(object.get("tag"), "tag_match.tag")), blockTags); case "minecraft:block_match" -> HostSet.of(Set.of(VanillaJson.namespaced(VanillaJson.string(object.get("block"), "block_match.block")))); case "minecraft:blockstate_match" -> HostSet.of(Set.of(VanillaBlockStates.irisBlock(object.get("block_state")).get("block").getAsString())); - case "minecraft:any_of" -> anyOf(rules(object)); - case "minecraft:all_of" -> allOf(rules(object)); + case "minecraft:any_of" -> anyOf(rules(object), blockTags); + case "minecraft:all_of" -> allOf(rules(object), blockTags); default -> throw new IngestConversionException("unsupported ore target predicate " + (type.isEmpty() ? object.toString() : type)); }; } - private static HostSet tag(String tag) throws IngestConversionException { - List blocks = BLOCK_TAGS.get(tag); + private static HostSet tag(String tag, Map> blockTags) throws IngestConversionException { + List blocks = blockTags.containsKey(tag) ? blockTags.get(tag) : BLOCK_TAGS.get(tag); if (blocks == null) { - throw new IngestConversionException("ore target uses block tag " + tag + " whose members are not known offline"); + throw new IngestConversionException("ore target uses block tag " + tag + " whose members the catalog did not capture"); } return HostSet.of(Set.copyOf(blocks)); } - private static HostSet anyOf(List rules) throws IngestConversionException { + private static HostSet anyOf(List rules, Map> blockTags) throws IngestConversionException { SortedSet union = new TreeSet<>(); for (JsonElement rule : rules) { - HostSet hosts = hosts(rule); + HostSet hosts = hosts(rule, blockTags); if (hosts.any()) { return HostSet.ANY; } @@ -76,7 +76,7 @@ final class VanillaRuleTests { return HostSet.of(union); } - private static HostSet allOf(List rules) throws IngestConversionException { + private static HostSet allOf(List rules, Map> blockTags) throws IngestConversionException { HostSet positive = null; SortedSet negative = new TreeSet<>(); for (JsonElement rule : rules) { @@ -86,14 +86,14 @@ final class VanillaRuleTests { continue; } if (type.equals("minecraft:not")) { - HostSet excluded = hosts(ruleObject.get("rule")); + HostSet excluded = hosts(ruleObject.get("rule"), blockTags); if (excluded.any()) { throw new IngestConversionException("ore target predicate never matches"); } negative.addAll(excluded.blocks()); continue; } - positive = intersect(positive, hosts(ruleObject)); + positive = intersect(positive, hosts(ruleObject, blockTags)); } if (positive == null) { throw new IngestConversionException("ore target all_of has no block constraint"); diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalog.java b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalog.java index ca04f42d5..70da5521c 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalog.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalog.java @@ -20,6 +20,7 @@ package art.arcane.iris.pack.ingest.catalog; import java.util.List; +import java.util.Map; import java.util.Objects; public record WorldgenCatalog( @@ -33,8 +34,9 @@ public record WorldgenCatalog( List placedFeatures, List structures, List dimensionTypes, - List levelStems) { - public static final int FORMAT = 1; + List levelStems, + Map> blockTags) { + public static final int FORMAT = 2; public WorldgenCatalog { Objects.requireNonNull(minecraftVersion, "minecraftVersion"); @@ -46,5 +48,6 @@ public record WorldgenCatalog( structures = List.copyOf(Objects.requireNonNull(structures, "structures")); dimensionTypes = List.copyOf(Objects.requireNonNull(dimensionTypes, "dimensionTypes")); levelStems = List.copyOf(Objects.requireNonNull(levelStems, "levelStems")); + blockTags = Map.copyOf(Objects.requireNonNull(blockTags, "blockTags")); } } diff --git a/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogIndex.java b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogIndex.java index c198818e3..f7edb206e 100644 --- a/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogIndex.java +++ b/core/src/main/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogIndex.java @@ -74,6 +74,10 @@ public final class WorldgenCatalogIndex { return Optional.ofNullable(dimensionTypes.get(key)); } + public Optional> blockTag(String key) { + return Optional.ofNullable(catalog.blockTags().get(key)); + } + public Optional levelStem(String key) { return Optional.ofNullable(levelStems.get(key)); } diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/CanopyEstimateTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/CanopyEstimateTest.java index 53f6f1862..a4ad5bcf3 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/CanopyEstimateTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/CanopyEstimateTest.java @@ -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 vegetal) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/FeatureConverterFormatTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/FeatureConverterFormatTest.java index b1e79e0d2..63408752f 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/FeatureConverterFormatTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/FeatureConverterFormatTest.java @@ -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 deposits = OreDepositConverter.convert(iron, OVERWORLD, -64); + List 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 deposits = OreDepositConverter.convert(clay, OVERWORLD, -64); + List 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())); } } diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestBiomePlacementTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestBiomePlacementTest.java index a2a5c845f..94271d321 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/IngestBiomePlacementTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestBiomePlacementTest.java @@ -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) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestHeightBoundsTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestHeightBoundsTest.java index dc24ffe07..664e82cbd 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/IngestHeightBoundsTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestHeightBoundsTest.java @@ -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); diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestOreHostOrderTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestOreHostOrderTest.java new file mode 100644 index 000000000..8a02c4817 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestOreHostOrderTest.java @@ -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 . + */ + +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 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 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 chunk = mock(MantleChunk.class, withSettings().stubOnly()); + Mantle 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 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 terrain = islandChunk(); + modifier.generateDeposits(terrain, chunkX, 0, false, context); + count += count(terrain, block); + } + } finally { + burst.close(); + } + return count; + } + + private Hunk islandChunk() { + Hunk 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 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 biomes = new ArrayList<>(fixture.biomes()); + List> 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 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> 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 resolved; + + private void bind(Map states) { + resolved = new KList<>(); + for (IrisBlockData block : getPalette()) { + resolved.add(states.computeIfAbsent(block.getBlock(), KeyedBlockState::new)); + } + } + + @Override + public KList getBlockData(IrisData data) { + return resolved; + } + } +} diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java index db03f6c78..f31f881f8 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestReservedNamespaceTest.java @@ -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) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/IngestTreeScopeTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/IngestTreeScopeTest.java index 8527399cb..57e5b6d61 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/IngestTreeScopeTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/IngestTreeScopeTest.java @@ -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) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/ModIngestFixture.java b/core/src/test/java/art/arcane/iris/pack/ingest/ModIngestFixture.java index a384b6346..4409d2087 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/ModIngestFixture.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/ModIngestFixture.java @@ -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 namespaces, boolean replace) { diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/ModIngesterPackTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/ModIngesterPackTest.java index ea1d3fa92..71b15de7a 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/ModIngesterPackTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/ModIngesterPackTest.java @@ -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)); diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/VanillaFeatureParsersTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/VanillaFeatureParsersTest.java index 694b8fc87..36008190c 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/VanillaFeatureParsersTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/VanillaFeatureParsersTest.java @@ -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 diff --git a/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogStoreTest.java b/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogStoreTest.java index 64fd6fba4..980c8a39e 100644 --- a/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogStoreTest.java +++ b/core/src/test/java/art/arcane/iris/pack/ingest/catalog/WorldgenCatalogStoreTest.java @@ -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());