From e9ba2db70eea30a866969420fcc16ff4d42a0f36 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 10:39:53 +0000 Subject: [PATCH] Keep saved ore settings when providers rename rules --- .github/workflows/ci.yml | 6 +- CHANGELOG.txt | 6 + README.md | 7 +- docs/MIGRATION.md | 43 ++ docs/VERSIONS.md | 2 +- gradle.properties | 2 +- gradle/release/artifacts.gradle | 10 +- .../RetainedProviderOreDefaults.java | 132 +++++ .../WorldgenIntegrationManager.java | 8 + .../worldgen/LegacyConfigMigrator.java | 2 +- .../LegacyMineralogyProfileMigration.java | 2 +- .../RetainedProviderOreDefaultsTest.java | 336 +++++++++++ src/test/resources/provider-merge/README.md | 16 + .../current-basemetals-ores.json | 325 +++++++++++ .../retained-basemetals-ores.json | 551 ++++++++++++++++++ 15 files changed, 1435 insertions(+), 13 deletions(-) create mode 100644 src/main/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaults.java create mode 100644 src/test/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaultsTest.java create mode 100644 src/test/resources/provider-merge/README.md create mode 100644 src/test/resources/provider-merge/current-basemetals-ores.json create mode 100644 src/test/resources/provider-merge/retained-basemetals-ores.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0959760e..5dc237a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,9 +125,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/OreSpawn-4.1.0.2601021.jar - build/libs/OreSpawn-4.1.0.2601021-sources.jar - build/libs/OreSpawn-4.1.0.2601021-javadoc.jar + build/libs/OreSpawn-4.1.1.2601021.jar + build/libs/OreSpawn-4.1.1.2601021-sources.jar + build/libs/OreSpawn-4.1.1.2601021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 2be39fec..7538a188 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,9 @@ +Version 4.1.1.2601021 + +* Stop recognised ore-rule renames from adding a second generation pass to existing profiles. +* Keep saved ore settings and show matching modern rules as disabled, editable alternatives. +* Preserve explicit choices, deleted rules, and deliberately separate deposits. + Version 4.1.0.2601021 * Leave imported vanilla ores inactive while Manage Vanilla Ores is off. diff --git a/README.md b/README.md index e5c67f29..28ea309d 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,15 @@ Its OS3 compatibility layer preserves ranged legacy block budgets, exclusive legacy height ceilings, and the historical "all dimensions except Nether and End" policy used by mods such as Base Metals. +The [provider-merge repair](docs/MIGRATION.md#retained-provider-rules) keeps +recognised legacy ore settings when modern provider IDs are first added. +Matching alternatives remain visible but disabled, so upgrading does not add +a second generation pass for the same deposit. + This is not the unrelated mod that adds mobs and dimensions under the same name. -This branch builds target-qualified version `4.1.0.2601021`: the OreSpawn 4.1 +This branch builds target-qualified version `4.1.1.2601021`: the OreSpawn 4.1 feature set for Minecraft 26.1.2 and Forge. See the [versioning policy](docs/VERSIONS.md) for the encoding and release convention. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 92846911..805768e8 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -17,6 +17,49 @@ When legacy OreSpawn rules are translated, a concise player-facing summary is also written atomically to `config/orespawn-upgrade-report.txt`; the existing detailed rule report remains at `config/orespawn-migration/migration-report.txt`. +## Retained Provider Rules + +Some existing OreSpawn 4 profiles still use rule IDs declared by older +providers. A changed rule ID must not silently add another generation pass +for the same deposit. The provider-merge repair recognises two positive +lineages: + +- OreSpawn's own imported `orespawn:legacy//...` rules. +- Base Metals' historical `basemetals:legacy/_ore` to + `basemetals:ore/` replacements for coldiron, adamantine, starsteel, + tin, lead, zinc, silver, mercury, nickel and platinum. Base Metals itself + declared the old IDs in its maintained 1.10/1.12 providers; this is an exact + compatibility list, not a guess based on the word `legacy`. + +The merge requires one saved rule and one current candidate with the same +single output, default metadata, and exact dimension/selector and effective +placement-channel footprint. It does not infer equivalence from overlapping +dimensions, a shared material name, or an `orphaned_provider` flag. Mixed +outputs, different deep outputs, and ambiguous matches retain the existing +merge behaviour rather than being guessed away. +Explicit conflicting `source_provider` or fallback `source_mod` ownership is +not treated as a replacement. The importer's neutral `legacy` owner marker is +accepted only with OreSpawn's own imported IDs. + +When that relationship is unambiguous, OreSpawn adds the missing modern rule +as a complete **disabled** alternative. The saved rule's rates, hosts, heights, +pattern, enablement and policy choices remain authoritative. Disabled and +zero-frequency settings count as saved choices too. Fresh installations keep +the provider's ordinary defaults. + +Players can enable the alternative in the normal ore settings. Later merges +preserve that explicit choice; they also preserve existing modern rules and +intentional deletions recorded in `known_ores`. Already-present old/new rule +combinations are not automatically cleaned up, since the player may have +chosen both. A deleted historical Base Metals rule recorded in `known_ores` +also keeps its mapped modern replacement disabled; that exact ID relationship +is known even though the old placement payload has been removed. Other custom +deposits do not cancel that recorded deletion and are not changed. Deleted +imports without an explicit historical mapping are not matched by guessing an +output from their filename. +Review ambiguous profiles explicitly. This repair does not change API +versions, schemas, or already-generated chunks. + ## Existing Mineralogy Worlds An already-generated world without an OreSpawn per-world profile is inspected diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index c1159bfc..da007f1b 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -62,7 +62,7 @@ Examples: | 1.20.6 | Forge | `120061` | `4.0.16.120061` | | 1.21.1 | Forge | `121011` | `4.0.16.121011` | | 1.21.11 | Forge | `121111` | `4.0.16.121111` | -| 26.1.2 | Forge | `2601021` | `4.1.0.2601021` | +| 26.1.2 | Forge | `2601021` | `4.1.1.2601021` | | 26.2 | Forge | `2602001` | `4.0.6.2602001` | | 26.2 | NeoForge | `2602002` | `4.0.6.2602002` | diff --git a/gradle.properties b/gradle.properties index 21b90309..5cec1ef7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -43,7 +43,7 @@ mapping_version=26.1.2 mod_id=orespawn mod_name=MMD OreSpawn mod_license=LGPL-2.1 -mod_version=4.1.0.2601021 +mod_version=4.1.1.2601021 mod_group=zone.moddev.mc.orespawn mod_authors=SkyBlade1978, dshadowwolf, the MMD Team mod_vendor=Minecraft Mod Development diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index 46e27f40..b43d09ee 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -79,7 +79,7 @@ def preparedReleaseDir = providers.gradleProperty('preparedReleaseDir') tasks.register('verifyReleaseConfiguration') { group = 'verification' doLast { - if (project.mod_version != '4.1.0.2601021' + if (project.mod_version != '4.1.1.2601021' || project.mod_group != expectedMavenGroup || project.minecraft_version != '26.1.2' || project.forge_version != '64.0.9' @@ -93,9 +93,9 @@ tasks.register('verifyReleaseConfiguration') { throw new GradleException('Unexpected dispatcher or Java target metadata') } List expectedPublicArtifacts = [ - 'OreSpawn-4.1.0.2601021.jar', - 'OreSpawn-4.1.0.2601021-sources.jar', - 'OreSpawn-4.1.0.2601021-javadoc.jar' + 'OreSpawn-4.1.1.2601021.jar', + 'OreSpawn-4.1.1.2601021-sources.jar', + 'OreSpawn-4.1.1.2601021-javadoc.jar' ] if (base.archivesName.get() != expectedMavenArtifact || expectedReleaseFiles.get().collect { it.toString() } != expectedPublicArtifacts) { @@ -112,7 +112,7 @@ tasks.register('verifyReleaseConfiguration') { 'src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java', 'README.md', 'CHANGELOG.txt' ].each { path -> - if (!file(path).getText('UTF-8').contains('4.1.0.2601021')) { + if (!file(path).getText('UTF-8').contains('4.1.1.2601021')) { throw new GradleException("Release identity missing from ${path}") } } diff --git a/src/main/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaults.java b/src/main/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaults.java new file mode 100644 index 00000000..08041ce7 --- /dev/null +++ b/src/main/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaults.java @@ -0,0 +1,132 @@ +package zone.moddev.mc.orespawn.integration; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.Identifier; + +/** Conservative matching at provider merge time, never in chunk generation. */ +final class RetainedProviderOreDefaults { + + private static final Set BASE_METALS_LEGACY_MATERIALS = Set.of( + "coldiron", "adamantine", "starsteel", "tin", "lead", "zinc", + "silver", "mercury", "nickel", "platinum"); + + private record Placement(String kind, String dimension, String channel) {} + + private RetainedProviderOreDefaults() {} + + static String predecessor(String owner, String currentId, JsonObject current, + JsonObject defaults, JsonObject saved, Set known) { + String output = singletonOutput(currentId, current); + Set scope = placements(current); + if (output == null || scope == null || scope.isEmpty()) return null; + String historical = historicalPredecessor(owner, currentId, output); + if (historical != null && known.contains(historical) && !saved.has(historical)) return historical; + + int replacements = 0; + for (var entry : defaults.entrySet()) { + if (equivalentOutputAndScope(entry.getKey(), entry.getValue(), output, scope)) replacements++; + } + if (replacements != 1) return null; + + String retained = null; + for (var entry : saved.entrySet()) { + if (!equivalentOutputAndScope(entry.getKey(), entry.getValue(), output, scope)) continue; + if (retained != null) return null; + retained = entry.getKey(); + } + return retained != null && declaredPredecessor(owner, currentId, output, retained, saved.getAsJsonObject(retained)) + ? retained : null; + } + + private static boolean declaredPredecessor(String owner, String currentId, String output, String savedId, JsonObject saved) { + String importedPrefix = "orespawn:legacy/" + owner + "/"; + boolean imported = savedId.startsWith(importedPrefix) && savedId.length() > importedPrefix.length(); + if (saved.has("source_provider")) { + if (!owner.equals(text(saved.get("source_provider")))) return false; + } else if (saved.has("source_mod")) { + String fallbackOwner = text(saved.get("source_mod")); + if (!owner.equals(fallbackOwner) && !(imported && "legacy".equals(fallbackOwner))) return false; + } + if (imported) return true; + return savedId.equals(historicalPredecessor(owner, currentId, output)); + } + + private static String historicalPredecessor(String owner, String currentId, String output) { + // Base Metals' maintained 1.10/1.12 providers declared these IDs themselves. + // A generic provider's "legacy/" prefix is not evidence of a replacement. + if (!owner.equals("basemetals")) return null; + for (String material : BASE_METALS_LEGACY_MATERIALS) { + if (currentId.equals("basemetals:ore/" + material) + && output.equals("basemetals:" + material + "_ore")) return "basemetals:legacy/" + material + "_ore"; + } + return null; + } + + private static boolean equivalentOutputAndScope(String id, JsonElement rule, String output, Set scope) { + return rule.isJsonObject() && output.equals(singletonOutput(id, rule.getAsJsonObject())) + && scope.equals(placements(rule.getAsJsonObject())); + } + + private static String singletonOutput(String id, JsonObject rule) { + String output = rule.has("block") ? text(rule.get("block")) : id; + Identifier parsed = output == null ? null : Identifier.tryParse(output); + if (parsed == null || !defaultMetadata(rule)) return null; + output = parsed.toString(); + if (rule.has("deep_output") && !output.equals(text(rule.get("deep_output")))) return null; + if (rule.has("outputs")) { + JsonElement values = rule.get("outputs"); + if (!values.isJsonArray() || values.getAsJsonArray().size() != 1 + || !values.getAsJsonArray().get(0).isJsonObject()) return null; + JsonObject only = values.getAsJsonArray().get(0).getAsJsonObject(); + if (!output.equals(text(only.get("block"))) || !defaultMetadata(only)) return null; + } + return output; + } + + private static boolean defaultMetadata(JsonObject rule) { + JsonElement metadata = rule.get("metadata"); + return metadata == null || metadata.isJsonPrimitive() && metadata.getAsJsonPrimitive().isNumber() + && metadata.getAsBigDecimal().signum() == 0; + } + + private static Set placements(JsonObject rule) { + Set result = new HashSet<>(); + for (String kind : List.of("dimensions", "dimension_selectors")) { + JsonElement table = rule.get(kind); + if (table == null) continue; + if (!table.isJsonObject()) return null; + for (var entry : table.getAsJsonObject().entrySet()) { + if (Identifier.tryParse(entry.getKey()) == null || !entry.getValue().isJsonObject()) return null; + // Disabled and zero-rate placements are saved choices too. + result.add(new Placement(kind, entry.getKey(), channel(entry.getValue().getAsJsonObject()))); + } + } + return result; + } + + private static String channel(JsonObject placement) { + Identifier explicit = identifier(placement.get("placement_channel")); + if (explicit != null) return explicit.toString(); + JsonElement pattern = placement.get("pattern"); + Identifier type = pattern != null && pattern.isJsonObject() + ? identifier(pattern.getAsJsonObject().get("type")) : null; + String legacy = text(pattern); + if (type == null && legacy != null && legacy.indexOf(':') >= 0) type = Identifier.tryParse(legacy); + return type == null ? "orespawn:standard" : type.toString(); + } + + private static Identifier identifier(JsonElement value) { + String name = text(value); + return name == null ? null : Identifier.tryParse(name); + } + + private static String text(JsonElement value) { + return value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isString() + ? value.getAsString() : null; + } +} diff --git a/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java b/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java index 03009db6..47bfdc22 100644 --- a/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java +++ b/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java @@ -248,6 +248,14 @@ public static synchronized boolean mergeProviderDefinitions(JsonObject target) { String id = entry.getKey(); if (!targetSection.has(id) && !known.contains(id)) { JsonObject value = entry.getValue().getAsJsonObject().deepCopy(); + if ("ores".equals(sectionName)) { + String retained = RetainedProviderOreDefaults.predecessor( + provider.modId, id, value, providerSection, targetSection, known); + if (retained != null) { + value.addProperty("enabled", false); + LOGGER.info("Preserved legacy ore choice '{}'; added provider alternative '{}' disabled", retained, id); + } + } if (!"biome_rules".equals(sectionName)) { value.addProperty("source_provider", provider.modId); } diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java index 1f69e1d0..67afc54f 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java @@ -358,7 +358,7 @@ private static void writeReport(Path config, List lines) { private static void writeUpgradeReport(Path config, int imported, List detail) { List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.2601021 Upgrade Report"); + lines.add("OreSpawn 4.1.1.2601021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Legacy OreSpawn settings were imported into the OS4 profile."); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java index 754b643b..f19ff9cd 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java @@ -205,7 +205,7 @@ private static void writeUpgradeReport(Path worldRoot, Path configPath, Path report = worldRoot.resolve("serverconfig/orespawn-upgrade-report.txt"); List missing = missingBlocks(igneous, metamorphic, sedimentary); List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.2601021 Upgrade Report"); + lines.add("OreSpawn 4.1.1.2601021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Existing Mineralogy " + identity.version + " world detected."); diff --git a/src/test/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaultsTest.java b/src/test/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaultsTest.java new file mode 100644 index 00000000..5be3f9cb --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaultsTest.java @@ -0,0 +1,336 @@ +package zone.moddev.mc.orespawn.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class RetainedProviderOreDefaultsTest { + + private Map providers; + private Map previous; + private JsonObject provider; + private JsonObject profile; + + @BeforeEach + @SuppressWarnings("unchecked") + void installOnlyTheFixtureProvider() throws Exception { + var field = WorldgenIntegrationManager.class.getDeclaredField("ACTIVE_PROVIDERS"); + field.setAccessible(true); + providers = (Map) field.get(null); + previous = new LinkedHashMap<>(providers); + providers.clear(); + provider = fixture("current-basemetals-ores.json"); + profile = fixture("retained-basemetals-ores.json"); + JsonObject policy = JsonParser.parseString(""" + {"mode":"keep_separate","outputs":{"basemetals:tin_ore":1.0}, + "placement_sources":{"orespawn:standard":"basemetals:legacy/tin_ore"}} + """).getAsJsonObject(); + JsonObject policies = new JsonObject(); + policies.add("orespawn:tin|minecraft:overworld", policy); + profile.add("ore_source_policies", policies); + activate("basemetals", provider); + } + + @AfterEach + void restoreOtherProviderTests() { + if (providers != null && previous != null) { + providers.clear(); + providers.putAll(previous); + } + } + + @ParameterizedTest + @ValueSource(strings = {"coldiron", "adamantine", "starsteel", "tin", "lead", "zinc", "silver", "mercury", "nickel", "platinum"}) + void retainsEachSavedBaseMetalsRuleAndAddsACompleteDisabledAlternative(String material) { + JsonObject before = profile.deepCopy(); + JsonObject providerBefore = provider.deepCopy(); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + JsonObject alternative = ores(profile).getAsJsonObject("basemetals:ore/" + material); + assertFalse(alternative.get("enabled").getAsBoolean(), material + " must not get a second placement pass"); + JsonObject expected = ores(provider).getAsJsonObject("basemetals:ore/" + material).deepCopy(); + expected.addProperty("enabled", false); + expected.addProperty("source_provider", "basemetals"); + assertEquals(expected, alternative, "The alternative remains complete and editable"); + assertSavedRules(before); + assertEquals(before.get("ore_source_policies"), profile.get("ore_source_policies")); + assertEquals(providerBefore, provider, "Merging must not change packaged provider defaults"); + } + + @Test + void disabledAndZeroFrequencySavedChoicesAreNotReenabled() { + JsonObject saved = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + saved.addProperty("enabled", false); + JsonObject placement = saved.getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether"); + placement.addProperty("enabled", false); + placement.addProperty("frequency", 0); + JsonObject before = profile.deepCopy(); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + assertSavedRules(before); + } + + @Test + void aUserMayEnableTheAlternativeAndThatChoiceSurvivesRepeatedMerges() { + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + ores(profile).getAsJsonObject("basemetals:ore/coldiron").addProperty("enabled", true); + JsonObject chosen = profile.deepCopy(); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(chosen, profile); + assertTrue(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @Test + void anExistingExplicitModernRuleIsNeverChanged() { + JsonObject chosen = ores(provider).getAsJsonObject("basemetals:ore/coldiron").deepCopy(); + chosen.getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether").addProperty("frequency", 0.125); + ores(profile).add("basemetals:ore/coldiron", chosen.deepCopy()); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(chosen, ores(profile).get("basemetals:ore/coldiron")); + } + + @Test + void anIntentionallyRemovedKnownAlternativeIsNotRecreated() { + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + ores(profile).remove("basemetals:ore/coldiron"); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).has("basemetals:ore/coldiron")); + } + + @Test + void deletingAKnownHistoricalRuleDoesNotEnableItsReplacement() { + ores(profile).remove("basemetals:legacy/coldiron_ore"); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).has("basemetals:legacy/coldiron_ore")); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2}) + void unrelatedMatchingRowsDoNotOverrideAKnownHistoricalDeletion(int count) { + ores(profile).remove("basemetals:legacy/coldiron_ore"); + Map custom = new LinkedHashMap<>(); + for (int index = 0; index < count; index++) { + JsonObject row = ores(provider).getAsJsonObject("basemetals:ore/coldiron").deepCopy(); + row.addProperty("source_provider", "anothermod"); + String id = "anothermod:custom/coldiron_" + index; + custom.put(id, row.deepCopy()); + ores(profile).add(id, row); + } + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + custom.forEach((id, row) -> assertEquals(row, ores(profile).get(id))); + } + + @Test + void aSecondCurrentDepositDoesNotOverrideAnExplicitHistoricalDeletion() throws Exception { + ores(profile).remove("basemetals:legacy/coldiron_ore"); + ores(provider).add("basemetals:ore/extra_coldiron", ores(provider).get("basemetals:ore/coldiron").deepCopy()); + activate("basemetals", provider); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + assertTrue(ores(profile).getAsJsonObject("basemetals:ore/extra_coldiron").get("enabled").getAsBoolean()); + } + + @Test + void aRuleThatWasNeverKnownStillGetsItsNormalDefault() { + ores(profile).remove("basemetals:legacy/coldiron_ore"); + profile.remove("providers"); + assertCurrentColdIronEnabled(); + } + + @Test + void freshProfilesKeepAllCurrentProviderDefaults() { + profile = new JsonObject(); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + for (var entry : ores(provider).entrySet()) { + JsonObject expected = entry.getValue().getAsJsonObject().deepCopy(); + expected.addProperty("source_provider", "basemetals"); + assertEquals(expected, ores(profile).get(entry.getKey())); + } + } + + @Test + void aCustomRuleOrOrphanMarkerAloneDoesNotDeclareALegacyReplacement() { + JsonObject old = ores(profile).remove("basemetals:legacy/coldiron_ore").getAsJsonObject(); + // No historical deletion is recorded in this unrelated-rule fixture. + profile.remove("providers"); + old.addProperty("orphaned_provider", true); + ores(profile).add("basemetals:custom/extra_coldiron", old); + assertCurrentColdIronEnabled(); + } + + @Test + void foreignOwnerProvenanceDoesNotDisableThisProvidersDefaults() { + JsonObject old = ores(profile).remove("basemetals:legacy/coldiron_ore").getAsJsonObject(); + profile.remove("providers"); + old.addProperty("source_provider", "anothermod"); + ores(profile).add("anothermod:legacy/coldiron_ore", old); + assertCurrentColdIronEnabled(); + } + + @Test + void conflictingExplicitOwnerIsNotTreatedAsAnOwnedImport() { + ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore").addProperty("source_provider", "anothermod"); + assertCurrentColdIronEnabled(); + } + + @Test + void oreSpawnImporterIdsAlsoCarryPositiveOwnerProvenance() { + JsonObject old = ores(profile).remove("basemetals:legacy/coldiron_ore").getAsJsonObject(); + profile.remove("providers"); + old.remove("source_provider"); + ores(profile).add("orespawn:legacy/basemetals/coldiron_ore", old); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @ParameterizedTest + @ValueSource(strings = {"orespawn:legacy/examplemod/diamond", "examplemod:legacy/diamond"}) + void genericImportsAreRecognisedButAnArbitraryProvidersLegacyPrefixIsNot(String savedId) throws Exception { + JsonObject current = ores(provider).getAsJsonObject("basemetals:ore/coldiron").deepCopy(); + current.addProperty("block", "minecraft:diamond_ore"); + current.addProperty("source_mod", "examplemod"); + JsonObject defaults = new JsonObject(); + JsonObject currentOres = new JsonObject(); + currentOres.add("examplemod:ore/diamond", current); + defaults.add("ores", currentOres); + providers.clear(); + activate("examplemod", defaults); + JsonObject saved = current.deepCopy(); + JsonObject savedOres = new JsonObject(); + savedOres.add(savedId, saved); + profile = new JsonObject(); + profile.add("ores", savedOres); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(!savedId.startsWith("orespawn:legacy/"), + ores(profile).getAsJsonObject("examplemod:ore/diamond").get("enabled").getAsBoolean()); + assertEquals(saved, ores(profile).get(savedId)); + } + + @ParameterizedTest + @ValueSource(strings = {"legacy", "basemetals", "anothermod"}) + void fallbackOwnershipMustNotConflictWithTheImportedOwner(String fallbackOwner) { + JsonObject old = ores(profile).remove("basemetals:legacy/coldiron_ore").getAsJsonObject(); + profile.remove("providers"); + old.remove("source_provider"); + old.addProperty("source_mod", fallbackOwner); + ores(profile).add("orespawn:legacy/basemetals/coldiron_ore", old); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(fallbackOwner.equals("anothermod"), + ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @Test + void explicitProviderOwnershipTakesPrecedenceOverTheFallbackOwner() { + ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore").addProperty("source_mod", "anothermod"); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @Test + void separateDimensionAndPlacementChannelDepositsRemainEnabled() { + JsonObject old = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + JsonObject placement = old.getAsJsonObject("dimensions").remove("minecraft:the_nether").getAsJsonObject(); + old.getAsJsonObject("dimensions").add("minecraft:the_end", placement); + assertCurrentColdIronEnabled(); + old.getAsJsonObject("dimensions").remove("minecraft:the_end"); + old.getAsJsonObject("dimensions").add("minecraft:the_nether", placement); + placement.addProperty("placement_channel", "anothermod:enrichment"); + assertCurrentColdIronEnabled(); + } + + @Test + void multipleCurrentPlacementsForOneOutputAreNotGuessedAway() throws Exception { + ores(provider).add("basemetals:ore/extra_coldiron", ores(provider).get("basemetals:ore/coldiron").deepCopy()); + activate("basemetals", provider); + assertCurrentColdIronEnabled(); + assertTrue(ores(profile).getAsJsonObject("basemetals:ore/extra_coldiron").get("enabled").getAsBoolean()); + } + + @Test + void multipleLegacyPlacementsForOneOutputAreNotGuessedAway() { + ores(profile).add("basemetals:legacy/extra_coldiron", ores(profile).get("basemetals:legacy/coldiron_ore").deepCopy()); + assertCurrentColdIronEnabled(); + } + + @Test + void weightedMixedOutputsAndMetadataVariantsAreNotEquivalent() { + JsonObject old = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + JsonArray outputs = old.getAsJsonArray("outputs"); + JsonObject extra = outputs.get(0).getAsJsonObject().deepCopy(); + extra.addProperty("block", "basemetals:tin_ore"); + outputs.add(extra); + assertCurrentColdIronEnabled(); + outputs.remove(1); + old.addProperty("metadata", 1); + assertCurrentColdIronEnabled(); + } + + @Test + void differentDeepOutputsOrNamespacedPatternChannelsAreNotEquivalent() { + JsonObject old = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + old.addProperty("deep_output", "basemetals:tin_ore"); + assertCurrentColdIronEnabled(); + old.remove("deep_output"); + JsonObject placement = old.getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether"); + placement.remove("placement_channel"); + placement.add("pattern", JsonParser.parseString("{\"type\":\"anothermod:enrichment\"}")); + assertCurrentColdIronEnabled(); + } + + @Test + void emptyOrMalformedPlacementScopesAreNotEquivalent() { + JsonObject old = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + old.add("dimensions", new JsonObject()); + assertCurrentColdIronEnabled(); + } + + private void assertCurrentColdIronEnabled() { + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertTrue(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + ores(profile).remove("basemetals:ore/coldiron"); + profile.remove("providers"); + profile.remove("ore_providers"); + } + + private void assertSavedRules(JsonObject before) { + for (var entry : ores(before).entrySet()) { + JsonObject expected = entry.getValue().getAsJsonObject().deepCopy(); + JsonObject actual = ores(profile).getAsJsonObject(entry.getKey()).deepCopy(); + expected.remove("orphaned_provider"); + actual.remove("orphaned_provider"); + assertEquals(expected, actual, entry.getKey()); + } + } + + private void activate(String owner, JsonObject defaults) throws Exception { + var type = Class.forName(WorldgenIntegrationManager.class.getName() + "$ProviderDefinition"); + var constructor = type.getDeclaredConstructor(String.class, int.class, JsonObject.class); + constructor.setAccessible(true); + providers.put(owner, constructor.newInstance(owner, 1, defaults)); + } + + private static JsonObject ores(JsonObject root) { return root.getAsJsonObject("ores"); } + + private static JsonObject fixture(String name) throws Exception { + try (var input = RetainedProviderOreDefaultsTest.class.getResourceAsStream("/provider-merge/" + name)) { + assertNotNull(input); + try (var reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { + return JsonParser.parseReader(reader).getAsJsonObject(); + } + } + } +} diff --git a/src/test/resources/provider-merge/README.md b/src/test/resources/provider-merge/README.md new file mode 100644 index 00000000..c9e71ce7 --- /dev/null +++ b/src/test/resources/provider-merge/README.md @@ -0,0 +1,16 @@ +# Retained Provider Merge Fixtures + +These are structured JSON extracts, not generated worlds. They retain the ten +affected Base Metals rules and their original placement values. + +- `retained-basemetals-ores.json`: selected rules and the Base Metals manifest + from the released base* 1.10 fixture retained by the 1.12 upgrade tests. + Complete source-profile SHA-256: + `AF4A6124287D5B40F81BCC05A3B013B5C94F903A221F3C7B48FB6B0842E72197`. +- `current-basemetals-ores.json`: the corresponding rules from Base Metals' + 26.1.2 provider declaration. Complete source-declaration SHA-256: + `3963718AA0464BDB240DA5521C65EA1479AC46D34B0FF8EAD2675013C93B05D0`. + +The regression tests call the real provider merge, temporarily installing +only this provider and restoring its prior state afterwards. No Base Metals +classes, jars, or live world folders are needed to run them. diff --git a/src/test/resources/provider-merge/current-basemetals-ores.json b/src/test/resources/provider-merge/current-basemetals-ores.json new file mode 100644 index 00000000..227c590f --- /dev/null +++ b/src/test/resources/provider-merge/current-basemetals-ores.json @@ -0,0 +1,325 @@ +{ + "schema_version": 3, + "provider_modid": "basemetals", + "provider_revision": 1, + "ores": { + "basemetals:ore/coldiron": { + "block": "basemetals:coldiron_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimensions": { + "minecraft:the_nether": { + "enabled": true, + "min_y": 0, + "max_y": 127, + "frequency": 5, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "uniform", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_blocks": [ + "minecraft:netherrack" + ], + "host_tags": [ + "forge:netherrack" + ] + } + } + }, + "basemetals:ore/adamantine": { + "block": "basemetals:adamantine_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimensions": { + "minecraft:the_nether": { + "enabled": true, + "min_y": 0, + "max_y": 127, + "frequency": 2, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "uniform", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_blocks": [ + "minecraft:netherrack" + ], + "host_tags": [ + "forge:netherrack" + ] + } + } + }, + "basemetals:ore/starsteel": { + "block": "basemetals:starsteel_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimensions": { + "minecraft:the_end": { + "enabled": true, + "min_y": 0, + "max_y": 254, + "frequency": 5, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "uniform", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_blocks": [ + "minecraft:end_stone" + ], + "host_tags": [ + "forge:end_stones" + ] + } + } + }, + "basemetals:ore/tin": { + "block": "basemetals:tin_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 128, + "frequency": 10, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/lead": { + "block": "basemetals:lead_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 64, + "frequency": 5, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/zinc": { + "block": "basemetals:zinc_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 96, + "frequency": 5, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/silver": { + "block": "basemetals:silver_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 32, + "frequency": 4, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "bottom_triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/mercury": { + "block": "basemetals:mercury_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 32, + "frequency": 3, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "bottom_triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/nickel": { + "block": "basemetals:nickel_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -32, + "max_y": 96, + "frequency": 1, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + }, + "basemetals:ore/platinum": { + "block": "basemetals:platinum_ore", + "enabled": true, + "source_mod": "basemetals", + "retrogen": false, + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "enabled": true, + "min_y": -64, + "max_y": 16, + "frequency": 0.125, + "min_quantity": 4, + "max_quantity": 11, + "pattern": "vein", + "height_distribution": "bottom_triangle", + "discard_chance_on_air_exposure": 0, + "spread": 8, + "vertical_spread": 4, + "node_size": 4, + "length": 16, + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [ + "minecraft:stone_ore_replaceables", + "minecraft:deepslate_ore_replaceables" + ] + } + } + } + } +} diff --git a/src/test/resources/provider-merge/retained-basemetals-ores.json b/src/test/resources/provider-merge/retained-basemetals-ores.json new file mode 100644 index 00000000..9c35adff --- /dev/null +++ b/src/test/resources/provider-merge/retained-basemetals-ores.json @@ -0,0 +1,551 @@ +{ + "schema_version": 7, + "ores": { + "basemetals:legacy/coldiron_ore": { + "block": "basemetals:coldiron_ore", + "dimension_selectors": {}, + "dimensions": { + "minecraft:the_nether": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 5, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 127, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "enabled": true, + "material": "orespawn:coldiron", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:coldiron_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/adamantine_ore": { + "block": "basemetals:adamantine_ore", + "dimension_selectors": {}, + "dimensions": { + "minecraft:the_nether": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 2, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 127, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "enabled": true, + "material": "orespawn:adamantine", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:adamantine_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/starsteel_ore": { + "block": "basemetals:starsteel_ore", + "dimension_selectors": {}, + "dimensions": { + "minecraft:the_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 5, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 254, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "enabled": true, + "material": "orespawn:starsteel", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:starsteel_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/tin_ore": { + "block": "basemetals:tin_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 10, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 127, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:tin", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:tin_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/lead_ore": { + "block": "basemetals:lead_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 5, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 63, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:lead", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:lead_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/zinc_ore": { + "block": "basemetals:zinc_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 5, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 95, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:zinc", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:zinc_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/silver_ore": { + "block": "basemetals:silver_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 4, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 31, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:silver", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:silver_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/mercury_ore": { + "block": "basemetals:mercury_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 3, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 31, + "min_quantity": 4, + "min_y": 0, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:mercury", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:mercury_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/nickel_ore": { + "block": "basemetals:nickel_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 1, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 95, + "min_quantity": 4, + "min_y": 32, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:nickel", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:nickel_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + }, + "basemetals:legacy/platinum_ore": { + "block": "basemetals:platinum_ore", + "dimension_selectors": { + "orespawn:all_except_nether_end": { + "biome_dictionary": [], + "biome_ids": [], + "discard_chance_on_air_exposure": 0, + "enabled": true, + "excluded_biome_dictionary": [], + "excluded_biome_ids": [], + "frequency": 0.125, + "geomes": {}, + "height_distribution": "uniform", + "host_blocks": [ + "minecraft:stone", + "minecraft:netherrack", + "minecraft:end_stone" + ], + "host_families": [ + "sedimentary", + "metamorphic", + "igneous_intrusive", + "igneous_volcanic" + ], + "host_tags": [], + "max_quantity": 11, + "max_y": 31, + "min_quantity": 4, + "min_y": 1, + "node_size": 4, + "pattern": "default", + "placement_channel": "orespawn:standard", + "spread": 8, + "vertical_spread": 4 + } + }, + "dimensions": {}, + "enabled": true, + "material": "orespawn:platinum", + "native_generation": false, + "outputs": [ + { + "block": "basemetals:platinum_ore", + "max_y": 2048, + "min_y": -2048, + "weight": 100 + } + ], + "retrogen": false, + "source_provider": "basemetals", + "suppress_vanilla": false + } + }, + "providers": { + "basemetals": { + "known_biome_palettes": [], + "known_biome_rules": [], + "known_dimension_materials": [], + "known_fluid_deposits": [], + "known_geomes": [], + "known_ores": [ + "basemetals:legacy/adamantine_ore", + "basemetals:legacy/coldiron_ore", + "basemetals:legacy/copper_ore", + "basemetals:legacy/lead_ore", + "basemetals:legacy/mercury_ore", + "basemetals:legacy/nickel_ore", + "basemetals:legacy/platinum_ore", + "basemetals:legacy/silver_ore", + "basemetals:legacy/starsteel_ore", + "basemetals:legacy/tin_ore", + "basemetals:legacy/zinc_ore" + ], + "known_rocks": [], + "known_templates": [], + "known_terrain_dimensions": [], + "provider_revision": 1 + } + } +}