From dcaaa933299dd8b1ebc73e31c9e5acd36dd7957c Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Thu, 12 Nov 2026 16:08:34 +0000 Subject: [PATCH] Qualify retained provider settings for local 26.2 upgrades --- .github/workflows/ci.yml | 6 +- CHANGELOG.txt | 5 + README.md | 6 +- docs/MIGRATION.md | 33 ++ docs/VERSIONS.md | 2 +- gradle.properties | 2 +- gradle/release/artifacts.gradle | 10 +- .../RetainedProviderOreDefaults.java | 138 +++++ .../WorldgenIntegrationManager.java | 8 + .../worldgen/LegacyConfigMigrator.java | 2 +- .../LegacyMineralogyProfileMigration.java | 2 +- .../RetainedProviderOreDefaultsTest.java | 374 ++++++++++++ src/test/resources/provider-merge/README.md | 16 + .../current-basemetals-ores.json | 325 +++++++++++ .../retained-basemetals-ores.json | 551 ++++++++++++++++++ 15 files changed, 1467 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 f7cf5545..c3b85c8f 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.2602001.jar - build/libs/OreSpawn-4.1.0.2602001-sources.jar - build/libs/OreSpawn-4.1.0.2602001-javadoc.jar + build/libs/OreSpawn-4.1.1.2602001.jar + build/libs/OreSpawn-4.1.1.2602001-sources.jar + build/libs/OreSpawn-4.1.1.2602001-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8508dbdd..8190bf6f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,8 @@ +Version 4.1.1.2602001 (local candidate) + +* Keep saved ore settings when a provider renames a recognised rule. Add the + matching modern rule as a disabled alternative rather than another ore pass. + Version 4.1.0.2602001 * Leave imported vanilla ores inactive while Manage Vanilla Ores is off. diff --git a/README.md b/README.md index 0b43da10..87dbe438 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,14 @@ 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. +Recognised provider rule renames keep the saved ore settings and add the modern +rule as a disabled, editable alternative, avoiding an extra generation pass. +See [retained provider rules](docs/MIGRATION.md#retained-provider-rules). + This is not the unrelated mod that adds mobs and dimensions under the same name. -This branch builds target-qualified version `4.1.0.2602001`: the OreSpawn 4.1 +This branch builds target-qualified version `4.1.1.2602001`: the OreSpawn 4.1 feature set for Minecraft 26.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..68706ab6 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -1,5 +1,38 @@ # Migration +## Retained Provider Rules + +An older profile can keep an ore rule after its provider renames that rule. +OreSpawn must not add a second generation pass just because the ID changed. +The merge recognises OreSpawn's imported `orespawn:legacy//...` +rules and the following exact Base Metals replacements: + +- `basemetals:legacy/_ore` to `basemetals:ore/` for + coldiron, adamantine, starsteel, tin, lead, zinc, silver, mercury, nickel + and platinum. + +A match needs one saved rule and one provider candidate with the same single +output, default metadata, and dimension/selector and effective placement-channel +footprint. Conflicting provider ownership, mixed outputs, different deep outputs +and ambiguous matches are not treated as replacements. A generic mod's +`legacy/` name is not enough evidence. + +For a recognised match, the missing modern rule is added as a complete, +disabled alternative. The saved rule keeps its rates, hosts, heights, pattern, +enablement and source choices, including disabled and zero-rate settings. +Fresh worlds still use the provider's normal defaults. + +You can enable the alternative in the ore settings. Later merges retain that +choice, existing modern rules and recorded deletions. A recorded deletion of +one of the historical Base Metals IDs also leaves its mapped replacement +disabled. Deleted imports without an exact mapping are not guessed from their +names. + +This repair does not automatically remove an already-saved historical +Base Metals/modern pair: both may have been selected deliberately. Review +ambiguous or previously affected profiles before changing their choices. +It does not alter generated chunks, public APIs or saved formats. + ## Provider-Aware OS3 Imports When an OreSpawn 2/3 file is named for an installed provider, the migrator now diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 2cd67018..56564d12 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -63,7 +63,7 @@ Examples: | 1.21.1 | Forge | `121011` | `4.0.16.121011` | | 1.21.11 | Forge | `121111` | `4.0.16.121111` | | 26.1.2 | Forge | `2601021` | `4.0.16.2601021` | -| 26.2 | Forge | `2602001` | `4.1.0.2602001` | +| 26.2 | Forge | `2602001` | `4.1.1.2602001` | | 26.2 | NeoForge | `2602002` | `4.0.6.2602002` | Historical MMD releases may also have four numeric components but may have used diff --git a/gradle.properties b/gradle.properties index 9d5495e3..09534941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -43,7 +43,7 @@ mapping_version=26.2 mod_id=orespawn mod_name=MMD OreSpawn mod_license=LGPL-2.1 -mod_version=4.1.0.2602001 +mod_version=4.1.1.2602001 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 d8d7f828..a69ca23b 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.2602001' + if (project.mod_version != '4.1.1.2602001' || project.mod_group != expectedMavenGroup || project.minecraft_version != '26.2' || project.forge_version != '65.1.0' @@ -93,9 +93,9 @@ tasks.register('verifyReleaseConfiguration') { throw new GradleException('Unexpected dispatcher or Java target metadata') } List expectedPublicArtifacts = [ - 'OreSpawn-4.1.0.2602001.jar', - 'OreSpawn-4.1.0.2602001-sources.jar', - 'OreSpawn-4.1.0.2602001-javadoc.jar' + 'OreSpawn-4.1.1.2602001.jar', + 'OreSpawn-4.1.1.2602001-sources.jar', + 'OreSpawn-4.1.1.2602001-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.2602001')) { + if (!file(path).getText('UTF-8').contains('4.1.1.2602001')) { 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..333ca533 --- /dev/null +++ b/src/main/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaults.java @@ -0,0 +1,138 @@ +package zone.moddev.mc.orespawn.integration; + +import java.util.HashSet; +import java.util.Arrays; +import java.util.Map; +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 (Map.Entry entry : defaults.entrySet()) { + if (equivalentOutputAndScope(entry.getKey(), entry.getValue(), output, scope)) replacements++; + } + if (replacements != 1) return null; + + String retained = null; + for (Map.Entry 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 : parseIdentifier(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 : Arrays.asList("dimensions", "dimension_selectors")) { + JsonElement table = rule.get(kind); + if (table == null) continue; + if (!table.isJsonObject()) return null; + for (Map.Entry entry : table.getAsJsonObject().entrySet()) { + if (parseIdentifier(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 = parseIdentifier(legacy); + return type == null ? "orespawn:standard" : type.toString(); + } + + private static Identifier parseIdentifier(String value) { + try { return value == null || value.trim().isEmpty() ? null : Identifier.parse(value); } + catch (RuntimeException ignored) { return null; } + } + + private static Identifier identifier(JsonElement value) { + String name = text(value); + return name == null ? null : parseIdentifier(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..96285c0f 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 saved 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 7dbfca78..94bb647b 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.2602001 Upgrade Report"); + lines.add("OreSpawn 4.1.1.2602001 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 51182792..077a84c1 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.2602001 Upgrade Report"); + lines.add("OreSpawn 4.1.1.2602001 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..8df2edbe --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/integration/RetainedProviderOreDefaultsTest.java @@ -0,0 +1,374 @@ +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.JsonElement; +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 { + java.lang.reflect.Field 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 = new JsonParser().parse("{\"mode\":\"keep_separate\",\"outputs\":{\"basemetals:tin_ore\":1.0},\n\t\t\t\t \"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 = copy(profile); + JsonObject providerBefore = copy(provider); + 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 = copy(ores(provider).getAsJsonObject("basemetals:ore/" + material)); + 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 = copy(profile); + 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 = copy(profile); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(chosen, profile); + assertTrue(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + @Test + void anExistingExplicitModernRuleIsNeverChanged() { + JsonObject chosen = copy(ores(provider).getAsJsonObject("basemetals:ore/coldiron")); + chosen.getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether").addProperty("frequency", 0.125); + ores(profile).add("basemetals:ore/coldiron", copy(chosen)); + 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 = copy(ores(provider).getAsJsonObject("basemetals:ore/coldiron")); + row.addProperty("source_provider", "anothermod"); + String id = "anothermod:custom/coldiron_" + index; + custom.put(id, copy(row)); + 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", copy(ores(provider).get("basemetals:ore/coldiron"))); + 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 (Map.Entry entry : ores(provider).entrySet()) { + JsonObject expected = copy(entry.getValue().getAsJsonObject()); + 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 = copy(ores(provider).getAsJsonObject("basemetals:ore/coldiron")); + 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 = copy(current); + 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", copy(ores(provider).get("basemetals:ore/coldiron"))); + 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", copy(ores(profile).get("basemetals:legacy/coldiron_ore"))); + assertCurrentColdIronEnabled(); + } + + @Test + void weightedMixedOutputsAndMetadataVariantsAreNotEquivalent() { + JsonObject old = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore"); + JsonArray outputs = old.getAsJsonArray("outputs"); + JsonObject extra = copy(outputs.get(0).getAsJsonObject()); + extra.addProperty("block", "basemetals:tin_ore"); + outputs.add(extra); + assertCurrentColdIronEnabled(); + JsonArray singleton = new JsonArray(); + singleton.add(outputs.get(0)); + old.add("outputs", singleton); + 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", new JsonParser().parse("{\"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 (Map.Entry entry : ores(before).entrySet()) { + JsonObject expected = copy(entry.getValue().getAsJsonObject()); + JsonObject actual = copy(ores(profile).getAsJsonObject(entry.getKey())); + expected.remove("orphaned_provider"); + actual.remove("orphaned_provider"); + assertEquals(expected, actual, entry.getKey()); + } + } + + private void activate(String owner, JsonObject defaults) throws Exception { + Class type = Class.forName(WorldgenIntegrationManager.class.getName() + "$ProviderDefinition"); + java.lang.reflect.Constructor constructor = type.getDeclaredConstructor(String.class, int.class, JsonObject.class); + constructor.setAccessible(true); + providers.put(owner, constructor.newInstance(owner, 1, defaults)); + } + + @ParameterizedTest + @ValueSource(strings = {"default", "vein", "normal_cloud", "precision", "clusters", "underfluids", "custom"}) + void matchingUsesThisTargetsEffectivePatternChannels(String pattern) throws Exception { + JsonObject current = ores(provider).getAsJsonObject("basemetals:ore/coldiron") + .getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether"); + JsonObject saved = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore") + .getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether"); + saved.remove("placement_channel"); + JsonObject shape = new JsonObject(); + shape.addProperty("type", "orespawn:" + pattern); + saved.add("pattern", shape); + Class policies = Class.forName("zone.moddev.mc.orespawn.worldgen.OreSourcePolicies"); + java.lang.reflect.Method channel = policies.getDeclaredMethod("placementChannel", JsonObject.class); + channel.setAccessible(true); + boolean sameChannel = channel.invoke(null, current).equals(channel.invoke(null, saved)); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertEquals(!sameChannel, ores(profile).getAsJsonObject("basemetals:ore/coldiron") + .get("enabled").getAsBoolean(), "A saved explicit or custom channel must not be collapsed"); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void blankChannelsUseTheTargetsDefaultChannel(String blank) throws Exception { + JsonObject saved = ores(profile).getAsJsonObject("basemetals:legacy/coldiron_ore") + .getAsJsonObject("dimensions").getAsJsonObject("minecraft:the_nether"); + saved.addProperty("placement_channel", blank); + WorldgenIntegrationManager.mergeProviderDefinitions(profile); + assertFalse(ores(profile).getAsJsonObject("basemetals:ore/coldiron").get("enabled").getAsBoolean()); + } + + private static JsonObject copy(JsonObject value) { + return new JsonParser().parse(value.toString()).getAsJsonObject(); + } + + private static JsonElement copy(JsonElement value) { + return new JsonParser().parse(value.toString()); + } + + private static JsonObject ores(JsonObject root) { return root.getAsJsonObject("ores"); } + + private static JsonObject fixture(String name) throws Exception { + try (java.io.InputStream input = RetainedProviderOreDefaultsTest.class.getResourceAsStream("/provider-merge/" + name)) { + assertNotNull(input); + try (InputStreamReader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { + return new JsonParser().parse(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 + } + } +}