diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43f8edf6..e9d62ca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,9 +126,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/OreSpawn-4.1.3.112021.jar - build/libs/OreSpawn-4.1.3.112021-sources.jar - build/libs/OreSpawn-4.1.3.112021-javadoc.jar + build/libs/OreSpawn-4.1.6.112021.jar + build/libs/OreSpawn-4.1.6.112021-sources.jar + build/libs/OreSpawn-4.1.6.112021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9719e393..8c0fe400 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,15 @@ +Version 4.1.6.112021 + +* Let Java providers select an exact ore host, such as plain stone without + granite. Whole-block selectors still work as before. +* Keep imported ore rules and saved choices when moving a world from 1.10.2 + or 1.11.2. Back up the profile before removing an untouched duplicate and + leave edited rules for review. +* Choose sulfur and lithium defaults after all mods have loaded so Base + Electric can share the same veins. Existing worlds keep their choices. +* Use the correct Austrian German locale and give Pirate Speak its own menu + and guide text. + Version 4.1.3.112021 * Apply hybrid and replacement background limits to imported built-in ore diff --git a/README.md b/README.md index 81b073f0..212bd33f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It gives mods and modpacks one place to configure ores, deposit shapes, optional rock strata and geomes, provider-owned underground fluid deposits, biome palettes and world materials, flat bedrock, and bounded ore retrogen. -This branch builds target-qualified version `4.1.3.112021`: the OreSpawn 4.1.3 +This branch builds target-qualified version `4.1.6.112021`: the OreSpawn 4.1.6 feature set for Minecraft 1.12.2 and Forge. It adds stable generation identity, non-loading geology sampling, per-resource background control, existing-world provider merge policy, an Ore Sources arbiter for equivalent provider ores, a diff --git a/docs/API.md b/docs/API.md index 2dcdad3d..9e321e51 100644 --- a/docs/API.md +++ b/docs/API.md @@ -98,6 +98,12 @@ exclusions. These methods work on both explicit `.dimension(...)` rules and `.dimensionSelector(...)` fallbacks; built definitions and their returned filter sets are immutable. +Use `.hostState(block, metadata)` to select one legacy ore host variant, or +`.hostState(block, metadata, weight)` to give it a replacement weight. +For example, `.hostState(new ResourceLocation("minecraft", "stone"), 0)` +accepts plain stone without accepting granite or diorite. The existing +`.hostBlock(...)` method still accepts every variant of a block. + Create one `BiomeRegistrar` during normal mod construction. It attaches to the calling mod's event bus and defers biome factories until Forge's biome registry event. `OreSpawnBiomes.copyAndRegister` clones a known biome without adding a diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index c0f61665..57c68aac 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -44,6 +44,10 @@ world. Restart after editing JSON by hand. | `flat_bedrock` | object | Opt-in flat bedrock controls | | `worldgen_aliases` | ID-to-ID object | Replacement output aliases resolved while baking | +Forge 1.12.2 applies the same gold settings to the normal and extra Mesa +gold passes. With vanilla ore management off and suppression disabled, both +passes keep their normal generation. + ## Enumerated Values | Setting | Accepted values | diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 439a78ee..e0e1ef1b 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -69,6 +69,16 @@ deterministic machine-readable detail. ## Provider-Aware OS3 Imports +If you move a world with an OreSpawn 1 import already saved by OreSpawn 4 +on 1.10.2 or 1.11.2, its rule ID and settings are kept even without the +converted provider file. If an older build added a +second rule, OreSpawn removes it only when the saved rule still matches the +provider default and no custom ore-source selection depends on it. The +original profile is backed up before that repair. Edited or ambiguous +overlaps are left in place and listed under `legacy_ore_review` in the +provider's saved manifest and reported in the log. This does not change +ore blocks in chunks already generated. + When an OreSpawn 2/3 file is named for an installed provider, the migrator now compares each converted primary output with that provider's ore declarations. A unique output match is written under the provider's stable rule ID, allowing diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 42d00337..39c00834 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -120,11 +120,12 @@ unambiguous. NetherMetals and EndMetals stay in their respective dimension domains. DenseMetals is enrichment, not an interchangeable output, and BaseSciences has no ordinary ore-source role. -Reviewed fresh-world conflicts start Balanced with all eligible output blocks. -Mineralogy, then BaseMinerals, then ElectricAdvantage is the initial placement -and Single-mode priority for sulfur; BaseMinerals, then ElectricAdvantage is the -corresponding lithium priority. No other conflict is automatically consolidated -without an explicit catalog row. Exact Ore Dictionary `oreX` inference recognizes the curated +Once mods have finished registering their ores, reviewed fresh-world conflicts +start Balanced with all eligible output blocks. Sulfur prefers Mineralogy, +then Base Minerals, then Base Electric (`baseelectric` or the older +`electricadvantage` ID) for placement. Lithium prefers Base Minerals, then +Base Electric. Existing choices are kept. Other conflicts need an explicit +catalog entry for automatic consolidation. Exact Ore Dictionary `oreX` inference recognizes the curated sulfur/sulphur and aluminum/aluminium aliases but deliberately keeps Niter and Saltpeter distinct. Multiple unrelated entries are marked for review rather than fuzzy-matched from block names. diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 67af7977..43fe17b8 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -52,7 +52,7 @@ Examples: | Minecraft | Loader | Target | Example full OreSpawn version | | --- | --- | ---: | --- | | 1.10.2 | Forge | `110021` | `4.0.6.110021` | -| 1.12.2 | Forge | `112021` | `4.1.3.112021` | +| 1.12.2 | Forge | `112021` | `4.1.6.112021` | | 1.13.2 | Forge | `113021` | `4.0.6.113021` | | 1.20.6 | Forge | `120061` | `4.0.6.120061` | | 1.21.11 | Forge | `121111` | `4.0.6.121111` | diff --git a/gradle.properties b/gradle.properties index 109768fd..83bafbe0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,7 +30,7 @@ mod_vendor=Minecraft Mod Development mod_license=LGPL-2.1 mod_license_name=GNU Lesser General Public License, Version 2.1 mod_license_url=https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt -mod_version=4.1.3.112021 +mod_version=4.1.6.112021 mod_group=zone.moddev.mc.orespawn mod_authors=SkyBlade1978, dshadowwolf, the MMD Team mod_description=Configurable, provider-driven terrain, ore, and deposit generation. diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index c338de23..84f0d601 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -81,7 +81,7 @@ tasks.register('verifyReleaseConfiguration') { description = 'Validates the target-qualified release, API, schemas, reports, and publishing identity.' doLast { - if (project.mod_version != '4.1.3.112021' + if (project.mod_version != '4.1.6.112021' || project.mod_group != expectedMavenGroup) { throw new GradleException("Unexpected OreSpawn release version: ${project.mod_version}") } @@ -108,8 +108,8 @@ tasks.register('verifyReleaseConfiguration') { 'src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java', 'README.md', 'CHANGELOG.txt' ].each { path -> - if (!file(path).getText('UTF-8').contains('4.1.3.112021')) { - throw new GradleException("Authoritative release location does not contain 4.1.3.112021: ${path}") + if (!file(path).getText('UTF-8').contains('4.1.6.112021')) { + throw new GradleException("Authoritative release location does not contain 4.1.6.112021: ${path}") } } if (!file('docs/API.md').getText('UTF-8').contains('orespawn@[4.0.6,5.0.0)')) { diff --git a/gradle/verification/packaged-forge.gradle b/gradle/verification/packaged-forge.gradle index bc993216..65110be3 100644 --- a/gradle/verification/packaged-forge.gradle +++ b/gradle/verification/packaged-forge.gradle @@ -7,17 +7,6 @@ def packagedSurfaceIntegrationTestModJar = tasks.named('renameSurfaceIntegration // Release qualification also exercises the reobfuscated jar through Forge's // packaged launcher. The official server jar and its library tree are // machine-local inputs and are never copied into source control or artifacts. -def forge14UniversalRuntime = { - File runtime = new File(gradle.gradleUserHomeDir, - "caches/forge_gradle/maven_downloader/net/minecraftforge/forge/" + - "${minecraft_version}-${forge_version}/" + - "forge-${minecraft_version}-${forge_version}-universal.jar") - if (!runtime.isFile()) { - throw new GradleException("Forge 14 universal runtime is missing: ${runtime}") - } - runtime -} - def packagedForgeRunDirectory = file("${buildDir}/packaged-forge-runtime-run") def packagedMinecraftServer = { if (!project.hasProperty('packagedMinecraftServerJar')) { @@ -29,6 +18,19 @@ def packagedMinecraftServer = { } server } +def forge14UniversalRuntime = { + String name = "forge-${minecraft_version}-${forge_version}-universal.jar" + File installed = new File(packagedMinecraftServer().parentFile, name) + if (installed.isFile()) return installed + // Some older installations cached the universal jar here. + File cached = new File(gradle.gradleUserHomeDir, + "caches/forge_gradle/maven_downloader/net/minecraftforge/forge/" + + "${minecraft_version}-${forge_version}/${name}") + if (!cached.isFile()) { + throw new GradleException("Forge 14 universal runtime is missing: ${installed} or ${cached}") + } + cached +} def packagedForgeLibraries = { if (!project.hasProperty('packagedForgeLibrariesRoot')) { throw new GradleException('packagedForgeLibrariesRoot is required for packagedForgeRuntimeTest') diff --git a/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/client/ClientProbeTestMod.java b/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/client/ClientProbeTestMod.java index 17dcea81..f5a67df1 100644 --- a/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/client/ClientProbeTestMod.java +++ b/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/client/ClientProbeTestMod.java @@ -113,9 +113,20 @@ public void onClientTick(TickEvent.ClientTickEvent event) { case 2: if (minecraft.currentScreen instanceof OreSpawnWorldSettingsScreen && editorFrames >= 2) { worldSettingsOpened = true; + validateLanguage(minecraft); validateCaptions((OreSpawnWorldSettingsScreen) minecraft.currentScreen); validateExpectedDirectory(minecraft.currentScreen); validateLongEditorRoundTrip(minecraft, minecraft.currentScreen); + if (Boolean.getBoolean("clientprobe.localeOnly")) { + Properties values = new Properties(); + values.setProperty("language", minecraft.getLanguageManager().getCurrentLanguage().getLanguageCode()); + try (FileOutputStream output = new FileOutputStream("client-locale-pass.properties")) { + values.store(output, "OreSpawn localized editor check"); + } + minecraft.shutdown(); + nextState(10); + break; + } nextState(3); } break; @@ -317,7 +328,7 @@ private void validateOreSourcesSingleScreen(GuiScreen parent, GeologyEditorSessi if (widget instanceof CompactScrollList && widget.x < right) groupList = true; if (widget instanceof CompactScrollList && widget.x >= right) outputList = true; if (widget instanceof TextFieldWidget && widget.x < right) inlineAdministration = true; - if ("Back".equals(caption)) back = true; + if (I18n.format("button.orespawn.back").equals(caption)) back = true; if (I18n.format("button.orespawn.show_all").equals(caption)) { showAll = true; showAllWidth = widget.width; @@ -633,13 +644,25 @@ private Button nextNavigationButton(OreSpawnWorldSettingsScreen root) { Button button = (Button) widget; String caption = TextFormatting.getTextWithoutFormattingCodes(button.getMessage()); if (!attemptedButtons.add(caption)) continue; - String lower = caption.toLowerCase(java.util.Locale.ROOT); - if (lower.equals("done") || lower.equals("cancel") || lower.contains("recommended")) continue; + if (I18n.format("gui.done").equals(caption) || I18n.format("gui.cancel").equals(caption) + || I18n.format("button.orespawn.recommended").equals(caption)) continue; return button; } return null; } + private static void validateLanguage(Minecraft minecraft) { + String expected = System.getProperty("clientprobe.language", ""); + if (expected.isEmpty()) return; + String active = minecraft.getLanguageManager().getCurrentLanguage().getLanguageCode(); + String caption = I18n.format("button.orespawn.recommended"); + if (!expected.equals(active) || !caption.equals(System.getProperty("clientprobe.recommendedCaption"))) { + throw new IllegalStateException("Unexpected locale or caption: " + active + " / " + caption); + } + org.apache.logging.log4j.LogManager.getLogger(MODID) + .info("Client locale {} loaded caption '{}'", active, caption); + } + private static void validateCaptions(OreSpawnScreen screen) { for (GuiButton widget : screen.buttons) { if (widget instanceof CogButton || widget instanceof CompactScrollList) continue; @@ -757,7 +780,7 @@ private static void pressDone(OreSpawnScreen screen) { for (GuiButton widget : screen.buttons) { if (!(widget instanceof Button)) continue; String caption = TextFormatting.getTextWithoutFormattingCodes(((Button) widget).getMessage()); - if ("done".equalsIgnoreCase(caption)) { + if (I18n.format("gui.done").equals(caption)) { ((Button) widget).press(); return; } diff --git a/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java b/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java index c58b71c4..0d049eff 100644 --- a/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java +++ b/src/main/java/com/mcmoddev/orespawn/compat/LegacyOs3Bridge.java @@ -853,7 +853,7 @@ private static void writeHumanUpgradeReport(Path destination) throws IOException } } List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.3.112021 Upgrade Report"); + lines.add("OreSpawn 4.1.6.112021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Legacy OreSpawn configuration was consumed and translated for OS4."); diff --git a/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java b/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java index 938a5e14..5395ab25 100644 --- a/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java +++ b/src/main/java/zone/moddev/mc/orespawn/OreSpawn.java @@ -52,7 +52,7 @@ public class OreSpawn { public static final String MODID = "orespawn"; public static final String NAME = "OreSpawn"; - public static final String VERSION = "4.1.3.112021"; + public static final String VERSION = "4.1.6.112021"; private static final Logger LOGGER = LogManager.getLogger(); @@ -86,7 +86,7 @@ public void preInit(FMLPreInitializationEvent event) { @EventHandler public void init(FMLInitializationEvent event) { WorldgenIntegrationManager.initialize(); - GeomeConfig.bake(); + GeomeConfig.bakeBeforeLoadComplete(); logGeomeSampler(); DocumentationExporter.exportBundledGuide(); StoneReplacer.registerConfiguredFeature(); @@ -105,7 +105,7 @@ public void processInterMod(FMLInterModComms.IMCEvent event) { public void postInit(FMLPostInitializationEvent event) { WorldgenIntegrationManager.processImcMessages(); WorldgenIntegrationManager.freeze(); - GeomeConfig.bake(); + GeomeConfig.bakeBeforeLoadComplete(); refreshGenerationConfig(); WorldgenIntegrationManager.markFeatureReady(); } diff --git a/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java b/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java index e5e648a7..5e645e83 100644 --- a/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java +++ b/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java @@ -565,6 +565,7 @@ public static final class OreDimensionDefinition implements JsonDefinition { private final Set hostFamilies; private final Map geomes; private final Set hostBlocks; + private final Map> hostStates; private final Set hostTags; private final Set biomeIds; private final Set excludedBiomeIds; @@ -596,6 +597,9 @@ private OreDimensionDefinition(Builder builder) { hostFamilies = Collections.unmodifiableSet(new LinkedHashSet<>(builder.hostFamilies)); geomes = immutableMap(builder.geomes); hostBlocks = immutableSet(builder.hostBlocks); + Map> states = new LinkedHashMap<>(); + builder.hostStates.forEach((block, variants) -> states.put(block, immutableMap(variants))); + hostStates = immutableMap(states); hostTags = immutableSet(builder.hostTags); biomeIds = immutableSet(builder.biomeIds); excludedBiomeIds = immutableSet(builder.excludedBiomeIds); @@ -630,6 +634,8 @@ private OreDimensionDefinition(Builder builder) { public Set hostFamilies() { return hostFamilies; } public Map geomes() { return geomes; } public Set hostBlocks() { return hostBlocks; } + /** Exact legacy variants, separate from whole-block selectors. */ + public Map> hostStates() { return hostStates; } public Set hostTags() { return hostTags; } public Set biomeIds() { return biomeIds; } public Set excludedBiomeIds() { return excludedBiomeIds; } @@ -678,7 +684,15 @@ public JsonObject toJson() { for (GeologyFamily family : hostFamilies) { families.add(family.configName()); } json.add("host_families", families); json.add("geomes", weights(geomes)); - json.add("host_blocks", weightedIds(hostBlocks, hostBlockWeights, "block")); + JsonArray hosts = weightedIds(hostBlocks, hostBlockWeights, "block"); + hostStates.forEach((block, variants) -> variants.forEach((metadata, weight) -> { + JsonObject state = new JsonObject(); + state.addProperty("block", block.toString()); + state.addProperty("metadata", metadata); + if (weight != 1.0D) state.addProperty("weight", weight); + hosts.add(state); + })); + json.add("host_blocks", hosts); json.add("host_tags", weightedIds(hostTags, hostTagWeights, "tag")); json.add("biome_ids", ids(biomeIds)); json.add("excluded_biome_ids", ids(excludedBiomeIds)); @@ -710,6 +724,7 @@ public static final class Builder { private final Set hostFamilies = new LinkedHashSet<>(); private final Map geomes = new LinkedHashMap<>(); private final Set hostBlocks = new LinkedHashSet<>(); + private final Map> hostStates = new LinkedHashMap<>(); private final Set hostTags = new LinkedHashSet<>(); private final Set biomeIds = new LinkedHashSet<>(); private final Set excludedBiomeIds = new LinkedHashSet<>(); @@ -750,6 +765,25 @@ public Builder placementChannel(ResourceLocation value) { public Builder hostFamily(GeologyFamily value) { hostFamilies.add(value); return this; } public Builder geomeWeight(ResourceLocation geome, double value) { geomes.put(geome, value); return this; } public Builder hostBlock(ResourceLocation value) { hostBlocks.add(value); return this; } + + /** Selects one legacy block variant, such as plain stone (metadata zero). */ + public Builder hostState(ResourceLocation block, int metadata) { + return hostState(block, metadata, 1.0D); + } + + /** Adds an exact variant with the same replacement weight as hostBlock. */ + public Builder hostState(ResourceLocation block, int metadata, double weight) { + Objects.requireNonNull(block, "block"); + if (metadata < 0 || metadata > 15) { + throw new IllegalArgumentException("Legacy host metadata must be within 0..15"); + } + + double validatedWeight = replacementWeight(weight); + hostStates.computeIfAbsent(block, ignored -> new LinkedHashMap<>()) + .put(metadata, validatedWeight); + return this; + } + public Builder hostTag(ResourceLocation value) { hostTags.add(value); return this; } public Builder biome(ResourceLocation value) { biomeIds.add(value); return this; } public Builder excludeBiome(ResourceLocation value) { excludedBiomeIds.add(value); return this; } @@ -790,7 +824,8 @@ public OreDimensionDefinition build() { || nodeSize < 1 || nodeSize > 32) { throw new IllegalStateException("Invalid ore placement values for " + dimension); } - if (enabled && hostFamilies.isEmpty() && hostBlocks.isEmpty() && hostTags.isEmpty()) { + if (enabled && hostFamilies.isEmpty() && hostBlocks.isEmpty() + && hostStates.isEmpty() && hostTags.isEmpty()) { throw new IllegalStateException("Enabled ore dimension has no hosts: " + dimension); } return new OreDimensionDefinition(this); diff --git a/src/main/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacements.java b/src/main/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacements.java new file mode 100644 index 00000000..52166dcb --- /dev/null +++ b/src/main/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacements.java @@ -0,0 +1,215 @@ +package zone.moddev.mc.orespawn.integration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import zone.moddev.mc.orespawn.util.JsonCopies; + +/** Keeps a saved OS1 rule in charge when its provider starts supplying a native default. */ +final class LegacyOreReplacements { + private static final Logger LOGGER = LogManager.getLogger(); + private static final String REPLACEMENTS = "legacy_ore_replacements"; + private static final String REVIEW = "legacy_ore_review"; + + static final class Result { + final Set suppressed = new HashSet<>(); + boolean changed; + } + + private LegacyOreReplacements() {} + + static Result reconcile(JsonObject profile, String provider, JsonObject ores, JsonObject defaults, + JsonObject active, JsonObject manifest, Set known) { + Result result = new Result(); + JsonObject previous = object(manifest, REPLACEMENTS); + JsonObject replacements = JsonCopies.copy(previous); + JsonObject review = new JsonObject(); + Map> imports = new HashMap<>(); + for (String id : known) { + JsonObject ore = object(ores, id); + String output = output(ore); + if (output != null && isOs1Import(provider, id, ore)) { + imports.computeIfAbsent(output, ignored -> new ArrayList<>()).add(id); + } + } + if (imports.isEmpty() && previous.entrySet().isEmpty() && !manifest.has(REVIEW)) return result; + + Map countByOutput = new HashMap<>(); + for (Map.Entry entry : defaults.entrySet()) { + JsonObject ore = object(defaults, entry.getKey()); + if (!isOs1Import(provider, entry.getKey(), ore)) { + String output = output(ore); + if (output != null) countByOutput.merge(output, 1, Integer::sum); + } + } + for (Map.Entry entry : defaults.entrySet()) { + String id = entry.getKey(); + JsonObject nativeOre = object(defaults, id); + if (isOs1Import(provider, id, nativeOre)) continue; + String remembered = text(previous, id); + String output = output(nativeOre); + List matches = imports.getOrDefault(output, Collections.emptyList()); + if (remembered.isEmpty() && matches.isEmpty()) continue; + + if (remembered.isEmpty() && (matches.size() != 1 || countByOutput.getOrDefault(output, 0) != 1)) { + flag(review, manifest, provider, id, "ambiguous_import"); + result.suppressed.add(id); + continue; + } + String legacyId = remembered.isEmpty() ? matches.get(0) : remembered; + if (id.equals(legacyId) || !known.contains(legacyId)) continue; + if (active.has(id) && !entry.getValue().equals(active.get(id))) { + flag(review, manifest, provider, id, "provider_override_differs"); + continue; + } + if (ores.has(id)) { + // Never undo an edited rule or a selection that depends on it. + JsonObject expected = JsonCopies.copy(nativeOre); + expected.addProperty("source_provider", provider); + JsonObject saved = JsonCopies.copy(object(ores, id)); + saved.remove("orphaned_provider"); + // Ore Sources can add empty scope objects while reading a rule. + removeEmptyScopes(expected); + removeEmptyScopes(saved); + List disposablePolicies = stockPolicyReferences(profile, id, legacyId); + if (!known.contains(id) || !ores.has(legacyId) || !ores.get(id).isJsonObject() + || !expected.equals(saved) || disposablePolicies == null) { + flag(review, manifest, provider, id, "saved_rule_or_policy_differs"); + continue; + } + ores.remove(id); + JsonObject policies = object(profile, "ore_source_policies"); + for (String key : disposablePolicies) policies.remove(key); + result.changed = true; + LOGGER.info("Kept imported ore rule {} and removed untouched duplicate {}", legacyId, id); + } + replacements.addProperty(id, legacyId); + result.suppressed.add(id); + // The provider still supplies a counterpart for the imported rule. + JsonObject imported = object(ores, legacyId); + if (imported.has("orphaned_provider")) { + imported.remove("orphaned_provider"); + result.changed = true; + } + } + result.changed |= update(manifest, REPLACEMENTS, replacements); + result.changed |= update(manifest, REVIEW, review); + return result; + } + + private static void removeEmptyScopes(JsonObject rule) { + for (String key : new String[] { "dimensions", "dimension_selectors" }) { + JsonElement scope = rule.get(key); + if (scope != null && scope.isJsonObject() && scope.getAsJsonObject().entrySet().isEmpty()) { + rule.remove(key); + } + } + } + + static boolean retains(JsonObject manifest, String id, Set currentDefaults) { + JsonObject replacements = object(manifest, REPLACEMENTS); + for (Map.Entry entry : replacements.entrySet()) { + if (currentDefaults.contains(entry.getKey()) && id.equals(text(replacements, entry.getKey()))) { + return true; + } + } + return false; + } + + private static boolean isOs1Import(String provider, String id, JsonObject ore) { + String block = text(ore, "block"); + if (!block.startsWith(provider + ":") || !provider.equals(text(ore, "source_mod")) + || !"false".equals(text(ore, "native_generation"))) return false; + if ("os1".equals(text(ore, "legacy_import"))) return true; + // Older OS1 imports used the namespaced output plus an ordinal. OS3 and + // authored rules do not qualify by block name alone. + String prefix = provider + ":legacy/" + block.replace(':', '_'); + return provider.equals(text(ore, "source_provider")) && id.startsWith(prefix) + && id.substring(prefix.length()).matches("_[0-9]+"); + } + + private static String output(JsonObject ore) { + String block = text(ore, "block"); + String variant = metadata(ore); + if (block.isEmpty() || variant == null) return null; + if (ore.has("outputs")) { + JsonElement value = ore.get("outputs"); + if (!value.isJsonArray()) return null; + JsonArray outputs = value.getAsJsonArray(); + if (outputs.size() != 1 || !outputs.get(0).isJsonObject()) return null; + JsonObject only = outputs.get(0).getAsJsonObject(); + if (!block.equals(text(only, "block")) || !variant.equals(metadata(only))) return null; + } + return block + "#" + variant; + } + + private static String metadata(JsonObject ore) { + if (!ore.has("metadata")) return "0"; + String meta = text(ore, "metadata"); + return meta.matches("[0-9]|1[0-5]") ? meta : null; + } + + /** Only remove the fallback policy generated for a lone native rule. */ + private static List stockPolicyReferences(JsonObject profile, String nativeId, String legacyId) { + List disposable = new ArrayList<>(); + JsonObject imported = object(object(profile, "ores"), legacyId); + for (Map.Entry entry : object(profile, "ore_source_policies").entrySet()) { + if (!entry.getValue().isJsonObject()) continue; + JsonObject policy = entry.getValue().getAsJsonObject(); + JsonObject outputs = object(policy, "outputs"); + JsonObject placements = object(policy, "placement_sources"); + if (!outputs.has(nativeId) && !placements.entrySet().stream() + .anyMatch(place -> nativeId.equals(text(placements, place.getKey())))) continue; + String domain = text(policy, "domain"); + if (!entry.getKey().equals(text(policy, "material") + "|" + domain) || policy.has("dormant") + || object(imported, "dimensions").has(domain) || object(imported, "dimension_selectors").has(domain) + || !"keep_separate".equals(text(policy, "mode")) || !"custom".equals(text(policy, "output_mode")) + || !"separate".equals(text(policy, "status")) || !"false".equals(text(policy, "review_required")) + || outputs.entrySet().size() != 1 || !unitWeight(outputs.get(nativeId)) + || placements.entrySet().size() != 1 + || !nativeId.equals(text(placements, "orespawn:standard"))) return null; + disposable.add(entry.getKey()); + } + return disposable; + } + + private static boolean unitWeight(JsonElement value) { + return value != null && value.isJsonPrimitive() && value.getAsJsonPrimitive().isNumber() + && value.getAsDouble() == 1.0D; + } + + private static void flag(JsonObject review, JsonObject manifest, String provider, String id, String reason) { + review.addProperty(id, reason); + if (!reason.equals(text(object(manifest, REVIEW), id))) { + LOGGER.warn("Review legacy ore overlap for provider {} and rule {} ({}); saved settings were kept", + provider, id, reason); + } + } + + private static boolean update(JsonObject manifest, String key, JsonObject value) { + if (value.equals(object(manifest, key))) return false; + if (value.entrySet().isEmpty()) manifest.remove(key); + else manifest.add(key, value); + return true; + } + + private static JsonObject object(JsonObject root, String key) { + JsonElement value = root.get(key); + return value != null && value.isJsonObject() ? value.getAsJsonObject() : new JsonObject(); + } + + private static String text(JsonObject root, String key) { + JsonElement value = root.get(key); + return value != null && value.isJsonPrimitive() ? value.getAsString() : ""; + } +} 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 015878c8..dda48e51 100644 --- a/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java +++ b/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java @@ -279,9 +279,15 @@ private static boolean mergeProviderDefinitions(JsonObject target, boolean exist String knownKey = "known_" + sectionName; Set known = stringSet(manifest.get(knownKey)); Set current = JsonCopies.keys(providerSection); + LegacyOreReplacements.Result replacements = existingWorld && "ores".equals(sectionName) + ? LegacyOreReplacements.reconcile(target, provider.modId, targetSection, + nativeOreDefaults(provider.modId, providerSection), providerSection, manifest, known) + : new LegacyOreReplacements.Result(); + changed |= replacements.changed; for (Entry entry : providerSection.entrySet()) { String id = entry.getKey(); + if (replacements.suppressed.contains(id)) continue; if (mergeNewEntries && !targetSection.has(id) && !known.contains(id)) { JsonObject value = JsonCopies.copy(entry.getValue().getAsJsonObject()); if (!"biome_rules".equals(sectionName)) { @@ -300,7 +306,9 @@ private static boolean mergeProviderDefinitions(JsonObject target, boolean exist for (String knownId : known) { if (!current.contains(knownId) && targetSection.has(knownId) - && targetSection.get(knownId).isJsonObject()) { + && !("ores".equals(sectionName) && LegacyOreReplacements.retains(manifest, knownId, current)) + && targetSection.get(knownId).isJsonObject() + && !bool(targetSection.getAsJsonObject(knownId), "orphaned_provider", false)) { targetSection.getAsJsonObject(knownId).addProperty("orphaned_provider", true); changed = true; } @@ -519,9 +527,21 @@ private static void rebuildActiveProviders() { } } + private static JsonObject nativeOreDefaults(String providerId, JsonObject activeOres) { + if (!FILE_PROVIDERS.containsKey(providerId)) return activeOres; + // A converted file can hide the native catalogue. Use the shipped rule to + // recognise an old duplicate; never treat an authored override as stock. + JsonObject shipped = installedProviderRoot(providerId); + return shipped == null ? new JsonObject() : optionalObject(shipped, "ores"); + } + private static JsonObject selectedProviderRoot(String providerId) { JsonObject root = FILE_PROVIDERS.get(providerId); - if (root == null) root = RESOURCE_PROVIDERS.get(providerId); + return root == null ? installedProviderRoot(providerId) : root; + } + + private static JsonObject installedProviderRoot(String providerId) { + JsonObject root = RESOURCE_PROVIDERS.get(providerId); if (root == null) { WorldgenProvider apiProvider = API_PROVIDERS.get(providerId); root = apiProvider == null ? null : apiProvider.toJson(); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java index 95316031..9eda8522 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java @@ -98,21 +98,31 @@ private GeomeConfig() { } public static synchronized BakedGeomeConfig bake() { + return bakeGlobal(true); + } + + public static synchronized BakedGeomeConfig bakeBeforeLoadComplete() { + // Features need their settings during startup, but other mods can still + // register ore providers. Wait for loadComplete before choosing defaults. + return bakeGlobal(false); + } + + private static BakedGeomeConfig bakeGlobal(boolean chooseOreSourceDefaults) { JsonObject root = loadConfig(); boolean changed = OreSpawnOreIntegration.mergeProviderOres(root); changed |= FluidDepositMigration.normalize(root); - changed |= OreSourcePolicies.initialize(root, false); + if (chooseOreSourceDefaults) changed |= OreSourcePolicies.initialize(root, false); if (changed) { writeProviderMerge(root); } globalBaseConfigRoot = JsonCopies.copy(root); globalBaseProfile = WorldGeologyProfile.fromGlobalConfig(root, - OreSpawnConfig.geologyMode(), OreSpawnConfig.placeCrudeOil()); - root = applyFreshWorldTemplate(root); - globalConfigRoot = JsonCopies.copy(root); - globalProfile = WorldGeologyProfile.fromGlobalConfig(root, - OreSpawnConfig.geologyMode(), OreSpawnConfig.placeCrudeOil()); - bakeDimensions(root); + OreSpawnConfig.geologyMode(), OreSpawnConfig.placeCrudeOil(), false); + JsonObject effective = applyFreshWorldTemplate(root); + globalConfigRoot = JsonCopies.copy(effective); + globalProfile = effective == root ? globalBaseProfile : WorldGeologyProfile.fromGlobalConfig(effective, + OreSpawnConfig.geologyMode(), OreSpawnConfig.placeCrudeOil(), chooseOreSourceDefaults); + bakeDimensions(effective); return bakedConfig; } 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 a2621574..f34159ec 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java @@ -152,7 +152,7 @@ private static void writeUpgradeReport(Path worldRoot, Path configDirectory, Path report = worldRoot.resolve("serverconfig/orespawn-upgrade-report.txt"); List missing = missingBlocks(igneous, metamorphic, sedimentary); List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.3.112021 Upgrade Report"); + lines.add("OreSpawn 4.1.6.112021 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Existing Mineralogy " + identity.version + " world detected."); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java index da91caf2..0973642b 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java @@ -65,7 +65,7 @@ private static void writeInitialUpgradeReport(Path configDirectory, boolean forceRetrogen, boolean flatBedrock, boolean retrogenBedrock, int bedrockLayers) throws IOException { String newline = System.lineSeparator(); - String text = "OreSpawn 4.1.3.112021 Upgrade Report" + newline + String text = "OreSpawn 4.1.6.112021 Upgrade Report" + newline + "================================" + newline + newline + "RESULT: Legacy OreSpawn settings were imported into the OS4 profile." + newline + "- Manage vanilla ores: " + manageVanilla + newline diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/OreSourcePolicies.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/OreSourcePolicies.java index 6fb1afe7..75b44b42 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/OreSourcePolicies.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/OreSourcePolicies.java @@ -487,9 +487,9 @@ private static Candidate preferred(Group group, ResourceLocation channel) { private static Map> priorities() { Map> result = new HashMap<>(); result.put("sulfur", Collections.unmodifiableList(Arrays.asList( - "mineralogy", "baseminerals", "electricadvantage"))); + "mineralogy", "baseminerals", "baseelectric", "electricadvantage"))); result.put("lithium", Collections.unmodifiableList(Arrays.asList( - "baseminerals", "electricadvantage"))); + "baseminerals", "baseelectric", "electricadvantage"))); return Collections.unmodifiableMap(result); } diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfile.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfile.java index 9f5be6f2..7af3c982 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfile.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfile.java @@ -71,8 +71,13 @@ public static WorldGeologyProfile recommended(boolean placeFluidDeposits) { public static WorldGeologyProfile fromGlobalConfig(JsonObject globalRoot, GeologyMode geologyMode, boolean placeFluidDeposits) { + return fromGlobalConfig(globalRoot, geologyMode, placeFluidDeposits, true); + } + + static WorldGeologyProfile fromGlobalConfig(JsonObject globalRoot, + GeologyMode geologyMode, boolean placeFluidDeposits, boolean chooseOreSourceDefaults) { JsonObject root = JsonCopies.copy(globalRoot); - OreSourcePolicies.initialize(root, false); + if (chooseOreSourceDefaults) OreSourcePolicies.initialize(root, false); if (!root.has("geology_mode")) { root.addProperty("geology_mode", geologyMode.name().toLowerCase(Locale.ROOT)); } diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java index 4d9fccc8..457ad51b 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java @@ -106,7 +106,7 @@ public static synchronized boolean reloadActiveProfile() { JsonObject merged = profile.rootCopy(); boolean changed = WorldgenIntegrationManager.mergeProviderDefinitionsIntoExistingWorld(merged); changed |= OreSourcePolicies.initialize(merged, true); - if (changed) { + if (changed && preserveBeforeLegacyOreRepair(profilePath, profile, merged)) { profile = profile.withRoot(merged); writeProfile(profilePath, profile); } @@ -130,7 +130,7 @@ public static void onServerAboutToStart(FMLServerAboutToStartEvent event) { String beforeMerge = merged.toString(); WorldgenIntegrationManager.mergeProviderDefinitionsIntoExistingWorld(merged); OreSourcePolicies.initialize(merged, true); - if (!beforeMerge.equals(merged.toString())) { + if (!beforeMerge.equals(merged.toString()) && preserveBeforeLegacyOreRepair(profilePath, profile, merged)) { profile = profile.withRoot(merged); writeProfile(profilePath, profile); LOGGER.info("Merged new OreSpawn worldgen-provider definitions into '{}'", profilePath); @@ -307,6 +307,24 @@ static WorldGeologyProfile readProfile(Path path, WorldGeologyProfile fallback) } } + private static boolean preserveBeforeLegacyOreRepair(Path path, WorldGeologyProfile before, JsonObject merged) { + JsonObject beforeRoot = before.rootCopy(); + if (!beforeRoot.has("ores") || !beforeRoot.get("ores").isJsonObject()) return true; + JsonObject newOres = merged.getAsJsonObject("ores"); + for (java.util.Map.Entry rule : beforeRoot.getAsJsonObject("ores").entrySet()) { + if (newOres != null && newOres.has(rule.getKey())) continue; + Path backup = path.resolveSibling("orespawn-worldgen.pre-legacy-ore-repair.bak"); + try { + if (!Files.exists(backup)) Files.copy(path, backup); + return true; + } catch (IOException e) { + LOGGER.warn("Could not back up '{}'; legacy ore repair was not applied", path, e); + return false; + } + } + return true; + } + private static boolean preserveBeforeOreDefaultsRefresh(Path path) { Path backup = path.resolveSibling("orespawn-worldgen.pre-ore-revision-" + GeomeConfig.oreDefaultsRevision() + ".bak"); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/math/PerlinNoise2D.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/math/PerlinNoise2D.java index 4821dc20..7ec6072c 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/math/PerlinNoise2D.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/math/PerlinNoise2D.java @@ -17,8 +17,8 @@ public PerlinNoise2D(long seed, float initialRange, float initialSize, int numOv for (int i = 0; i < layers.length; i++) { seed = scramble(seed); layers[i] = new NoiseLayer2D(seed, initialSize, initialRange); - initialSize *= 0.5; - initialRange *= 0.5; + initialSize *= 0.5F; + initialRange *= 0.5F; } } diff --git a/src/main/resources/assets/orespawn/lang/de_au.lang b/src/main/resources/assets/orespawn/lang/de_at.lang similarity index 99% rename from src/main/resources/assets/orespawn/lang/de_au.lang rename to src/main/resources/assets/orespawn/lang/de_at.lang index 223e3812..b8de30a7 100644 --- a/src/main/resources/assets/orespawn/lang/de_au.lang +++ b/src/main/resources/assets/orespawn/lang/de_at.lang @@ -1,3 +1,4 @@ +# German for Minecraft's Austria locale. tooltip.orespawn.fluid.add_deposit=Wählen Sie einen installierten Flüssigkeitsblock aus und erstellen Sie dafür eine neue Regel für unterirdische Flüssigkeitsvorkommen. tooltip.orespawn.assignment.ore=Weisen Sie diesen installierten Block als Erz zu und bearbeiten Sie anschließend seine Dimensionen, Vorkommensform, Wirte und Geome-Regeln. tooltip.orespawn.assignment.rock_family=Weisen Sie diesen installierten Block als Gestein in der ausgewählten Familie zu und bearbeiten Sie dann seine Tiefen- und Geome-Regeln. diff --git a/src/main/resources/assets/orespawn/lang/en_en.lang b/src/main/resources/assets/orespawn/lang/en_en.lang deleted file mode 100644 index df65a9b6..00000000 --- a/src/main/resources/assets/orespawn/lang/en_en.lang +++ /dev/null @@ -1,510 +0,0 @@ -tooltip.orespawn.fluid.add_deposit=Choose an installed fluid block and create a new underground fluid-deposit rule for it. -tooltip.orespawn.assignment.ore=Assign this installed block as an ore, then edit its dimensions, deposit shape, hosts, and geome rules. -tooltip.orespawn.assignment.rock_family=Assign this installed block as a rock in the selected family, then edit its depth and geome rules. -tooltip.orespawn.picker.mod_filter=Limit the installed block list to one mod namespace, or choose All Mods. -tooltip.orespawn.material.add_block=Choose an installed, unassigned block and create a rock or ore rule for the current tab. -tooltip.orespawn.material.safe_only=Hide blocks with block entities or unusual collision and show only ordinary full solid blocks. -tooltip.orespawn.material.show_all=Include blocks with block entities or unusual collision that are normally hidden because terrain replacement may be unsafe. -tooltip.orespawn.material.tab.unassigned=Show installed blocks that are not yet assigned as an OreSpawn rock, ore, or fluid. -tooltip.orespawn.material.tab.ores=Show configured ore entries and open their dimension, shape, host, and geome rules. -tooltip.orespawn.material.tab.igneous=Show intrusive and volcanic igneous rocks and open their generation rules. -tooltip.orespawn.material.tab.metamorphic=Show rocks classified as metamorphic and open their generation rules. -tooltip.orespawn.material.tab.sedimentary=Show rocks classified as sedimentary and open their generation rules. -tooltip.orespawn.geome.new_id.dictionary=Enter a Forge biome type name used by the installed biome dictionary. -tooltip.orespawn.geome.new_id.biomes=Enter an installed biome registry ID, for example minecraft:plains. -tooltip.orespawn.geome.new_id.geomes=Enter a new geome name. OreSpawn stores it in lowercase. -tooltip.orespawn.geome.tab.dictionary=Map Forge biome type names to the geomes they should favour. -tooltip.orespawn.geome.tab.biomes=Map exact biome registry IDs to the geomes they should favour. -tooltip.orespawn.geome.tab.geomes=Edit named geology regions and their base and rock-family weights. -tooltip.orespawn.geome.biome_weight=Influence this biome or biome type adds to the named geome. Valid range: 0 to 1000; 0 adds no influence. -tooltip.orespawn.geome.entry_weight=Relative chance for this rock, ore, or fluid deposit inside the named geome. Valid range: 0 to 1000; 0 excludes it. -tooltip.orespawn.geome.family_weight=Relative preference for this rock family inside the geome. Valid range: 0 to 1000; 0 excludes the family. -tooltip.orespawn.geome.base_weight=Base chance for this geome before biome influences are added. Valid range: 0 to 1000; 0 leaves only biome influence. -tooltip.orespawn.numeric.rock_layer_thickness=Base thickness of legacy Cyano rock layers. Whole numbers from 1 to 255 are accepted. -tooltip.orespawn.numeric.rock_layer_noise=Amount of vertical variation in legacy Cyano rock layers. Valid range: 1 to 32767. -tooltip.orespawn.numeric.geome_size=Horizontal size of legacy Cyano geome regions. Whole numbers from 4 to 32767 are accepted. -tooltip.orespawn.numeric.continuity=Chance that a formation keeps its identity across a boundary. Valid range: 0 to 1. -tooltip.orespawn.numeric.edge_octaves=Number of detail-noise layers combined at formation edges. Whole numbers from 1 to 8 are accepted. -tooltip.orespawn.numeric.edge_amplitude=Maximum vertical displacement caused by boundary detail. Valid range: 0 to 256. -tooltip.orespawn.numeric.edge_wavelength=Horizontal wavelength of small-scale boundary detail. Valid range: 8 to 512. -tooltip.orespawn.numeric.waviness_amplitude=Maximum vertical displacement caused by broad layer waviness. Valid range: 0 to 512. -tooltip.orespawn.numeric.waviness_wavelength=Horizontal wavelength of broad vertical layer bends. Valid range: 32 to 2048. -tooltip.orespawn.numeric.vertical_thickness=Typical vertical thickness of a Sky stratum. Whole numbers from 1 to 192 are accepted. -tooltip.orespawn.numeric.family_region_wavelength=Horizontal wavelength of rock-family regions. Larger values make broader regions. Valid range: 16 to 8192. -tooltip.orespawn.numeric.stratum_wavelength=Horizontal wavelength of Sky strata. The editor accepts 16 to 8192; Stable Layers effectively uses at least 32. -tooltip.orespawn.advanced.fluid_deposits=Open the configured underground fluid pockets and their dimension-specific placement rules. -tooltip.orespawn.advanced.cyano=Edit the legacy Cyano engine's region size, layer variation, and layer thickness. -tooltip.orespawn.advanced.formations=Edit the exact Sky formation values used when a formation control is set to Custom. -tooltip.orespawn.main.fluid_editor=Open each configured fluid deposit to edit dimensions, rarity, size, hosts, biome filters, and geome weights. -tooltip.orespawn.main.advanced=Open exact numeric controls for custom Sky formations, legacy Cyano layers, and configured fluid deposits. -tooltip.orespawn.main.biomes_materials=Configure optional biome placement plus dimension-wide aquifer, snow, ice, and surface-material overrides. -tooltip.orespawn.main.configure_strata=Create editable rock rules for Minecraft's standard stone, granite, diorite, and andesite strata. -tooltip.orespawn.main.materials=Open the current rock and ore rules to edit families, depth ranges, hosts, deposit shapes, and per-geome weights. -tooltip.orespawn.main.recommended=Set the geology engine and formation controls to the recommended Sky and Average choices. Detailed rock, ore, biome, and fluid rules are left unchanged. -tooltip.orespawn.main.template=Select a complete geology setup supplied by an installed mod or mod pack. Pack Defaults keeps the pack's normal selection. -tooltip.orespawn.enabled=Enable or disable this entry without deleting its saved settings. -tooltip.orespawn.weight=Relative chance compared with other eligible entries. Valid range: 0 to 1000; 0 prevents selection and larger values make this entry more likely. -tooltip.orespawn.geome_weights=Set this entry's relative chance in each Overworld geome. A weight of 0 prevents it there. -tooltip.orespawn.host_family=Allow generation in blocks assigned to this rock family. An enabled rule needs at least one family, block, or tag host. -tooltip.orespawn.host_blocks=Comma-separated block registry IDs that may be replaced, for example minecraft:stone. -tooltip.orespawn.host_tags=Comma-separated block-tag registry IDs whose blocks may be replaced, for example forge:stone. -tooltip.orespawn.fluid.dimension_settings=Open the first configured dimension. Use the dimension list below to open a specific dimension. -tooltip.orespawn.fluid.available_dimension=Choose an installed dimension to add, then edit its placement, host, and biome rules. -tooltip.orespawn.fluid.min_y=Lowest Y allowed for the deposit center. The editor accepts 0 to 255, but the value must also be inside the target dimension's build height and must not exceed Maximum Y. -tooltip.orespawn.fluid.max_y=Highest Y allowed for the deposit center. The editor accepts 0 to 255, but the value must also be inside the target dimension's build height and must not be below Minimum Y. -tooltip.orespawn.fluid.frequency=Average deposit attempts per chunk. 0 disables attempts; decimals are allowed up to 64. -tooltip.orespawn.fluid.min_radius=Smallest horizontal radius selected for a deposit lobe. Valid range: 1 to 64. -tooltip.orespawn.fluid.max_radius=Largest horizontal radius selected for a deposit lobe. It must be at least Minimum Radius and no more than 64. -tooltip.orespawn.fluid.min_vertical_radius=Smallest vertical radius selected for a deposit lobe. Valid range: 1 to 64. -tooltip.orespawn.fluid.max_vertical_radius=Largest vertical radius selected for a deposit lobe. It must be at least Minimum Vertical Radius and no more than 64. -tooltip.orespawn.fluid.max_lobes=Maximum rounded lobes joined into one deposit. 1 creates a single pocket; valid range: 1 to 16. -tooltip.orespawn.fluid.min_solid_cover=Minimum solid blocks required above a deposit. 0 disables extra roof protection; valid range: 0 to 64. -tooltip.orespawn.fluid.min_solid_shell=Minimum solid blocks required around the sides and floor. 0 disables extra shell protection; valid range: 0 to 64. -tooltip.orespawn.fluid.biome_ids=If set, deposits may generate only in these comma-separated biome registry IDs. Leave blank for no exact-biome restriction. -tooltip.orespawn.fluid.excluded_biome_ids=Deposits never generate in these comma-separated biome registry IDs. Exclusions override inclusions. -tooltip.orespawn.fluid.biome_dictionary=Include biomes matching these comma-separated Forge biome type names, for example OCEAN. Leave blank for no type restriction. -tooltip.orespawn.fluid.excluded_biome_dictionary=Exclude biomes matching these comma-separated Forge biome type names. Exclusions override inclusions. -tooltip.orespawn.ore.min_y=Lowest Y at which an ore placement attempt may start. The editor accepts 0 to 255, but the value must also be inside the target dimension's build height and must not exceed Maximum Y. -tooltip.orespawn.ore.max_y=Highest Y at which an ore placement attempt may start. The editor accepts 0 to 255, but the value must also be inside the target dimension's build height and must not be below Minimum Y. -tooltip.orespawn.ore.frequency=Average ore placement attempts per chunk. 0 disables attempts; decimals are allowed up to 64. -tooltip.orespawn.ore.min_quantity=Smallest block budget assigned to one deposit attempt. Valid range: 1 to 64. -tooltip.orespawn.ore.max_quantity=Largest block budget assigned to one deposit attempt. It must be at least Minimum Block Budget and no more than 64. -tooltip.orespawn.ore.discard_air_exposure=Chance to reject ore that would touch air. 0 keeps exposed ore; 1 rejects every exposed placement. -tooltip.orespawn.ore.pattern=Choose the deposit shape. Pattern-specific controls below are enabled only when the selected pattern uses them. -tooltip.orespawn.ore.height_distribution=Choose how placement attempts are distributed between Minimum Y and Maximum Y. -tooltip.orespawn.ore.spread=Horizontal range used by cluster and cloud patterns. Valid range: 0 to 64. -tooltip.orespawn.ore.vertical_spread=Vertical range used by cluster and cloud patterns. Valid range: 0 to 64. -tooltip.orespawn.ore.node_size=Block budget for each node in the Clusters pattern. Valid range: 1 to 32. -tooltip.orespawn.rock.family=Classify this rock as sedimentary, metamorphic, intrusive igneous, or volcanic igneous for geome and depth preferences. -tooltip.orespawn.rock.depth_peak=Y level where this rock receives its strongest depth preference. Valid range: 0 to 255. -tooltip.orespawn.rock.depth_spread=How gradually the rock's depth preference falls away from Depth Peak. Larger values cover a broader vertical range; valid range: 1 to 512. -tooltip.orespawn.rock.min_y=Lowest Y where this rock may replace terrain. Valid range: 0 to 255; it must not exceed Maximum Y. -tooltip.orespawn.rock.max_y=Highest Y where this rock may replace terrain. Valid range: 0 to 255; it must not be below Minimum Y. -tooltip.orespawn.rock.ore_replaceable=Allow OreSpawn-managed ores to replace this rock when it is selected as a host family. -tooltip.orespawn.biome.dimension=Select the dimension whose biome-placement and world-material settings are shown. -tooltip.orespawn.biome.palette_enabled=Enable provider-supplied biome placement in this dimension. Turning it off preserves the saved biome entries. -tooltip.orespawn.biome.mode=Augment mixes configured biomes with the original biome. Replace chooses only from eligible configured biomes. -tooltip.orespawn.biome.scope=Choose which existing biome namespaces may be replaced: all biomes, Minecraft only, or selected mod namespaces. -tooltip.orespawn.biome.region_size=Controls the horizontal size of biome-placement regions. Larger values create broader, less frequent boundaries. -tooltip.orespawn.biome.entries=Open this dimension's biome entries to configure weights, climate limits, similarity rules, and surface materials. -tooltip.orespawn.biome.dimension_materials=Configure aquifer fluid, snow, and ice across every biome in newly generated terrain for this dimension. Existing chunks are never rewritten. -tooltip.orespawn.biome.geome_influences=Map installed biomes to relative geome weights used by Sky geology. -tooltip.orespawn.biome.similar_biomes=Allow this output only when the original biome matches one of these IDs. An empty list allows any biome within the climate limits. -tooltip.orespawn.biome.required_similar_biomes=Like Similar Biomes, but this output is disabled if any listed biome is not installed. -tooltip.orespawn.biome.min_temperature=Lowest original-biome temperature eligible for this output. Valid range: -2 to 2. -tooltip.orespawn.biome.max_temperature=Highest original-biome temperature eligible for this output. Valid range: -2 to 2. -tooltip.orespawn.biome.min_downfall=Lowest original-biome downfall eligible for this output. Valid range: 0 to 1. -tooltip.orespawn.biome.max_downfall=Highest original-biome downfall eligible for this output. Valid range: 0 to 1. -tooltip.orespawn.biome.top_block=Replace this biome's top surface block. Not set keeps the generated biome's normal top block. -tooltip.orespawn.biome.filler_block=Replace the blocks immediately below the top surface. Filler Depth controls how many layers are changed. -tooltip.orespawn.biome.underwater_block=Replace the biome's exposed underwater surface block. Not set keeps the normal block. -tooltip.orespawn.biome.ceiling_block=Replace the biome's ceiling surface block in dimensions that generate ceilings. Not set keeps the normal block. -tooltip.orespawn.biome.filler_depth=Number of layers below the top block that use Filler Block. Valid range: 0 to 16. -tooltip.orespawn.material.default_fluid=Replace the normal aquifer fluid below sea level across every biome in this dimension. Not set keeps the generator's original fluid. Existing chunks are unchanged; fluids with unusual lighting may generate more slowly. -tooltip.orespawn.material.deep_aquifer_fluid=Unavailable in Minecraft 1.12.2; the stored value is retained for later versions. -tooltip.orespawn.material.deep_aquifer_y=Unavailable in Minecraft 1.12.2; the stored value is retained for later versions. -tooltip.orespawn.material.snow_block=Replace vanilla snow placed near the surface in this dimension. Not set keeps normal snow. -tooltip.orespawn.material.ice_block=Replace ordinary vanilla ice placed near the surface in this dimension. Not set keeps normal ice. -option.orespawn.min_quantity=Minimum Block Budget -option.orespawn.max_quantity=Maximum Block Budget -value.orespawn.dimension.all_except_nether_end=All except Nether and End -option.orespawn.host_blocks_short=Host Blocks -option.orespawn.host_tags_short=Host Block Tags -option.orespawn.biome_ids_short=Included Biomes -option.orespawn.excluded_biome_ids_short=Excluded Biomes -option.orespawn.biome_dictionary_short=Included Biome Types -option.orespawn.excluded_biome_dictionary_short=Excluded Biome Types -screen.orespawn.world_settings=OreSpawn Geology -button.orespawn.world_settings=OreSpawn... -button.orespawn.configure_strata=Configure Rock Strata... -button.orespawn.fluid_deposit_details=Fluid Deposits... -button.orespawn.fluid_deposits_count=Fluid Deposits: %s / %s -screen.orespawn.fluid_deposits=Fluid Deposits -screen.orespawn.fluid_deposit=Fluid Deposit -screen.orespawn.fluid_deposit_dimension=Fluid Deposit Dimension -option.orespawn.fluid_deposits=Fluid Deposits -option.orespawn.output_fluid=Output Fluid Block -option.orespawn.biome_ids=Biome IDs (comma separated) -option.orespawn.excluded_biome_ids=Excluded Biome IDs (comma separated) -option.orespawn.biome_dictionary=Biome Types (comma separated) -option.orespawn.excluded_biome_dictionary=Excluded Biome Types (comma separated) -tooltip.orespawn.fluid_deposits=ON generates configured covered underground fluid pockets. OFF keeps their settings but does not place them. -error.orespawn.host_required=Choose at least one host family, block, or tag. -error.orespawn.invalid_values=Check the values and registry IDs. -button.orespawn.recommended=Recommended Defaults -button.orespawn.advanced=Advanced Settings... -button.orespawn.help=Help & Guide -button.orespawn.previous=Previous -button.orespawn.next=Next -button.orespawn.materials=Rocks & Ores... -button.orespawn.geomes=Geomes & Biomes... -button.orespawn.formation_details=Custom Shapes... -button.orespawn.cyano_details=Cyano Settings... -button.orespawn.geome_weights=Geome Weights... -button.orespawn.search=Search -button.orespawn.show_all=Show All -button.orespawn.safe_only=Safe Only -button.orespawn.add=Add -button.orespawn.add_block=Add Block... -button.orespawn.reset=Reset -button.orespawn.unassign=Unassign -button.orespawn.remove=Remove -button.orespawn.remove_dimension=Remove Dimension -screen.orespawn.numeric_settings=Detailed Geology Settings -screen.orespawn.materials=Worldgen Materials -screen.orespawn.assign_block=Assign Block -screen.orespawn.choose_block=Choose Installed Block -screen.orespawn.rock_entry=Rock Settings -screen.orespawn.geome_weights=Geome Weights -screen.orespawn.ore_entry=Ore Settings -screen.orespawn.ore_dimension=Ore Dimension Settings -screen.orespawn.geomes=Geomes & Biome Influences -screen.orespawn.geome_entry=Geome Settings -screen.orespawn.advanced=Advanced Geology Settings -screen.orespawn.guide=OreSpawn Player Guide -guide.orespawn.welcome.title=Welcome to OreSpawn -guide.orespawn.welcome.1=OreSpawn is a world-building engine used by other mods. Installed alone, it leaves terrain and vanilla ores unchanged. -guide.orespawn.welcome.2=Provider mods can supply rocks, ores, fluid deposits, and ready-made settings. Ore-only mods do not have to add rock layers. -guide.orespawn.welcome.3=Recommended Defaults restores the choices supplied by your installed mods and mod pack. It is the safest place to start. -guide.orespawn.world.title=Choose Your Geology -guide.orespawn.world.1=Your choices are copied into this world when it is created. A template loads a complete setup supplied by a mod or mod pack. -guide.orespawn.world.2=Rock layers are optional. Sky makes biome-influenced geological regions called geomes. Cyano (Legacy) uses the classic older layer engine. -guide.orespawn.world.3=Formation Reach controls width. Layer Thickness controls depth. Waviness bends layers, Edge Detail roughens their borders, and Continuity keeps formations together. -guide.orespawn.rocks.title=Rocks and Geomes -guide.orespawn.rocks.1=Sedimentary, metamorphic, and igneous rocks form in different places and at different depths. Geomes decide which families suit each region. -guide.orespawn.rocks.2=Open Rocks & Ores to edit a block. Removing a rock only stops it appearing in new terrain; the block and its recipes stay available for old worlds. -guide.orespawn.rocks.3=Configure Rock Strata starts with stone, granite, diorite, and andesite. Add Block can deliberately include mod rocks or special vanilla blocks. -guide.orespawn.ores.title=Ores -guide.orespawn.ores.1=Manage Vanilla Ores lets OreSpawn replace Minecraft's normal ore features with the rules shown here. Leave it off to keep vanilla placement. -guide.orespawn.ores.2=Attempts per Chunk controls how often an ore tries to generate. Minimum and Maximum Block Budget control the possible size of each deposit. -guide.orespawn.ores.3=Minimum and Maximum Y set depth. Hosts choose which rock families, blocks, or block tags may be replaced; geome weights favour particular regions. -guide.orespawn.mods.title=Using Other Mods -guide.orespawn.mods.1=Blocks and biomes from installed mods appear in the pickers. OreSpawn does not need a hard dependency just to use their registry names. -guide.orespawn.mods.2=Provider mods and mod packs can supply ready-made rocks, ores, biome influences, dimensions, and templates. OreSpawn validates them before generation. -guide.orespawn.mods.3=Full registry IDs, such as minecraft:granite, identify blocks safely. Missing blocks or invalid settings are rejected before a world is generated. -guide.orespawn.server.title=Worlds and Servers -guide.orespawn.server.1=Your choices are saved in serverconfig/orespawn-worldgen.json inside the world. Keep that file when moving the world to a dedicated server. -guide.orespawn.server.2=Changes affect newly generated chunks only. Land you have already explored is never rewritten. -guide.orespawn.server.3=A server needs the same mods used by the profile. For a new server world, a pack author may instead prepare config/orespawn-worldgen.json. -tab.orespawn.sedimentary=Sedimentary -tab.orespawn.metamorphic=Metamorphic -tab.orespawn.igneous=Igneous -tab.orespawn.ores=Ores -tab.orespawn.unassigned=Unassigned -tab.orespawn.geomes=Geomes -tab.orespawn.biomes=Biomes -tab.orespawn.dictionary=Biome Types -tab.orespawn.placement=Placement -tab.orespawn.pattern=Pattern -tab.orespawn.hosts=Hosts -option.orespawn.geology_mode=Geology -option.orespawn.template=Template -option.orespawn.manage_vanilla_ores=Manage Vanilla Ores -option.orespawn.search=Search -option.orespawn.block_id=Block registry ID -option.orespawn.registry_id=Registry ID -option.orespawn.dimension=Dimension ID -option.orespawn.available_dimension=Available Dimension -option.orespawn.mod_filter=Mod -option.orespawn.enabled=Enabled -option.orespawn.family=Rock Family -option.orespawn.weight=Selection Weight -option.orespawn.depth_peak=Preferred Y -option.orespawn.depth_spread=Depth Spread -option.orespawn.min_y=Minimum Y -option.orespawn.max_y=Maximum Y -option.orespawn.frequency=Attempts / Chunk -option.orespawn.ore_richness=Ore Richness -option.orespawn.quantity=Block Budget -option.orespawn.pattern=Deposit Pattern -option.orespawn.height_distribution=Height Distribution -option.orespawn.discard_air_exposure=Air-Exposure Discard -option.orespawn.spread=Horizontal Spread -option.orespawn.vertical_spread=Vertical Spread -option.orespawn.node_size=Cluster Node Size -option.orespawn.ore_replaceable=Accepts Ores -option.orespawn.host_blocks=Additional Host Blocks (comma separated) -option.orespawn.host_tags=Host Block Tags (comma separated) -option.orespawn.base_weight=Regional Base Weight -option.orespawn.stratum_wavelength=Stratum Wavelength -option.orespawn.family_region_wavelength=Family Region Wavelength -option.orespawn.waviness_wavelength=Waviness Wavelength -option.orespawn.waviness_amplitude=Waviness Amplitude -option.orespawn.edge_wavelength=Edge Wavelength -option.orespawn.edge_amplitude=Edge Amplitude -option.orespawn.edge_octaves=Edge Octaves -option.orespawn.continuity=Continuity -option.orespawn.geome_size=Cyano Region Size -option.orespawn.rock_layer_noise=Cyano Layer Reach -option.orespawn.rock_layer_thickness=Cyano Layer Thickness -option.orespawn.min_radius=Minimum Radius -option.orespawn.max_radius=Maximum Radius -option.orespawn.min_vertical_radius=Minimum Vertical Radius -option.orespawn.max_vertical_radius=Maximum Vertical Radius -option.orespawn.max_lobes=Maximum Lobes -option.orespawn.min_solid_cover=Minimum Solid Cover -option.orespawn.min_solid_shell=Minimum Solid Shell -option.orespawn.horizontal_size=Formation Reach -option.orespawn.vertical_thickness=Layer Thickness -option.orespawn.waviness=Waviness -option.orespawn.edge_irregularity=Edge Detail -option.orespawn.formation_continuity=Continuity -value.orespawn.geology_mode.geome=Sky -value.orespawn.geology_mode.legacy=Cyano (Legacy) -value.orespawn.template.none=Pack Defaults -value.orespawn.intrusive=Igneous (Intrusive) -value.orespawn.volcanic=Igneous (Volcanic) -value.orespawn.all_mods=All Mods -value.orespawn.dimension.overworld=Overworld -value.orespawn.dimension.the_nether=The Nether -value.orespawn.dimension.the_end=The End -label.orespawn.adding_to=Add to %s -value.orespawn.family.sedimentary=Sedimentary -value.orespawn.family.metamorphic=Metamorphic -value.orespawn.family.igneous_intrusive=Igneous (Intrusive) -value.orespawn.family.igneous_volcanic=Igneous (Volcanic) -value.orespawn.ore_pattern.vein=Vein -value.orespawn.ore_pattern.cluster=Clusters -value.orespawn.ore_pattern.cloud=Cloud -value.orespawn.height_distribution.uniform=Uniform -value.orespawn.height_distribution.triangle=Triangular -value.orespawn.height_distribution.bottom_triangle=Bottom-Biased Triangle -value.orespawn.height_distribution.uniform_bottom_triangle=Uniform + Bottom Triangle -value.orespawn.ore_richness.ultra_poor=Ultra Poor -value.orespawn.ore_richness.poor=Poor -value.orespawn.ore_richness.average=Average -value.orespawn.ore_richness.rich=Rich -value.orespawn.ore_richness.ultra_rich=Ultra Rich -value.orespawn.preset.tiny=Tiny -value.orespawn.preset.small=Small -value.orespawn.preset.average=Average -value.orespawn.preset.large=Large -value.orespawn.preset.huge=Huge -value.orespawn.preset.custom=Custom -tooltip.orespawn.geology_mode=Sky uses biome-influenced geomes. Cyano (Legacy) uses the original Cyano rock-layer engine. -tooltip.orespawn.manage_vanilla_ores=ON disables Minecraft's normal ore features and generates those ores with OreSpawn's configured rules. OFF keeps vanilla ore placement. -tooltip.orespawn.ore_richness=Scales attempts per chunk from this ore's installed-pack default. Each step halves or doubles abundance, up to the 64-attempt safety limit; depth and deposit shape stay unchanged. -tooltip.orespawn.available_dimension=Lists dimensions from the current world settings and installed mod data. The registry ID below remains editable for server-only dimensions. -tooltip.orespawn.horizontal_size=Controls how far individual rock formations persist horizontally. -tooltip.orespawn.vertical_thickness=Controls the typical vertical thickness of rock layers. -tooltip.orespawn.waviness=Controls the broad vertical slope and curvature of rock layers. -tooltip.orespawn.edge_irregularity=Adds independent small-scale detail along layer boundaries. -tooltip.orespawn.formation_continuity=Controls how often formations retain their identity across regional and geome boundaries. -value.orespawn.ore_pattern.default=Compact -value.orespawn.ore_pattern.normal_cloud=Cloud -value.orespawn.ore_pattern.precision=Precision -value.orespawn.ore_pattern.clusters=Clusters -value.orespawn.ore_pattern.underfluids=Under Fluids -message.orespawn.external_pattern_read_only=Settings for this registered pattern are read-only here. -screen.orespawn.biomes_world_materials=Biomes & World Materials -screen.orespawn.biome_palette=Biome Palette -screen.orespawn.choose_biome=Choose Installed Biome -screen.orespawn.biome_placement=Biome Settings -screen.orespawn.biome_references=Similar Biomes -screen.orespawn.dimension_materials=%s Materials -button.orespawn.biomes_world_materials=Biomes... -button.orespawn.biome_palette_count=Biomes: %s -button.orespawn.dimension_materials=World Materials... -button.orespawn.dimension_materials_named=%s Materials... -label.orespawn.dimension_materials.scope=Applies to every biome in newly generated %s terrain. -button.orespawn.geome_influences=Geome Influences... -button.orespawn.add_biome=Add Biome... -button.orespawn.similar_biomes=Similar: %s -button.orespawn.required_biomes=Required: %s -button.orespawn.clear=Clear -button.orespawn.biome_selected=%s (selected) -button.orespawn.biome_available=%s -option.orespawn.biome_palette=Biome Palette -option.orespawn.biome_mode=Placement Mode -option.orespawn.biome_scope=Replacement Scope -option.orespawn.biome_region_size=Region Size -option.orespawn.min_temperature=Minimum Temperature -option.orespawn.max_temperature=Maximum Temperature -option.orespawn.min_downfall=Minimum Downfall -option.orespawn.max_downfall=Maximum Downfall -option.orespawn.filler_depth=Filler Depth -option.orespawn.top_block=Top: %s -option.orespawn.filler_block=Filler: %s -option.orespawn.underwater_block=Underwater: %s -option.orespawn.ceiling_block=Ceiling: %s -option.orespawn.default_fluid=Aquifer Fluid: %s -option.orespawn.deep_aquifer_fluid=Deep Aquifer: %s -option.orespawn.deep_aquifer_y=Deep Aquifer Below Y -option.orespawn.snow_block=Snow: %s -option.orespawn.ice_block=Ice: %s -value.orespawn.not_set=Not set -value.orespawn.biome_mode.augment=Augment -value.orespawn.biome_mode.replace=Replace -value.orespawn.biome_scope.all=All Biomes -value.orespawn.biome_scope.minecraft_only=Minecraft Only -value.orespawn.biome_scope.selected_namespaces=Selected Mods -tab.orespawn.biome_placement=Placement -tab.orespawn.biome_surface=Surface -tab.orespawn.biome_climate=Climate -guide.orespawn.biomes.title=Biomes -guide.orespawn.biomes.1=Provider mods can offer new biomes. Augment mixes them with the existing world, while Replace uses only the configured choices where the rule applies. -guide.orespawn.biomes.2=Scope decides which existing biome namespaces can be replaced. Minecraft Only protects every modded biome; Selected Mods changes only namespaces you choose. -guide.orespawn.biomes.3=Region Size controls broad patches. Each biome can use weight, climate limits, similar-biome hints, and optional top, filler, underwater, and ceiling blocks. -guide.orespawn.patterns.title=Ore Shapes and Visibility -guide.orespawn.patterns.1=Compact makes familiar blobs. Veins stretch through rock, Clusters form groups, Clouds spread broadly, and Under Fluids favours blocks beneath liquids. -guide.orespawn.patterns.2=Height Distribution controls where attempts land between Minimum and Maximum Y. Uniform spreads them evenly; triangle choices favour particular depths. -guide.orespawn.patterns.3=Air-Exposure Discard hides some ore touching caves. 0 keeps every exposed attempt and 1 rejects all exposed attempts; values between are chances. -guide.orespawn.fluids.title=Fluid Deposits -guide.orespawn.fluids.1=Fluid Deposits create covered underground pockets of water, lava, oil, or another installed fluid. They are separate from oceans and World Materials. -guide.orespawn.fluids.2=Attempts per Chunk controls rarity. Horizontal and Vertical Radius set deposit size, while Lobes join several rounded pockets into a less regular shape. -guide.orespawn.fluids.3=Solid Cover protects the roof and Solid Shell protects the sides and floor from caves. Hosts and biome rules decide where a deposit is allowed. -guide.orespawn.materials.title=World Materials -guide.orespawn.materials.1=World Materials changes background materials across a whole dimension. Use Fluid Deposits instead when you only want occasional underground pockets. -guide.orespawn.materials.2=Aquifer Fluid changes the normal fluid used below sea level. Deep Aquifer uses a second fluid below the chosen Y level. -guide.orespawn.materials.3=Snow and Ice replace vanilla snow and ordinary ice near the surface. Not set leaves Minecraft's normal material unchanged. -button.orespawn.mods=Mods -button.orespawn.mod.configure=Configure -tooltip.orespawn.main.mods=View loaded mods integrated with OreSpawn. -screen.orespawn.mods=OreSpawn Mods -tooltip.orespawn.mod.configure=Open %s configuration. -tooltip.orespawn.mod.unavailable=%s does not provide an OreSpawn-linked configuration screen. -label.orespawn.mod.id_version=%s - Mod version %s -label.orespawn.mod.additional_lineage=Also detected through OreSpawn %s compatibility. -label.orespawn.mod.status=Status: %s -label.orespawn.mod.os4_version=Native OS4 - schema %s, provider revision %s -label.orespawn.mod.os4=Native OS4 provider -label.orespawn.mod.legacy_version=OreSpawn %s compatibility -> OS4 schema %s, provider revision %s -label.orespawn.mod.legacy=OreSpawn %s compatibility -> OS4 -label.orespawn.mod.config_only=Client configuration integration -label.orespawn.mod.none=No loaded OreSpawn integrations were found. -label.orespawn.mod.integration_status=%s - %s -status.orespawn.mod.active=Active -status.orespawn.mod.pending=Pending -status.orespawn.mod.inactive=Inactive -status.orespawn.mod.rejected=Rejected -status.orespawn.mod.config_only=Configuration screen only -error.orespawn.mod.screen_missing=%s did not create a configuration screen. -error.orespawn.mod.screen_failed=Could not open %s configuration (%s). -button.orespawn.ore_sources=Ore Sources... -tooltip.orespawn.ore_sources=Review equivalent ore outputs without multiplying placement frequency. -screen.orespawn.ore_sources=Ore Sources -screen.orespawn.ore_source_detail=Ore Source Policy -label.orespawn.ore_source.none=No equivalent managed ore sources were found. -label.orespawn.ore_source.row=%s - %s - %s candidates - %s -label.orespawn.ore_source.intro=Choose how equivalent ores generate. -label.orespawn.ore_source.summary=%s candidates - %s -button.orespawn.ore_source.review=Review... -button.orespawn.ore_source.configure=Configure... -label.orespawn.ore_source.material=Material: %s -label.orespawn.ore_source.domain=Dimension/domain: %s -label.orespawn.ore_source.channels=Placement channels: %s -label.orespawn.ore_source.missing=Missing placement source -label.orespawn.ore_source.owner=Owner: %s, version %s -label.orespawn.ore_source.registry=Registry output: %s, metadata %s -label.orespawn.ore_source.dictionary=Ore Dictionary: %s -option.orespawn.ore_source.mode=Mode: %s -option.orespawn.ore_source.channel=Channel: %s -option.orespawn.ore_source.placement=Placement source: %s -option.orespawn.ore_source.weight=Weight -mode.orespawn.ore_source.consolidated=Consolidated -mode.orespawn.ore_source.keep_separate=Keep Separate -status.orespawn.ore_source.consolidated=Consolidated -status.orespawn.ore_source.separate=Separate -status.orespawn.ore_source.review_required=Review required -status.orespawn.ore_source.missing_source=Missing source -status.orespawn.ore_source.external_generation=External generation -tooltip.orespawn.ore_source.open=Open this material policy. -tooltip.orespawn.ore_source.mode=Consolidated uses one placement budget per channel. Keep Separate preserves every configured rule. -tooltip.orespawn.ore_source.channel=Placement engines are arbitrated independently. -tooltip.orespawn.ore_source.placement=Choose the rule that owns frequency, shape, hosts, filters and height for this channel. -tooltip.orespawn.ore_source.weight=Positive relative chance of selecting this output for a whole vein or identified deposit body. -tooltip.orespawn.ore_source.external=This mod registers generation outside OreSpawn; OreSpawn reports it but cannot suppress it. -tooltip.orespawn.ore_source.controlled=OreSpawn controls this configured placement and output source. -tooltip.orespawn.ore_source.enrichment=This entry is enrichment, not an interchangeable ore source. -tooltip.orespawn.ore_source.missing=This source is unavailable. Its saved selection will be restored if the owning mod returns. -error.orespawn.ore_source.weight=Output weights must be positive numbers. -button.orespawn.back=Back -button.orespawn.ore_source.all_groups=All Groups -button.orespawn.ore_source.add_group=Add Group -button.orespawn.ore_source.accept=Accept -tooltip.orespawn.ore_source.accept=Keep the current Keep Original policy unchanged and mark this group as reviewed. It is saved only when the main editor's Done is used. -button.orespawn.ore_source.delete_group=Delete -button.orespawn.ore_source.dissolve_group=Dissolve Group -button.orespawn.ore_source.confirm_dissolve=Confirm Dissolve -button.orespawn.ore_source.move=Move -button.orespawn.ore_source.advanced=Advanced... -button.orespawn.ore_source.hide_advanced=Hide Advanced -option.orespawn.ore_source.group_name=Group name -option.orespawn.ore_source.alias=Ore Dictionary entry -option.orespawn.ore_source.output_mode=Output: %s -mode.orespawn.ore_source.keep_original=Keep Original -mode.orespawn.ore_source.balanced=Balanced -mode.orespawn.ore_source.single=Single -mode.orespawn.ore_source.custom=Custom -label.orespawn.ore_source.no_aliases=No Ore Dictionary entries -status.orespawn.ore_source.external_short=external -status.orespawn.ore_source.missing_short=missing -status.orespawn.ore_source.output_only=output only -tooltip.orespawn.ore_source.output_only=This provider output is available, but its placement rule is inactive. -tooltip.orespawn.ore_source.output_mode=Keep Original preserves every rule. Balanced shares equally, Single chooses one block, and Custom enables weighted choices. -tooltip.orespawn.ore_source.alias=Add an exact Ore Dictionary ore name. Press Move again to confirm reassignment from another group. -tooltip.orespawn.ore_source.reset_group=Restore this curated group name and Ore Dictionary entries. -tooltip.orespawn.ore_source.delete_group=Delete this empty custom group without rewriting generated chunks. -tooltip.orespawn.ore_source.dissolve_group=Return every entry to its inferred or curated group. Press twice to confirm. -error.orespawn.ore_source.confirm_dissolve=Press Confirm Dissolve to return every Ore Dictionary entry to its original group. -error.orespawn.ore_source.alias=Use an exact Ore Dictionary ore name such as oreSulfur. -error.orespawn.ore_source.alias_owned=%s already owns this entry. Press Move to reassign it. -error.orespawn.ore_source.manage_vanilla_first=Enable Manage Vanilla Ores before moving a native vanilla ore into another group. -error.orespawn.ore_source.defaults_write=Could not save material-group defaults. -screen.orespawn.ore_source.group_settings=Ore Source Group Settings -label.orespawn.ore_source.groups=Groups -label.orespawn.ore_source.aliases=Ore Dictionary entries -label.orespawn.ore_source.placement_rules=Placement Rules -label.orespawn.ore_source.placement_explanation=Outputs choose the block; placement rules choose frequency, shape, depth and hosts. -label.orespawn.ore_source.standard_veins=Standard veins -label.orespawn.ore_source.placement_choice_count=%s (%s choices) -label.orespawn.ore_source.no_placement_rules=No managed placement rules -tooltip.orespawn.ore_source.group_settings=Edit this group's name, aliases and placement rules. -tooltip.orespawn.ore_source.remove_alias=Remove %s from this group. -tooltip.orespawn.ore_source.placement_rules=Outputs select the ore block; placement rules select frequency, shape, depth and host restrictions. -tooltip.orespawn.ore_source.placement_requires_consolidated=Placement ownership applies only to consolidated Balanced, Single or Custom modes. -tooltip.orespawn.ore_source.placement_missing=The saved placement source is missing; this channel remains disabled. -tooltip.orespawn.ore_source.placement_readonly=Only one active managed rule is available, so this source is read-only. -tooltip.orespawn.ore_source.placement_select=Click to choose between active managed placement sources. -tooltip.orespawn.ore_source.external_excluded=Independent external generators are reported but cannot be selected as placement rules. -button.orespawn.ore_source.hide_single=Hide Singles -tooltip.orespawn.ore_source.group_colours=Green entries have one alias and one output and need no action. Yellow groups have a saved rule. Red groups need attention. Show All includes green entries. -button.orespawn.ore_source.reset_all=Reset All -button.orespawn.ore_source.confirm_reset_all=Confirm Reset -tooltip.orespawn.ore_source.reset_all=Restore built-in material groups and recreate source policies from the currently loaded ores. The change remains pending until the main editor's Done button is used. -screen.orespawn.biomes=Biomes -screen.orespawn.biome_palettes=Biome Palettes -screen.orespawn.biome_palette_settings=Biome Palette Settings -button.orespawn.biome.hide_routine=Hide Routine -button.orespawn.biome.replace_new_terrain=Replace in new terrain with... -button.orespawn.biome.replace_with=Replace in new terrain with %s... -button.orespawn.biome.leave_original=Leave original behaviour -button.orespawn.biome.palettes=Palettes: %s -button.orespawn.biome.reset_selected=Reset Biome -button.orespawn.biome.confirm_reset=Confirm Reset -button.orespawn.biome.reset_palette=Reset Palette -button.orespawn.biome.confirm_reset_palette=Confirm Palette Reset -button.orespawn.biome.reset_dimension=Reset Dimension -button.orespawn.biome.confirm_reset_dimension=Confirm Dimension Reset -button.orespawn.biome.reset_all=Reset All Biomes -button.orespawn.biome.confirm_reset_all=Confirm All-Biome Reset -value.orespawn.biome.status.user_replaced=Replaced in new terrain -value.orespawn.biome.status.layered=Several placement rules -value.orespawn.biome.status.disabled=Placement disabled -value.orespawn.biome.status.missing=Missing biome or provider -value.orespawn.biome.status.provider_managed=Provider-managed -value.orespawn.biome.status.unmanaged=Registered, unmanaged -label.orespawn.biome.rule_count=Placement rules: %s -label.orespawn.biome.replaced_by=New-terrain replacement: %s -label.orespawn.biome.palette_owner_order=Owner: %s; effective order: %s -label.orespawn.biome.surface_effective=This rule supplies the effective surface. -label.orespawn.biome.surface_overridden=A later enabled rule overrides this surface. -label.orespawn.biome.placement_rules=Placement rules -label.orespawn.biome.palette_entries=Placement entries: %s -tooltip.orespawn.biome.no_retrogen=Biome replacements apply only while new terrain is generated. Existing chunks are never rewritten. -tooltip.orespawn.biome.override_palette=Reserved final replacement layer. Edit it through each biome's replacement control. -tooltip.orespawn.biome.palette_entries_read_only=Palette membership is provider/profile-defined. Select a biome in the directory to edit an existing placement rule. -option.orespawn.biome.coverage=Coverage -option.orespawn.biome.fallback_weight=Original-biome weight -option.orespawn.biome.include_namespaces=Included namespaces (comma-separated) -option.orespawn.biome.exclude_namespaces=Excluded namespaces (comma-separated) diff --git a/src/main/resources/assets/orespawn/lang/en_pt.lang b/src/main/resources/assets/orespawn/lang/en_pt.lang index df65a9b6..32bf021c 100644 --- a/src/main/resources/assets/orespawn/lang/en_pt.lang +++ b/src/main/resources/assets/orespawn/lang/en_pt.lang @@ -114,15 +114,15 @@ option.orespawn.biome_ids_short=Included Biomes option.orespawn.excluded_biome_ids_short=Excluded Biomes option.orespawn.biome_dictionary_short=Included Biome Types option.orespawn.excluded_biome_dictionary_short=Excluded Biome Types -screen.orespawn.world_settings=OreSpawn Geology -button.orespawn.world_settings=OreSpawn... -button.orespawn.configure_strata=Configure Rock Strata... -button.orespawn.fluid_deposit_details=Fluid Deposits... -button.orespawn.fluid_deposits_count=Fluid Deposits: %s / %s -screen.orespawn.fluid_deposits=Fluid Deposits -screen.orespawn.fluid_deposit=Fluid Deposit -screen.orespawn.fluid_deposit_dimension=Fluid Deposit Dimension -option.orespawn.fluid_deposits=Fluid Deposits +screen.orespawn.world_settings=OreSpawn's Chart Room +button.orespawn.world_settings=OreSpawn Ahoy... +button.orespawn.configure_strata=Chart Rock Layers... +button.orespawn.fluid_deposit_details=Liquid Caches... +button.orespawn.fluid_deposits_count=Liquid Caches: %s / %s +screen.orespawn.fluid_deposits=Liquid Caches +screen.orespawn.fluid_deposit=Liquid Cache +screen.orespawn.fluid_deposit_dimension=Liquid Cache Waters +option.orespawn.fluid_deposits=Liquid Caches option.orespawn.output_fluid=Output Fluid Block option.orespawn.biome_ids=Biome IDs (comma separated) option.orespawn.excluded_biome_ids=Excluded Biome IDs (comma separated) @@ -131,61 +131,61 @@ option.orespawn.excluded_biome_dictionary=Excluded Biome Types (comma separated) tooltip.orespawn.fluid_deposits=ON generates configured covered underground fluid pockets. OFF keeps their settings but does not place them. error.orespawn.host_required=Choose at least one host family, block, or tag. error.orespawn.invalid_values=Check the values and registry IDs. -button.orespawn.recommended=Recommended Defaults -button.orespawn.advanced=Advanced Settings... -button.orespawn.help=Help & Guide -button.orespawn.previous=Previous -button.orespawn.next=Next -button.orespawn.materials=Rocks & Ores... -button.orespawn.geomes=Geomes & Biomes... -button.orespawn.formation_details=Custom Shapes... -button.orespawn.cyano_details=Cyano Settings... -button.orespawn.geome_weights=Geome Weights... -button.orespawn.search=Search -button.orespawn.show_all=Show All -button.orespawn.safe_only=Safe Only -button.orespawn.add=Add -button.orespawn.add_block=Add Block... -button.orespawn.reset=Reset -button.orespawn.unassign=Unassign -button.orespawn.remove=Remove -button.orespawn.remove_dimension=Remove Dimension -screen.orespawn.numeric_settings=Detailed Geology Settings -screen.orespawn.materials=Worldgen Materials -screen.orespawn.assign_block=Assign Block -screen.orespawn.choose_block=Choose Installed Block -screen.orespawn.rock_entry=Rock Settings -screen.orespawn.geome_weights=Geome Weights -screen.orespawn.ore_entry=Ore Settings -screen.orespawn.ore_dimension=Ore Dimension Settings -screen.orespawn.geomes=Geomes & Biome Influences -screen.orespawn.geome_entry=Geome Settings -screen.orespawn.advanced=Advanced Geology Settings -screen.orespawn.guide=OreSpawn Player Guide -guide.orespawn.welcome.title=Welcome to OreSpawn -guide.orespawn.welcome.1=OreSpawn is a world-building engine used by other mods. Installed alone, it leaves terrain and vanilla ores unchanged. -guide.orespawn.welcome.2=Provider mods can supply rocks, ores, fluid deposits, and ready-made settings. Ore-only mods do not have to add rock layers. -guide.orespawn.welcome.3=Recommended Defaults restores the choices supplied by your installed mods and mod pack. It is the safest place to start. -guide.orespawn.world.title=Choose Your Geology -guide.orespawn.world.1=Your choices are copied into this world when it is created. A template loads a complete setup supplied by a mod or mod pack. -guide.orespawn.world.2=Rock layers are optional. Sky makes biome-influenced geological regions called geomes. Cyano (Legacy) uses the classic older layer engine. -guide.orespawn.world.3=Formation Reach controls width. Layer Thickness controls depth. Waviness bends layers, Edge Detail roughens their borders, and Continuity keeps formations together. -guide.orespawn.rocks.title=Rocks and Geomes -guide.orespawn.rocks.1=Sedimentary, metamorphic, and igneous rocks form in different places and at different depths. Geomes decide which families suit each region. -guide.orespawn.rocks.2=Open Rocks & Ores to edit a block. Removing a rock only stops it appearing in new terrain; the block and its recipes stay available for old worlds. -guide.orespawn.rocks.3=Configure Rock Strata starts with stone, granite, diorite, and andesite. Add Block can deliberately include mod rocks or special vanilla blocks. -guide.orespawn.ores.title=Ores -guide.orespawn.ores.1=Manage Vanilla Ores lets OreSpawn replace Minecraft's normal ore features with the rules shown here. Leave it off to keep vanilla placement. -guide.orespawn.ores.2=Attempts per Chunk controls how often an ore tries to generate. Minimum and Maximum Block Budget control the possible size of each deposit. -guide.orespawn.ores.3=Minimum and Maximum Y set depth. Hosts choose which rock families, blocks, or block tags may be replaced; geome weights favour particular regions. -guide.orespawn.mods.title=Using Other Mods -guide.orespawn.mods.1=Blocks and biomes from installed mods appear in the pickers. OreSpawn does not need a hard dependency just to use their registry names. -guide.orespawn.mods.2=Provider mods and mod packs can supply ready-made rocks, ores, biome influences, dimensions, and templates. OreSpawn validates them before generation. -guide.orespawn.mods.3=Full registry IDs, such as minecraft:granite, identify blocks safely. Missing blocks or invalid settings are rejected before a world is generated. -guide.orespawn.server.title=Worlds and Servers -guide.orespawn.server.1=Your choices are saved in serverconfig/orespawn-worldgen.json inside the world. Keep that file when moving the world to a dedicated server. -guide.orespawn.server.2=Changes affect newly generated chunks only. Land you have already explored is never rewritten. -guide.orespawn.server.3=A server needs the same mods used by the profile. For a new server world, a pack author may instead prepare config/orespawn-worldgen.json. +button.orespawn.recommended=Captain's Defaults +button.orespawn.advanced=Fine-Tune the Charts... +button.orespawn.help=Captain's Handbook +button.orespawn.previous=Astern +button.orespawn.next=Ahead +button.orespawn.materials=Rocks & Buried Ores... +button.orespawn.geomes=Chart Geomes & Biomes... +button.orespawn.formation_details=Shape the Layers... +button.orespawn.cyano_details=Rig the Cyano Engine... +button.orespawn.geome_weights=Weigh the Geomes... +button.orespawn.search=Spyglass +button.orespawn.show_all=Show the Lot +button.orespawn.safe_only=Safe Cargo Only +button.orespawn.add=Bring Aboard +button.orespawn.add_block=Bring a Block Aboard... +button.orespawn.reset=Restore the Chart +button.orespawn.unassign=Unclaim +button.orespawn.remove=Strike Off +button.orespawn.remove_dimension=Strike Off Dimension +screen.orespawn.numeric_settings=Geology Chart Details +screen.orespawn.materials=World Materials Ledger +screen.orespawn.assign_block=Give the Block a Job +screen.orespawn.choose_block=Pick a Block from the Hold +screen.orespawn.rock_entry=Rock Chart +screen.orespawn.geome_weights=Weigh the Geomes +screen.orespawn.ore_entry=Ore Chart +screen.orespawn.ore_dimension=Ore Chart by Dimension +screen.orespawn.geomes=Geomes & Biome Charts +screen.orespawn.geome_entry=Geome Chart +screen.orespawn.advanced=Fine-Tune the Geology Charts +screen.orespawn.guide=OreSpawn Captain's Handbook +guide.orespawn.welcome.title=Welcome Aboard OreSpawn +guide.orespawn.welcome.1=OreSpawn helps other mods build yer world. With no providers aboard, it leaves the land and vanilla ores as they were. +guide.orespawn.welcome.2=Provider mods can bring rocks, ores, liquid caches and ready-made orders aboard. An ore-only mod needn't lay any rock layers. +guide.orespawn.welcome.3=Captain's Defaults restores the choices from yer mods and mod pack. Start there if ye haven't charted a course. +guide.orespawn.world.title=Chart Yer Geology +guide.orespawn.world.1=Yer choices are copied into the world when ye make it. A template brings aboard a complete setup from a mod or mod pack. +guide.orespawn.world.2=Rock layers are optional. Sky draws regions called geomes with help from the biomes. Cyano (Legacy) follows the old layer charts. +guide.orespawn.world.3=Formation Reach sets width; Layer Thickness sets depth. Waviness bends the layers, Edge Detail roughens the borders, and Continuity holds the formations together. +guide.orespawn.rocks.title=Rocks Beneath the Keel +guide.orespawn.rocks.1=Sedimentary, metamorphic and igneous rocks belong in different places and at different depths. The geome charts say which families suit each region. +guide.orespawn.rocks.2=Open Rocks & Buried Ores to edit a block. Strike a rock off the chart and it stops appearing in new land; its block and recipes stay aboard for old worlds. +guide.orespawn.rocks.3=Chart Rock Layers starts with stone, granite, diorite and andesite. Bring a Block Aboard can add mod rocks or special vanilla blocks. +guide.orespawn.ores.title=Chart the Buried Ores +guide.orespawn.ores.1=Manage Vanilla Loot lets OreSpawn use these rules in place of Minecraft's usual ore features. Leave it off to keep vanilla placement. +guide.orespawn.ores.2=Attempts per Chunk says how often ye try for ore. Minimum and Maximum Block Budget set the possible size of each deposit. +guide.orespawn.ores.3=Minimum and Maximum Y set depth. Hosts tell ye which rock families, blocks or tags can be replaced; geome weights favour some regions over others. +guide.orespawn.mods.title=Bring Other Mods Aboard +guide.orespawn.mods.1=Blocks and biomes from mods aboard appear in the pickers. OreSpawn needn't depend on a mod just to use its registry names. +guide.orespawn.mods.2=Provider mods and packs can bring ready-made rocks, ores, biome influences, dimensions and templates. OreSpawn checks the cargo before generation. +guide.orespawn.mods.3=Use full registry IDs like minecraft:granite to point to the right block. Missing blocks and bad settings are caught before the world is made. +guide.orespawn.server.title=Keep Yer World and Server Charts +guide.orespawn.server.1=Yer world keeps its orders in serverconfig/orespawn-worldgen.json. Take that file aboard when ye move the world to a dedicated server. +guide.orespawn.server.2=These changes only reach newly charted chunks. Land ye already explored won't be rewritten. +guide.orespawn.server.3=A server needs the same mods aboard as the saved profile. For a new server world, a pack author can prepare config/orespawn-worldgen.json instead. tab.orespawn.sedimentary=Sedimentary tab.orespawn.metamorphic=Metamorphic tab.orespawn.igneous=Igneous @@ -199,14 +199,14 @@ tab.orespawn.pattern=Pattern tab.orespawn.hosts=Hosts option.orespawn.geology_mode=Geology option.orespawn.template=Template -option.orespawn.manage_vanilla_ores=Manage Vanilla Ores -option.orespawn.search=Search +option.orespawn.manage_vanilla_ores=Manage Vanilla Loot +option.orespawn.search=Spyglass option.orespawn.block_id=Block registry ID option.orespawn.registry_id=Registry ID option.orespawn.dimension=Dimension ID option.orespawn.available_dimension=Available Dimension option.orespawn.mod_filter=Mod -option.orespawn.enabled=Enabled +option.orespawn.enabled=In Service option.orespawn.family=Rock Family option.orespawn.weight=Selection Weight option.orespawn.depth_peak=Preferred Y @@ -251,10 +251,10 @@ option.orespawn.edge_irregularity=Edge Detail option.orespawn.formation_continuity=Continuity value.orespawn.geology_mode.geome=Sky value.orespawn.geology_mode.legacy=Cyano (Legacy) -value.orespawn.template.none=Pack Defaults +value.orespawn.template.none=Pack's Own Orders value.orespawn.intrusive=Igneous (Intrusive) value.orespawn.volcanic=Igneous (Volcanic) -value.orespawn.all_mods=All Mods +value.orespawn.all_mods=All Mods Aboard value.orespawn.dimension.overworld=Overworld value.orespawn.dimension.the_nether=The Nether value.orespawn.dimension.the_end=The End @@ -296,22 +296,22 @@ value.orespawn.ore_pattern.precision=Precision value.orespawn.ore_pattern.clusters=Clusters value.orespawn.ore_pattern.underfluids=Under Fluids message.orespawn.external_pattern_read_only=Settings for this registered pattern are read-only here. -screen.orespawn.biomes_world_materials=Biomes & World Materials -screen.orespawn.biome_palette=Biome Palette -screen.orespawn.choose_biome=Choose Installed Biome -screen.orespawn.biome_placement=Biome Settings -screen.orespawn.biome_references=Similar Biomes -screen.orespawn.dimension_materials=%s Materials -button.orespawn.biomes_world_materials=Biomes... -button.orespawn.biome_palette_count=Biomes: %s -button.orespawn.dimension_materials=World Materials... -button.orespawn.dimension_materials_named=%s Materials... -label.orespawn.dimension_materials.scope=Applies to every biome in newly generated %s terrain. -button.orespawn.geome_influences=Geome Influences... -button.orespawn.add_biome=Add Biome... +screen.orespawn.biomes_world_materials=Biomes & the World's Cargo +screen.orespawn.biome_palette=Biome Chart +screen.orespawn.choose_biome=Pick a Biome Aboard +screen.orespawn.biome_placement=Biome Chart Settings +screen.orespawn.biome_references=Lookalike Biomes +screen.orespawn.dimension_materials=%s Cargo +button.orespawn.biomes_world_materials=Chart the Biomes... +button.orespawn.biome_palette_count=Biomes Aboard: %s +button.orespawn.dimension_materials=World Cargo... +button.orespawn.dimension_materials_named=%s Cargo... +label.orespawn.dimension_materials.scope=Every biome in newly charted %s terrain uses this cargo. +button.orespawn.geome_influences=Chart Geome Influences... +button.orespawn.add_biome=Bring a Biome Aboard... button.orespawn.similar_biomes=Similar: %s button.orespawn.required_biomes=Required: %s -button.orespawn.clear=Clear +button.orespawn.clear=Clear the Slate button.orespawn.biome_selected=%s (selected) button.orespawn.biome_available=%s option.orespawn.biome_palette=Biome Palette @@ -341,26 +341,26 @@ value.orespawn.biome_scope.selected_namespaces=Selected Mods tab.orespawn.biome_placement=Placement tab.orespawn.biome_surface=Surface tab.orespawn.biome_climate=Climate -guide.orespawn.biomes.title=Biomes -guide.orespawn.biomes.1=Provider mods can offer new biomes. Augment mixes them with the existing world, while Replace uses only the configured choices where the rule applies. -guide.orespawn.biomes.2=Scope decides which existing biome namespaces can be replaced. Minecraft Only protects every modded biome; Selected Mods changes only namespaces you choose. -guide.orespawn.biomes.3=Region Size controls broad patches. Each biome can use weight, climate limits, similar-biome hints, and optional top, filler, underwater, and ceiling blocks. -guide.orespawn.patterns.title=Ore Shapes and Visibility -guide.orespawn.patterns.1=Compact makes familiar blobs. Veins stretch through rock, Clusters form groups, Clouds spread broadly, and Under Fluids favours blocks beneath liquids. -guide.orespawn.patterns.2=Height Distribution controls where attempts land between Minimum and Maximum Y. Uniform spreads them evenly; triangle choices favour particular depths. -guide.orespawn.patterns.3=Air-Exposure Discard hides some ore touching caves. 0 keeps every exposed attempt and 1 rejects all exposed attempts; values between are chances. -guide.orespawn.fluids.title=Fluid Deposits -guide.orespawn.fluids.1=Fluid Deposits create covered underground pockets of water, lava, oil, or another installed fluid. They are separate from oceans and World Materials. -guide.orespawn.fluids.2=Attempts per Chunk controls rarity. Horizontal and Vertical Radius set deposit size, while Lobes join several rounded pockets into a less regular shape. -guide.orespawn.fluids.3=Solid Cover protects the roof and Solid Shell protects the sides and floor from caves. Hosts and biome rules decide where a deposit is allowed. -guide.orespawn.materials.title=World Materials -guide.orespawn.materials.1=World Materials changes background materials across a whole dimension. Use Fluid Deposits instead when you only want occasional underground pockets. -guide.orespawn.materials.2=Aquifer Fluid changes the normal fluid used below sea level. Deep Aquifer uses a second fluid below the chosen Y level. -guide.orespawn.materials.3=Snow and Ice replace vanilla snow and ordinary ice near the surface. Not set leaves Minecraft's normal material unchanged. -button.orespawn.mods=Mods -button.orespawn.mod.configure=Configure +guide.orespawn.biomes.title=Chart the Biomes +guide.orespawn.biomes.1=Provider mods can bring new biomes aboard. Augment mixes them into the existing world; Replace uses only the choices on yer chart where the rule applies. +guide.orespawn.biomes.2=Scope sets which biome namespaces can be swapped. Minecraft Only protects modded biomes; Selected Mods changes only the namespaces ye pick. +guide.orespawn.biomes.3=Region Size sets the width of each patch. Ye can set weights, climate limits, similar biomes and optional top, filler, underwater and ceiling blocks. +guide.orespawn.patterns.title=Shape the Buried Ores +guide.orespawn.patterns.1=Compact makes familiar lumps. Veins stretch through rock, Clusters gather in groups, Clouds scatter wider, and Under Fluids favours blocks below liquids. +guide.orespawn.patterns.2=Height Distribution sets where attempts land between Minimum and Maximum Y. Uniform spreads them evenly; triangle choices favour particular depths on yer chart. +guide.orespawn.patterns.3=Air-Exposure Discard can hide ore that touches a cave. Set it to 0 to keep every exposed attempt, or 1 to reject them all. Values in between are chances. +guide.orespawn.fluids.title=Liquid Caches Below Deck +guide.orespawn.fluids.1=Liquid Caches make covered pockets of water, lava, oil or another fluid aboard. They don't change oceans or the materials in the World Cargo chart. +guide.orespawn.fluids.2=Attempts per Chunk controls how rare the caches are. Horizontal and Vertical Radius set size, while Lobes join rounded pockets into a less regular shape. +guide.orespawn.fluids.3=Solid Cover guards the roof and Solid Shell guards the sides and floor against caves. Host and biome rules tell ye where a cache may form. +guide.orespawn.materials.title=Take Stock of World Cargo +guide.orespawn.materials.1=World Cargo swaps background materials across an entire dimension. Use Liquid Caches if ye only want the odd underground pocket. +guide.orespawn.materials.2=Aquifer Fluid sets the usual fluid below sea level. Deep Aquifer uses another fluid below the Y level ye choose. +guide.orespawn.materials.3=Snow and Ice swap the vanilla materials near the surface. Leave a material unset to keep Minecraft's usual block. +button.orespawn.mods=Mods Aboard +button.orespawn.mod.configure=Set Up tooltip.orespawn.main.mods=View loaded mods integrated with OreSpawn. -screen.orespawn.mods=OreSpawn Mods +screen.orespawn.mods=OreSpawn Mods Aboard tooltip.orespawn.mod.configure=Open %s configuration. tooltip.orespawn.mod.unavailable=%s does not provide an OreSpawn-linked configuration screen. label.orespawn.mod.id_version=%s - Mod version %s @@ -380,16 +380,16 @@ status.orespawn.mod.rejected=Rejected status.orespawn.mod.config_only=Configuration screen only error.orespawn.mod.screen_missing=%s did not create a configuration screen. error.orespawn.mod.screen_failed=Could not open %s configuration (%s). -button.orespawn.ore_sources=Ore Sources... +button.orespawn.ore_sources=Ore Supplies... tooltip.orespawn.ore_sources=Review equivalent ore outputs without multiplying placement frequency. -screen.orespawn.ore_sources=Ore Sources -screen.orespawn.ore_source_detail=Ore Source Policy -label.orespawn.ore_source.none=No equivalent managed ore sources were found. +screen.orespawn.ore_sources=Ore Supplies +screen.orespawn.ore_source_detail=Ore Supply Orders +label.orespawn.ore_source.none=No matching managed ore supplies aboard. label.orespawn.ore_source.row=%s - %s - %s candidates - %s -label.orespawn.ore_source.intro=Choose how equivalent ores generate. +label.orespawn.ore_source.intro=Set the orders for matching ores. label.orespawn.ore_source.summary=%s candidates - %s -button.orespawn.ore_source.review=Review... -button.orespawn.ore_source.configure=Configure... +button.orespawn.ore_source.review=Inspect... +button.orespawn.ore_source.configure=Give Orders... label.orespawn.ore_source.material=Material: %s label.orespawn.ore_source.domain=Dimension/domain: %s label.orespawn.ore_source.channels=Placement channels: %s @@ -418,17 +418,17 @@ tooltip.orespawn.ore_source.controlled=OreSpawn controls this configured placeme tooltip.orespawn.ore_source.enrichment=This entry is enrichment, not an interchangeable ore source. tooltip.orespawn.ore_source.missing=This source is unavailable. Its saved selection will be restored if the owning mod returns. error.orespawn.ore_source.weight=Output weights must be positive numbers. -button.orespawn.back=Back -button.orespawn.ore_source.all_groups=All Groups -button.orespawn.ore_source.add_group=Add Group -button.orespawn.ore_source.accept=Accept +button.orespawn.back=Back to Deck +button.orespawn.ore_source.all_groups=Every Crew +button.orespawn.ore_source.add_group=Form a Crew +button.orespawn.ore_source.accept=Aye tooltip.orespawn.ore_source.accept=Keep the current Keep Original policy unchanged and mark this group as reviewed. It is saved only when the main editor's Done is used. -button.orespawn.ore_source.delete_group=Delete -button.orespawn.ore_source.dissolve_group=Dissolve Group -button.orespawn.ore_source.confirm_dissolve=Confirm Dissolve -button.orespawn.ore_source.move=Move -button.orespawn.ore_source.advanced=Advanced... -button.orespawn.ore_source.hide_advanced=Hide Advanced +button.orespawn.ore_source.delete_group=Strike Off +button.orespawn.ore_source.dissolve_group=Disband Crew +button.orespawn.ore_source.confirm_dissolve=Aye, Disband Crew +button.orespawn.ore_source.move=Move to This Crew +button.orespawn.ore_source.advanced=More Orders... +button.orespawn.ore_source.hide_advanced=Stow Extra Orders option.orespawn.ore_source.group_name=Group name option.orespawn.ore_source.alias=Ore Dictionary entry option.orespawn.ore_source.output_mode=Output: %s @@ -451,8 +451,8 @@ error.orespawn.ore_source.alias=Use an exact Ore Dictionary ore name such as ore error.orespawn.ore_source.alias_owned=%s already owns this entry. Press Move to reassign it. error.orespawn.ore_source.manage_vanilla_first=Enable Manage Vanilla Ores before moving a native vanilla ore into another group. error.orespawn.ore_source.defaults_write=Could not save material-group defaults. -screen.orespawn.ore_source.group_settings=Ore Source Group Settings -label.orespawn.ore_source.groups=Groups +screen.orespawn.ore_source.group_settings=Ore Crew Orders +label.orespawn.ore_source.groups=Crews label.orespawn.ore_source.aliases=Ore Dictionary entries label.orespawn.ore_source.placement_rules=Placement Rules label.orespawn.ore_source.placement_explanation=Outputs choose the block; placement rules choose frequency, shape, depth and hosts. @@ -467,27 +467,27 @@ tooltip.orespawn.ore_source.placement_missing=The saved placement source is miss tooltip.orespawn.ore_source.placement_readonly=Only one active managed rule is available, so this source is read-only. tooltip.orespawn.ore_source.placement_select=Click to choose between active managed placement sources. tooltip.orespawn.ore_source.external_excluded=Independent external generators are reported but cannot be selected as placement rules. -button.orespawn.ore_source.hide_single=Hide Singles +button.orespawn.ore_source.hide_single=Hide Lone Sources tooltip.orespawn.ore_source.group_colours=Green entries have one alias and one output and need no action. Yellow groups have a saved rule. Red groups need attention. Show All includes green entries. -button.orespawn.ore_source.reset_all=Reset All -button.orespawn.ore_source.confirm_reset_all=Confirm Reset +button.orespawn.ore_source.reset_all=Restore All Orders +button.orespawn.ore_source.confirm_reset_all=Aye, Restore tooltip.orespawn.ore_source.reset_all=Restore built-in material groups and recreate source policies from the currently loaded ores. The change remains pending until the main editor's Done button is used. -screen.orespawn.biomes=Biomes -screen.orespawn.biome_palettes=Biome Palettes -screen.orespawn.biome_palette_settings=Biome Palette Settings -button.orespawn.biome.hide_routine=Hide Routine -button.orespawn.biome.replace_new_terrain=Replace in new terrain with... -button.orespawn.biome.replace_with=Replace in new terrain with %s... -button.orespawn.biome.leave_original=Leave original behaviour -button.orespawn.biome.palettes=Palettes: %s -button.orespawn.biome.reset_selected=Reset Biome -button.orespawn.biome.confirm_reset=Confirm Reset -button.orespawn.biome.reset_palette=Reset Palette -button.orespawn.biome.confirm_reset_palette=Confirm Palette Reset -button.orespawn.biome.reset_dimension=Reset Dimension -button.orespawn.biome.confirm_reset_dimension=Confirm Dimension Reset -button.orespawn.biome.reset_all=Reset All Biomes -button.orespawn.biome.confirm_reset_all=Confirm All-Biome Reset +screen.orespawn.biomes=Biome Charts +screen.orespawn.biome_palettes=Biome Chart Sets +screen.orespawn.biome_palette_settings=Biome Chart Orders +button.orespawn.biome.hide_routine=Hide Ordinary Biomes +button.orespawn.biome.replace_new_terrain=Swap in New Land... +button.orespawn.biome.replace_with=Swap New Land for %s... +button.orespawn.biome.leave_original=Keep the Old Course +button.orespawn.biome.palettes=Chart Sets: %s +button.orespawn.biome.reset_selected=Restore Biome +button.orespawn.biome.confirm_reset=Aye, Restore +button.orespawn.biome.reset_palette=Restore Chart Set +button.orespawn.biome.confirm_reset_palette=Aye, Restore Chart Set +button.orespawn.biome.reset_dimension=Restore Dimension +button.orespawn.biome.confirm_reset_dimension=Aye, Restore Dimension +button.orespawn.biome.reset_all=Restore All Biomes +button.orespawn.biome.confirm_reset_all=Aye, Restore All Biomes value.orespawn.biome.status.user_replaced=Replaced in new terrain value.orespawn.biome.status.layered=Several placement rules value.orespawn.biome.status.disabled=Placement disabled diff --git a/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java b/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java index 4ef3a40d..d50b554f 100644 --- a/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java @@ -25,7 +25,7 @@ class LocalizationParityTest { private static final Path LANG_DIR = Paths.get("src", "main", "resources", "assets", "orespawn", "lang"); private static final Set EXPECTED_LOCALES = ImmutableSet.of( - "de_au.lang", "de_de.lang", "en_ca.lang", "en_en.lang", + "de_at.lang", "de_de.lang", "en_ca.lang", "en_gb.lang", "en_pt.lang", "en_us.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang"); @@ -37,20 +37,20 @@ class LocalizationParityTest { private static final Map> INTENTIONAL_ENGLISH_VALUES = ImmutableMap.>builder() .put("button.orespawn.world_settings", ImmutableSet.of( - "de_au.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", + "de_at.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) .put("button.orespawn.biome_available", ImmutableSet.of( - "de_au.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", + "de_at.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) .put("button.orespawn.biomes_world_materials", ImmutableSet.of( "fr_ca.lang", "fr_fr.lang")) .put("screen.orespawn.biomes", ImmutableSet.of("fr_ca.lang", "fr_fr.lang")) .put("option.orespawn.mod_filter", ImmutableSet.of( - "de_au.lang", "de_de.lang", "fr_ca.lang", "fr_fr.lang")) + "de_at.lang", "de_de.lang", "fr_ca.lang", "fr_fr.lang")) .put("tab.orespawn.biomes", ImmutableSet.of("fr_ca.lang", "fr_fr.lang")) - .put("tab.orespawn.geomes", ImmutableSet.of("de_au.lang", "de_de.lang")) + .put("tab.orespawn.geomes", ImmutableSet.of("de_at.lang", "de_de.lang")) .put("tab.orespawn.placement", ImmutableSet.of("fr_ca.lang", "fr_fr.lang")) .put("tab.orespawn.biome_placement", ImmutableSet.of("fr_ca.lang", "fr_fr.lang")) .put("tab.orespawn.biome_surface", ImmutableSet.of("fr_ca.lang", "fr_fr.lang")) @@ -58,19 +58,19 @@ class LocalizationParityTest { .put("value.orespawn.geology_mode.geome", ImmutableSet.of( "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) - .put("value.orespawn.geology_mode.legacy", ImmutableSet.of("de_au.lang", "de_de.lang")) - .put("value.orespawn.height_distribution.uniform", ImmutableSet.of("de_au.lang", "de_de.lang")) + .put("value.orespawn.geology_mode.legacy", ImmutableSet.of("de_at.lang", "de_de.lang")) + .put("value.orespawn.height_distribution.uniform", ImmutableSet.of("de_at.lang", "de_de.lang")) .put("value.orespawn.height_distribution.triangle", ImmutableSet.of("pt_br.lang")) .put("value.orespawn.ore_pattern.default", ImmutableSet.of( - "de_au.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", + "de_at.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) .put("value.orespawn.ore_pattern.precision", ImmutableSet.of( - "de_au.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", + "de_at.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) .put("value.orespawn.ore_pattern.underfluids", ImmutableSet.of( - "de_au.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", + "de_at.lang", "de_de.lang", "es_es.lang", "es_mx.lang", "fr_ca.lang", "fr_fr.lang", "ja_jp.lang", "ko_kr.lang", "pt_br.lang", "ru_ru.lang", "zh_cn.lang")) .build(); @@ -142,6 +142,18 @@ void everyLocaleMatchesEnglishKeysAndFormatting() throws Exception { "The locale-specific English exception list is stale; review and narrow it"); } + @Test + void pirateSpeakUsesItsOwnCaptionsAndGuide() throws Exception { + JsonObject english = read(LANG_DIR.resolve("en_us.lang")); + JsonObject pirate = read(LANG_DIR.resolve("en_pt.lang")); + for (String key : new String[] { + "screen.orespawn.world_settings", "button.orespawn.recommended", + "button.orespawn.help", "guide.orespawn.welcome.1", "guide.orespawn.server.2" + }) { + assertFalse(english.get(key).equals(pirate.get(key)), key + " should be in Pirate Speak"); + } + } + private static int formatArgumentCount(String value) { int count = 0; for (int i = 0; i < value.length() - 1; i++) { diff --git a/src/test/java/zone/moddev/mc/orespawn/api/ExactHostStateApiTest.java b/src/test/java/zone/moddev/mc/orespawn/api/ExactHostStateApiTest.java new file mode 100644 index 00000000..0271dc67 --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/api/ExactHostStateApiTest.java @@ -0,0 +1,92 @@ +package zone.moddev.mc.orespawn.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.Test; + +import net.minecraft.util.ResourceLocation; + +class ExactHostStateApiTest { + + @Test + void publicBuilderCanSelectPlainStoneWithoutItsIgneousVariants() throws Exception { + WorldgenProvider.OreDimensionDefinition.Builder builder = + WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld")); + Method hostState = builder.getClass().getMethod("hostState", ResourceLocation.class, int.class); + hostState.invoke(builder, new ResourceLocation("minecraft:stone"), 0); + + assertEquals("[{\"block\":\"minecraft:stone\",\"metadata\":0}]", + builder.build().toJson().getAsJsonArray("host_blocks").toString()); + } + + @Test + void exactHostsAreImmutableWeightedAndDoNotChangeOldBlockSelectors() { + ResourceLocation stone = new ResourceLocation("minecraft:stone"); + WorldgenProvider.OreDimensionDefinition.Builder builder = + WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld")); + String original = builder.hostBlock(stone).build().toJson().toString(); + WorldgenProvider.OreDimensionDefinition exact = builder.hostState(stone, 0, 0.5D).build(); + + assertEquals(0.5D, exact.hostStates().get(stone).get(0)); + assertThrows(UnsupportedOperationException.class, () -> exact.hostStates().clear()); + assertThrows(UnsupportedOperationException.class, () -> exact.hostStates().get(stone).put(1, 1.0D)); + builder.hostState(stone, 1); + assertEquals(1, exact.hostStates().get(stone).size()); + assertEquals(original, WorldgenProvider.OreDimensionDefinition + .builder(new ResourceLocation("minecraft:overworld")).hostBlock(stone).build().toJson().toString()); + assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, -1)); + assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, 16)); + assertThrows(NullPointerException.class, () -> builder.hostState(null, 0)); + } + + @Test + void rejectedWeightsDoNotBypassTheNoHostCheck() { + assertAll("invalid weights", () -> rejectWithoutAddingHost(-0.1D), + () -> rejectWithoutAddingHost(1.1D), () -> rejectWithoutAddingHost(Double.NaN), + () -> rejectWithoutAddingHost(Double.POSITIVE_INFINITY), + () -> rejectWithoutAddingHost(Double.NEGATIVE_INFINITY)); + } + + private static void rejectWithoutAddingHost(double weight) { + WorldgenProvider.OreDimensionDefinition.Builder builder = + WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld")); + assertThrows(IllegalArgumentException.class, + () -> builder.hostState(new ResourceLocation("minecraft:stone"), 0, weight)); + assertThrows(IllegalStateException.class, builder::build, + "A rejected host must not make an empty rule valid: " + weight); + } + + @Test + void rejectedWeightsLeaveExistingHostsUnchanged() { + ResourceLocation stone = new ResourceLocation("minecraft:stone"); + ResourceLocation dirt = new ResourceLocation("minecraft:dirt"); + WorldgenProvider.OreDimensionDefinition.Builder builder = + WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld")) + .hostState(stone, 0, 0.5D); + String original = builder.build().toJson().toString(); + assertThrows(IllegalArgumentException.class, () -> builder.hostState(dirt, 0, Double.NaN)); + assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, 0, 2.0D)); + WorldgenProvider.OreDimensionDefinition result = builder.build(); + assertFalse(result.hostStates().containsKey(dirt)); + assertEquals(0.5D, result.hostStates().get(stone).get(0)); + assertEquals(original, result.toJson().toString()); + } + + @Test + void endpointsAndSeveralVariantsSerializeInInsertionOrder() { + WorldgenProvider.OreDimensionDefinition value = + WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld")) + .hostState(new ResourceLocation("minecraft:stone"), 0, 0.0D) + .hostState(new ResourceLocation("minecraft:stone"), 1, 0.5D) + .hostState(new ResourceLocation("minecraft:stone"), 6, 1.0D).build(); + assertEquals("[{\"block\":\"minecraft:stone\",\"metadata\":0,\"weight\":0.0}," + + "{\"block\":\"minecraft:stone\",\"metadata\":1,\"weight\":0.5}," + + "{\"block\":\"minecraft:stone\",\"metadata\":6}]", + value.toJson().getAsJsonArray("host_blocks").toString()); + } +} diff --git a/src/test/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacementsTest.java b/src/test/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacementsTest.java new file mode 100644 index 00000000..c23795b8 --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/integration/LegacyOreReplacementsTest.java @@ -0,0 +1,178 @@ +package zone.moddev.mc.orespawn.integration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import zone.moddev.mc.orespawn.util.JsonCopies; + +class LegacyOreReplacementsTest { + private static final String PROVIDER = "baseminerals"; + private static final String OLD = "baseminerals:legacy/baseminerals_lithium_ore_0"; + private static final String NATIVE = "baseminerals:legacy/lithium_ore"; + private static final String FALLBACK_POLICY = "orespawn:lithium|orespawn:all_except_nether_end"; + + private final JsonObject profile = parse("{\"ores\":{},\"ore_source_policies\":{}}"); + private final JsonObject ores = profile.getAsJsonObject("ores"); + private final JsonObject policies = profile.getAsJsonObject("ore_source_policies"); + private final JsonObject defaults = new JsonObject(); + private final JsonObject manifest = new JsonObject(); + private final Set known = new LinkedHashSet<>(Arrays.asList(OLD)); + + LegacyOreReplacementsTest() { + ores.add(OLD, parse("{\"block\":\"baseminerals:lithium_ore\",\"enabled\":false," + + "\"source_mod\":\"baseminerals\",\"source_provider\":\"baseminerals\"," + + "\"native_generation\":false,\"outputs\":[{\"block\":\"baseminerals:lithium_ore\"}]," + + "\"dimensions\":{\"minecraft:overworld\":{\"frequency\":1.5}}}")); + defaults.add(NATIVE, parse("{\"block\":\"baseminerals:lithium_ore\",\"enabled\":true," + + "\"dimensions\":{\"minecraft:overworld\":{\"frequency\":4.0}}," + + "\"dimension_selectors\":{\"orespawn:all_except_nether_end\":{\"frequency\":4.0}}}")); + } + + @Test + void remembersImportAndPreservesItsScopeAndChoicesOnEveryReload() { + policies.add("existing", parse("{\"mode\":\"consolidated\",\"output_mode\":\"single\",\"outputs\":{\"" + OLD + "\":1}}")); + JsonObject saved = JsonCopies.copy(profile); + assertTrue(merge().suppressed.contains(NATIVE)); + assertEquals(OLD, manifest.getAsJsonObject("legacy_ore_replacements").get(NATIVE).getAsString()); + assertEquals(saved, profile); + assertTrue(LegacyOreReplacements.retains(manifest, OLD, JsonCopies.keys(defaults))); + assertFalse(merge().changed); + // Removing a previously tracked import is not permission to add the native default. + ores.remove(OLD); + assertTrue(merge().suppressed.contains(NATIVE)); + } + + @Test + void dropsOnlyTheUntouchedAddedDefaultAndItsGeneratedFallback() { + addNative(); + policies.add(FALLBACK_POLICY, parse("{\"material\":\"orespawn:lithium\",\"domain\":\"orespawn:all_except_nether_end\"," + + "\"mode\":\"keep_separate\",\"output_mode\":\"custom\",\"status\":\"separate\"," + + "\"review_required\":false,\"outputs\":{\"" + NATIVE + "\":1}," + + "\"placement_sources\":{\"orespawn:standard\":\"" + NATIVE + "\"}}")); + JsonObject original = JsonCopies.copy(ores.getAsJsonObject(OLD)); + assertTrue(merge().changed); + assertFalse(ores.has(NATIVE)); + assertFalse(policies.has(FALLBACK_POLICY)); + assertEquals(original, ores.get(OLD)); + assertFalse(merge().changed); + } + + @Test + void emptyScopeAddedByOreSourceDiscoveryIsNotAUserEdit() { + defaults.getAsJsonObject(NATIVE).remove("dimension_selectors"); + addNative(); + ores.getAsJsonObject(NATIVE).add("dimension_selectors", new JsonObject()); + + assertTrue(merge().suppressed.contains(NATIVE)); + assertFalse(ores.has(NATIVE)); + } + + @Test + void keepsANativeRuleWithAnAddedPlacementScope() { + defaults.getAsJsonObject(NATIVE).remove("dimension_selectors"); + addNative(); + ores.getAsJsonObject(NATIVE).add("dimension_selectors", + parse("{\"orespawn:all_except_nether_end\":{\"frequency\":1}}")); + JsonObject before = JsonCopies.copy(profile); + + assertFalse(merge().suppressed.contains(NATIVE)); + assertEquals(before, profile); + assertTrue(manifest.getAsJsonObject("legacy_ore_review").has(NATIVE)); + } + + @Test + void retainsAnEditedNativeRuleAndAsksForReview() { + addNative(); + ores.getAsJsonObject(NATIVE).addProperty("enabled", false); + JsonObject before = JsonCopies.copy(profile); + assertFalse(merge().suppressed.contains(NATIVE)); + assertEquals(before, profile); + assertTrue(manifest.getAsJsonObject("legacy_ore_review").has(NATIVE)); + assertFalse(merge().changed); + } + + @Test + void retainsAnUntouchedNativeRuleWhenPlayerPolicyUsesIt() { + addNative(); + policies.add("existing", parse("{\"mode\":\"consolidated\",\"output_mode\":\"single\",\"outputs\":{\"" + NATIVE + "\":1}}")); + JsonObject before = JsonCopies.copy(profile); + assertFalse(merge().suppressed.contains(NATIVE)); + assertEquals(before, profile); + assertTrue(manifest.getAsJsonObject("legacy_ore_review").has(NATIVE)); + } + + @Test + void doesNotGuessForMultipleImportsOrMultipleNativeRules() { + String second = OLD.substring(0, OLD.length() - 1) + "1"; + known.add(second); + ores.add(second, JsonCopies.copy(ores.get(OLD))); + addNative(); + assertTrue(merge().suppressed.contains(NATIVE)); + assertTrue(ores.has(NATIVE)); + assertFalse(manifest.has("legacy_ore_replacements")); + assertTrue(manifest.getAsJsonObject("legacy_ore_review").has(NATIVE)); + + ores.remove(second); + defaults.add("baseminerals:other_lithium", JsonCopies.copy(defaults.get(NATIVE))); + assertTrue(merge().suppressed.contains(NATIVE)); + assertTrue(ores.has(NATIVE)); + assertFalse(manifest.has("legacy_ore_replacements")); + } + + @Test + void doesNotMatchAnUntrackedRuleOrAnotherOutputVariant() { + known.clear(); + assertFalse(merge().suppressed.contains(NATIVE)); + known.add(OLD); + defaults.getAsJsonObject(NATIVE).addProperty("metadata", 1); + assertFalse(merge().suppressed.contains(NATIVE)); + ores.getAsJsonObject(OLD).addProperty("source_provider", "anothermod"); + defaults.getAsJsonObject(NATIVE).remove("metadata"); + assertFalse(merge().suppressed.contains(NATIVE)); + assertFalse(manifest.has("legacy_ore_replacements")); + } + + @Test + void recognizesStockDuplicateEvenWhenTheOldConvertedFileIsActive() { + addNative(); + JsonObject active = new JsonObject(); + active.add(OLD, JsonCopies.copy(ores.get(OLD))); + assertTrue(LegacyOreReplacements.reconcile(profile, PROVIDER, ores, defaults, active, manifest, known) + .suppressed.contains(NATIVE)); + assertFalse(ores.has(NATIVE)); + } + + @Test + void retainsADefaultChangedByAProviderOverride() { + addNative(); + JsonObject active = JsonCopies.copy(defaults); + active.getAsJsonObject(NATIVE).addProperty("enabled", false); + assertFalse(LegacyOreReplacements.reconcile(profile, PROVIDER, ores, defaults, active, manifest, known) + .suppressed.contains(NATIVE)); + assertTrue(ores.has(NATIVE)); + assertTrue(manifest.getAsJsonObject("legacy_ore_review").has(NATIVE)); + } + + private void addNative() { + JsonObject nativeRule = JsonCopies.copy(defaults.getAsJsonObject(NATIVE)); + nativeRule.addProperty("source_provider", PROVIDER); + ores.add(NATIVE, nativeRule); + known.add(NATIVE); + } + + private LegacyOreReplacements.Result merge() { + return LegacyOreReplacements.reconcile(profile, PROVIDER, ores, defaults, defaults, manifest, known); + } + + private static JsonObject parse(String source) { + return new JsonParser().parse(source).getAsJsonObject(); + } +} diff --git a/src/test/java/zone/moddev/mc/orespawn/worldgen/ExactHostStateBakeTest.java b/src/test/java/zone/moddev/mc/orespawn/worldgen/ExactHostStateBakeTest.java new file mode 100644 index 00000000..5623d356 --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/worldgen/ExactHostStateBakeTest.java @@ -0,0 +1,56 @@ +package zone.moddev.mc.orespawn.worldgen; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Random; + +import com.google.gson.JsonObject; + +import org.junit.jupiter.api.Test; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.util.ResourceLocation; + +import zone.moddev.mc.orespawn.api.WorldgenProvider; +import zone.moddev.mc.orespawn.test.Forge14TestBootstrap; + +class ExactHostStateBakeTest { + @Test + void providerStatesReachTheExistingMatcherWithoutAcceptingSiblingVariants() throws Exception { + Forge14TestBootstrap.registerVanilla(); + if (zone.moddev.mc.orespawn.init.OreSpawnPatterns.registry() + .getValue(new ResourceLocation("orespawn:default")) == null) { + zone.moddev.mc.orespawn.init.OreSpawnPatterns.registerTypes(new net.minecraftforge.event.RegistryEvent.Register<>( + zone.moddev.mc.orespawn.api.OreSpawnPatternRegistry.REGISTRY_NAME, + zone.moddev.mc.orespawn.init.OreSpawnPatterns.registry())); + } + JsonObject provider = WorldgenProvider.builder("coaltest", 1) + .ore(new ResourceLocation("coaltest:coal"), new ResourceLocation("minecraft:coal_ore"), + ore -> ore.dimension(WorldIds.OVERWORLD, placement -> placement + .hostState(new ResourceLocation("minecraft:stone"), 0))) + .build().toJson(); + provider.addProperty("manage_vanilla_ores", true); + String before = provider.toString(); + + Method geology = GeomeConfig.class.getDeclaredMethod("bake", JsonObject.class, ResourceLocation.class); + geology.setAccessible(true); + BakedGeomeConfig config = (BakedGeomeConfig) geology.invoke(null, new JsonObject(), WorldIds.OVERWORLD); + Method bake = OreSpawnOreGeneration.class.getDeclaredMethod("bakeOres", JsonObject.class, BakedGeomeConfig.class); + bake.setAccessible(true); + Object baked = bake.invoke(null, provider, config); + Field byDimension = baked.getClass().getDeclaredField("byDimension"); + byDimension.setAccessible(true); + Object[] rules = (Object[]) ((Map) byDimension.get(baked)).get(WorldIds.OVERWORLD); + assertEquals(1, rules.length); + Method accepts = rules[0].getClass().getDeclaredMethod("accepts", IBlockState.class, Random.class, BakedGeomeConfig.class); + accepts.setAccessible(true); + for (int metadata = 0; metadata <= 6; metadata++) { + assertEquals(metadata == 0, accepts.invoke(rules[0], Blocks.STONE.getStateFromMeta(metadata), new Random(1), config)); + } + assertEquals(before, provider.toString()); + } +} diff --git a/src/test/java/zone/moddev/mc/orespawn/worldgen/OreSourcePoliciesTest.java b/src/test/java/zone/moddev/mc/orespawn/worldgen/OreSourcePoliciesTest.java index 125e6547..1b633427 100644 --- a/src/test/java/zone/moddev/mc/orespawn/worldgen/OreSourcePoliciesTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/worldgen/OreSourcePoliciesTest.java @@ -373,6 +373,26 @@ void separateLegacyPolicyForOneLoadedOutputIsHiddenWithoutBeingDeleted() { "All Groups must retain harmless single-output material groups"); } + @Test + void baseElectricIsARecognizedOutputWithoutAddingAPlacementBudget() { + JsonObject root = rootWithSulfurConflict(); + addOre(root, "baseelectric:sulfur", "baseelectric", "minecraft:coal_ore", + "orespawn:sulfur", "minecraft:overworld", null); + root.getAsJsonObject("ores").getAsJsonObject("baseelectric:sulfur") + .addProperty("enabled", false); + + OreSourcePolicies.initialize(root, false); + + JsonObject policy = policy(root); + assertEquals("consolidated", policy.get("mode").getAsString()); + assertEquals("balanced", policy.get("output_mode").getAsString()); + assertEquals(3, policy.getAsJsonObject("outputs").entrySet().size()); + assertTrue(policy.getAsJsonObject("outputs").has("baseelectric:sulfur")); + JsonObject sources = policy.getAsJsonObject("placement_sources"); + assertEquals(1, sources.entrySet().size()); + assertEquals("mineralogy:sulfur", sources.get("orespawn:standard").getAsString()); + } + @Test void inactiveManagedProviderRemainsAnOutputWithoutOwningPlacement() { JsonObject root = root();