diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dcf9495..1d5020a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,9 +79,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/OreSpawn-4.1.0.121012.jar - build/libs/OreSpawn-4.1.0.121012-sources.jar - build/libs/OreSpawn-4.1.0.121012-javadoc.jar + build/libs/OreSpawn-4.1.1.121012.jar + build/libs/OreSpawn-4.1.1.121012-sources.jar + build/libs/OreSpawn-4.1.1.121012-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 8213079b..de4c6d03 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,12 @@ +Version 4.1.1.121012 + +* Use each dimension's actual height for rock strata, including deeper and + taller worlds supplied by other mods or datapacks. +* Keep native rock where no configured rock is eligible, rather than replacing + it with stone. Preserve saved ranges and leave existing chunks untouched. +* Allow rock height settings outside the vanilla range and explain how rock + weights differ from a geome's family weights. + Version 4.1.0.121012 * Keep imported vanilla ore settings inactive while Manage Vanilla Ores is off. diff --git a/README.md b/README.md index b9b0ad65..dbc64b0d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ 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. +Rock strata use each dimension's actual build height, including deeper or +taller worlds supplied by mods and datapacks. There is no separate OreSpawn +world-floor setting to keep in sync. Explicit rock and ore ranges still apply; +uncovered heights keep the terrain generator's original rock. + Its OS3 compatibility layer preserves ranged legacy block budgets, exclusive legacy height ceilings, and the historical "all dimensions except Nether and End" policy used by mods such as Base Metals. @@ -102,10 +107,10 @@ exported to `config/orespawn-guide/` without overwriting existing files. ## Building -This branch builds target-qualified version `4.1.0.121012`: the OreSpawn 4.1 +This branch builds target-qualified version `4.1.1.121012`: the OreSpawn 4.1 feature set for Minecraft 1.21.1 and NeoForge. -OreSpawn 4.1.0.121012 uses NeoGradle 7.1.38 and Gradle 9.2.1. Use exact +OreSpawn 4.1.1.121012 uses NeoGradle 7.1.38 and Gradle 9.2.1. Use exact Temurin 21.0.7+6 from the repository root: ```powershell @@ -130,7 +135,7 @@ Use `runClient` or `runTestOreSourcesClient` from Eclipse's Gradle Tasks view. NeoGradle does not need ForgeGradle's `genEclipseRuns` task or standalone `.launch` files. -The audited public outputs are `OreSpawn-4.1.0.121012.jar`, its sources jar, +The audited public outputs are `OreSpawn-4.1.1.121012.jar`, its sources jar, and its Javadoc jar. CI additionally proves a cache-cold NeoGradle bootstrap and a same-cache offline rerun. diff --git a/docs/API.md b/docs/API.md index e59ccf5b..ffe2baf1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -163,6 +163,12 @@ Layers when a close geome transition is staggered by layer, even when later surface work changes the final heightmap slightly. Sampling is read-only and is intended for gameplay decisions, diagnostics, and compatible generation outside OreSpawn's block loops. +The sampler uses the level's actual build limits, including extended-height +dimensions. It samples configured strata, not blocks already in the world. +Where no configured rock is eligible, generation leaves native terrain alone, +`familyAt` is empty, and `rockAt` retains its Stone fallback. That fallback is +not a prediction of the other generator's rock; inspect existing terrain if +you need its actual block. Callbacks inside OreSpawn generation loops are intentionally unsupported. Custom pattern mods create a NeoForge `DeferredRegister` using diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 018b6ec8..5a9190e5 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -124,12 +124,32 @@ fresh pack needs to author. ## Rocks And Geomes -A rock requires `enabled`, `family`, `depth_peak`, `depth_spread`, `min_y`, +A rock uses `enabled`, `family`, `depth_peak`, `depth_spread`, `min_y`, `max_y`, `weight`, and `ore_replaceable`. Provider definitions should also set `block`; global/world entries may use the rule ID as the block ID when `block` is omitted. `dimensions` limits membership, and `geomes` multiplies selection weight by province. A weight of zero prevents selection in that context. +Rock heights are absolute world Y coordinates. On this Minecraft target the +editor accepts -2032 through 2031, and generation is limited to the actual +dimension's build range. OreSpawn reads that range at runtime, so a modded +floor such as -128 needs no matching OreSpawn height setting. Omitting a +rock's `min_y` or `max_y` leaves that end unrestricted within the dimension. +Explicit saved values stay unchanged: a rule with `min_y: -64` still stops at +-64 until you edit it. The same applies to provider declarations and imported +rules. Explicit ore and fluid-deposit ranges are not stretched automatically. + +`depth_peak` and `depth_spread` describe a preference, not hard cutoffs. The +rock's `min_y` and `max_y` decide where it is eligible. Where no rock is +eligible, the terrain generator's original rock is left in place, including +deepslate. These changes affect new terrain only; strata have no retrogen pass. + +Rock weights are relative to other eligible rocks in the same family. A low +andesite weight can still win every volcanic layer if it is the only eligible +volcanic rock. To reduce the whole family's share, lower that family's weight +in the relevant geomes. To exclude an individual rock, disable it or set its +weight to zero. Geome-specific rock weights can narrow the choice further. + Geomes contain a non-negative `base` weight and non-negative weights for each rock family. Keys may retain the legacy unnamespaced form or use a provider resource ID such as `examplemod:crystal_basin`; the creation editor preserves diff --git a/docs/PLAYER_GUIDE.md b/docs/PLAYER_GUIDE.md index 0f9e8563..25b193d0 100644 --- a/docs/PLAYER_GUIDE.md +++ b/docs/PLAYER_GUIDE.md @@ -85,6 +85,18 @@ replace. Removing a rock from generation does not unregister its block or recipes. It only prevents that rock appearing in newly generated terrain. +Rock weight is not an overall percentage. It compares that rock with the other +eligible rocks in its family. For example, reducing andesite's weight has +little effect if it is the only volcanic rock available. Lower the volcanic +family weight in the relevant geomes to reduce volcanic layers, or disable +andesite to exclude it completely. + +For deeper or taller worlds, OreSpawn detects the dimension's real height. +You do not need to enter the world's floor a second time. Check the rocks' +own Minimum Y and Maximum Y, though: saved limits are kept exactly as entered. +For a floor of -128, an old rock rule starting at -64 will not extend down +automatically. Where no rock rule applies, native stone or deepslate remains. + ## Existing Worlds And Servers Each world stores its final choices in: diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index a66be26c..b0354ce6 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -27,6 +27,15 @@ including vanilla granite/diorite/andesite/tuff features, untouched. Formation controls remain inert until a profile contains eligible rocks and an enabled terrain-replacement dimension. +Rock heights use absolute world Y, not distance from the bottom. Omitted rock +limits and the Java rock builder's defaults cover the dimension's full build +range. Explicit limits remain authoritative, including values such as +`min_y: -128`; they are not rewritten when a terrain mod changes the world's +height. The runtime clips generation to each dimension's actual bounds and +leaves native rock wherever no declared rock is eligible. No Tectonic-specific +configuration or integration hook is needed. Ore and fluid-deposit height +ranges remain independent and are not extended automatically. + Rule IDs in `rocks` and `ores` must use the provider namespace. They are stable ownership keys, not necessarily block IDs. Set `block` for one output or `outputs` for a weighted list: diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 69fa71a8..eea59b53 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -19,6 +19,19 @@ Confirm the dimension is enabled in `terrain_dimensions`, its hosts resolve, at least one enabled rock includes that dimension, and biome restrictions match. OreSpawn does not replace Nether or End terrain by default. +## Rocks stop at the old world floor + +OreSpawn reads each dimension's actual build range at runtime, including +deeper and taller dimensions supplied by terrain mods or datapacks. There is +no separate world-floor setting to duplicate. Check each rock's explicit +`min_y` and `max_y`: an existing `min_y: -64` remains a deliberate limit even +if the world now reaches -128. Edit that rule if you want it to extend lower. + +Where no rock is eligible, native stone or deepslate stays in place. Test in +newly generated chunks. A fix cannot reconstruct deepslate that an earlier +version already replaced, and rock strata are never retro-generated. Keep +world backups before changing a terrain mod's height settings. + ## A custom biome does not appear Confirm its registry ID resolves, its palette targets the intended dimension, diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 53f116ec..4427580f 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -62,7 +62,7 @@ Examples: | 1.20.6 | Forge | `120061` | `4.0.16.120061` | | 1.20.6 | NeoForge | `120062` | `4.0.16.120062` | | 1.21.1 | Forge | `121011` | `4.0.16.121011` | -| 1.21.1 | NeoForge | `121012` | `4.1.0.121012` | +| 1.21.1 | NeoForge | `121012` | `4.1.1.121012` | | 1.21.11 | Forge | `121111` | `4.0.6.121111` | | 1.21.11 | NeoForge | `121112` | `4.0.6.121112` | | 26.1.2 | Forge | `2601021` | `4.0.6.2601021` | diff --git a/docs/schemas/profile-common.schema.json b/docs/schemas/profile-common.schema.json index c76a11e9..e38653ec 100644 --- a/docs/schemas/profile-common.schema.json +++ b/docs/schemas/profile-common.schema.json @@ -81,15 +81,15 @@ }, "rock": { "type": "object", - "required": ["enabled", "family", "depth_peak", "depth_spread", "min_y", "max_y", "weight", "ore_replaceable"], + "required": ["enabled", "family", "depth_peak", "depth_spread", "weight", "ore_replaceable"], "properties": { "block": { "$ref": "#/$defs/registryId" }, "enabled": { "type": "boolean" }, "family": { "$ref": "#/$defs/family" }, "depth_peak": { "type": "integer", "minimum": -2048, "maximum": 2048 }, "depth_spread": { "type": "integer", "minimum": 1 }, - "min_y": { "type": "integer", "minimum": -2048, "maximum": 2048 }, - "max_y": { "type": "integer", "minimum": -2048, "maximum": 2048 }, + "min_y": { "type": "integer", "minimum": -2048, "maximum": 2048, "description": "Lowest absolute world Y. If omitted, the dimension's lower build limit applies." }, + "max_y": { "type": "integer", "minimum": -2048, "maximum": 2048, "description": "Highest absolute world Y. If omitted, the dimension's upper build limit applies." }, "weight": { "type": "number", "minimum": 0 }, "ore_replaceable": { "type": "boolean" }, "geomes": { "$ref": "#/$defs/weights" }, diff --git a/gradle.properties b/gradle.properties index 810d248f..62f26fc0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,7 +39,7 @@ mod_name=MMD OreSpawn # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-2.1 # Four numeric parts: feature version followed by this Minecraft and loader target. -mod_version=4.1.0.121012 +mod_version=4.1.1.121012 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index 0f277f37..fec8ebe2 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -50,7 +50,7 @@ tasks.named('assemble') { tasks.register('verifyReleaseConfiguration') { group = 'verification' doLast { - if (project.mod_version != '4.1.0.121012' + if (project.mod_version != '4.1.1.121012' || project.mod_group_id != expectedMavenGroup || project.minecraft_version != '1.21.1' || project.neo_version != '21.1.247') { @@ -63,9 +63,9 @@ tasks.register('verifyReleaseConfiguration') { throw new GradleException('Unexpected dispatcher or Java target metadata') } List expectedPublicArtifacts = [ - 'OreSpawn-4.1.0.121012.jar', - 'OreSpawn-4.1.0.121012-sources.jar', - 'OreSpawn-4.1.0.121012-javadoc.jar' + 'OreSpawn-4.1.1.121012.jar', + 'OreSpawn-4.1.1.121012-sources.jar', + 'OreSpawn-4.1.1.121012-javadoc.jar' ] if (base.archivesName.get() != expectedMavenArtifact || expectedReleaseFiles.get().collect { it.toString() } != expectedPublicArtifacts) { diff --git a/gradle/verification/client-and-benchmark.gradle b/gradle/verification/client-and-benchmark.gradle index 049a9206..5484c31b 100644 --- a/gradle/verification/client-and-benchmark.gradle +++ b/gradle/verification/client-and-benchmark.gradle @@ -137,7 +137,7 @@ tasks.register('clientIntegrationProcess') { if (!marker.isFile()) throw new GradleException('The client UI probe did not finish') Properties result = new Properties() marker.withInputStream { result.load(it) } - for (String key : ['ore_sources_opened', 'mods_opened', 'first_world_rendered', 'reload_rendered']) { + for (String key : ['ore_sources_opened', 'mods_opened', 'extended_rock_height_roundtrip', 'first_world_rendered', 'reload_rendered']) { if (result.getProperty(key) != 'true') throw new GradleException("Client UI probe missed ${key}") } shared.assertRuntimeLogsClean(clientIntegrationRunDirectory, diff --git a/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/clientprobe/ClientProbeTestMod.java b/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/clientprobe/ClientProbeTestMod.java index 95599ddf..c7c1c6ad 100644 --- a/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/clientprobe/ClientProbeTestMod.java +++ b/src/clientIntegrationTest/java/zone/moddev/mc/orespawn/clientprobe/ClientProbeTestMod.java @@ -57,6 +57,7 @@ public final class ClientProbeTestMod { private int editorFrames; private boolean worldSettingsOpened; private boolean longEditorRoundTrip; + private boolean extendedRockHeightRoundTrip; private boolean oreSourcesOpened; private boolean modsOpened; private int creationBiomeCount; @@ -135,6 +136,11 @@ && activateWorldSettings(minecraft.screen, worldCreationButtons)) { MainMenuLayoutProbe.verify(creation); validateCaptions(minecraft.screen); validateLongEditorRoundTrip(minecraft, minecraft.screen); + try { + validateExtendedRockHeights(minecraft, minecraft.screen); + } catch (ReflectiveOperationException failure) { + throw new IllegalStateException("Could not exercise the extended-height rock editor", failure); + } nextState(3); } break; @@ -399,6 +405,37 @@ private static Object newEditorSession(JsonObject root) { } } + private void validateExtendedRockHeights(Minecraft minecraft, Screen parent) throws ReflectiveOperationException { + for (int[] size : new int[][] { { 426, 265 }, { 320, 240 } }) { + JsonObject root = WorldGeologyProfile.recommended(false).rootCopy(); + JsonObject rocks = new JsonObject(); + JsonObject rock = new JsonObject(); + rock.addProperty("family", "metamorphic"); + rocks.add("minecraft:basalt", rock); + root.add("rocks", rocks); + Object session = newEditorSession(root); + Class type = Class.forName("zone.moddev.mc.orespawn.client.RockEntryScreen"); + var constructor = type.getDeclaredConstructor(Screen.class, session.getClass(), String.class); + constructor.setAccessible(true); + Screen screen = (Screen) constructor.newInstance(parent, session, "minecraft:basalt"); + initializeScreen(screen, minecraft, size[0], size[1]); + for (String field : new String[] { "minY", "maxY", "peak" }) { + var input = type.getDeclaredField(field); + input.setAccessible(true); + ((net.minecraft.client.gui.components.EditBox) input.get(screen)) + .setValue(field.equals("minY") ? "-128" : field.equals("maxY") ? "-65" : "-96"); + } + pressDone(screen); + JsonObject saved = com.google.gson.JsonParser.parseString(editorSessionRoot(session)) + .getAsJsonObject().getAsJsonObject("rocks").getAsJsonObject("minecraft:basalt"); + if (saved.get("min_y").getAsInt() != -128 || saved.get("max_y").getAsInt() != -65 + || saved.get("depth_peak").getAsInt() != -96) { + throw new IllegalStateException("The rock editor rejected extended heights at " + size[0] + "x" + size[1]); + } + } + extendedRockHeightRoundTrip = true; + } + private static Screen newDimensionScreen(String simpleName, Screen parent, Object session, String ruleId, String dimensionId) { try { @@ -431,6 +468,10 @@ private static JsonArray values(String... entries) { } private static void initializeScreen(Screen screen, Minecraft minecraft) { + initializeScreen(screen, minecraft, 640, 480); + } + + private static void initializeScreen(Screen screen, Minecraft minecraft, int width, int height) { for (Method method : Screen.class.getDeclaredMethods()) { Class[] parameters = method.getParameterTypes(); if (parameters.length != 3 || parameters[0] != Minecraft.class @@ -439,13 +480,13 @@ private static void initializeScreen(Screen screen, Minecraft minecraft) { || !java.lang.reflect.Modifier.isFinal(method.getModifiers())) continue; try { method.setAccessible(true); - method.invoke(screen, minecraft, 640, 480); + method.invoke(screen, minecraft, width, height); return; } catch (ReflectiveOperationException failure) { throw new IllegalStateException("Could not initialize target-native editor", failure); } } - throw new IllegalStateException("Could not locate NeoForge 20.6 Screen initialization method"); + throw new IllegalStateException("Could not locate the Screen initialization method"); } private static void pressDone(Screen screen) { @@ -593,6 +634,7 @@ private void writeMarker() throws IOException { values.setProperty("main_menu_layout", Boolean.toString(MainMenuLayoutProbe.completed())); values.setProperty("world_settings_opened", Boolean.toString(worldSettingsOpened)); values.setProperty("long_editor_roundtrip", Boolean.toString(longEditorRoundTrip)); + values.setProperty("extended_rock_height_roundtrip", Boolean.toString(extendedRockHeightRoundTrip)); values.setProperty("creation_biome_count", Integer.toString(creationBiomeCount)); values.setProperty("static_biome_count", Integer.toString(staticBiomeCount)); values.setProperty("loaded_biome_count", Integer.toString(loadedBiomeCount)); diff --git a/src/main/java/zone/moddev/mc/orespawn/api/GeologyColumn.java b/src/main/java/zone/moddev/mc/orespawn/api/GeologyColumn.java index 28ec8155..aa2233c8 100644 --- a/src/main/java/zone/moddev/mc/orespawn/api/GeologyColumn.java +++ b/src/main/java/zone/moddev/mc/orespawn/api/GeologyColumn.java @@ -13,6 +13,8 @@ public interface GeologyColumn { int blockX(); int blockZ(); int surfaceY(); + /** Configured rock choice, or Stone when no configured rock covers this height. */ BlockState rockAt(int y); + /** Empty where OreSpawn leaves the native generator's rock unchanged. */ Optional familyAt(int y); } diff --git a/src/main/java/zone/moddev/mc/orespawn/api/OreSpawnGeologySampler.java b/src/main/java/zone/moddev/mc/orespawn/api/OreSpawnGeologySampler.java index e0387099..80bcafee 100644 --- a/src/main/java/zone/moddev/mc/orespawn/api/OreSpawnGeologySampler.java +++ b/src/main/java/zone/moddev/mc/orespawn/api/OreSpawnGeologySampler.java @@ -34,7 +34,7 @@ final class OreSpawnGeologySampler implements GeologySampler { private OreSpawnGeologySampler(ServerLevel level) { this.level = level; dimension = level.dimension().location(); - config = GeomeConfig.baked(level.dimension()); + config = GeomeConfig.baked(level.dimension(), level); WorldGeologyProfile profile = WorldGeologyProfileManager.activeProfile(); mode = profile.geologyMode(); if (mode == GeologyMode.LEGACY) { 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 26880754..0146f30d 100644 --- a/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java +++ b/src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java @@ -18,6 +18,7 @@ import zone.moddev.mc.orespawn.util.JsonCopies; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.dimension.DimensionType; /** * Immutable declarative world-generation contribution submitted to OreSpawn. @@ -311,8 +312,8 @@ public static final class Builder { private boolean enabled = true; private int depthPeak = 48; private int depthSpread = 40; - private int minY = -64; - private int maxY = 319; + private int minY = DimensionType.MIN_Y; + private int maxY = DimensionType.MAX_Y; private double weight = 1.0D; private boolean oreReplaceable = true; private final Map geomes = new LinkedHashMap<>(); diff --git a/src/main/java/zone/moddev/mc/orespawn/client/GeologyEditorSession.java b/src/main/java/zone/moddev/mc/orespawn/client/GeologyEditorSession.java index 3e9b99b1..79644cd9 100644 --- a/src/main/java/zone/moddev/mc/orespawn/client/GeologyEditorSession.java +++ b/src/main/java/zone/moddev/mc/orespawn/client/GeologyEditorSession.java @@ -35,6 +35,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; @@ -306,8 +307,8 @@ void assignRock(String id, RockFamily family) { rock.addProperty("family", family.configName); rock.addProperty("depth_peak", defaultPeak(family)); rock.addProperty("depth_spread", 40); - rock.addProperty("min_y", -64); - rock.addProperty("max_y", 319); + rock.addProperty("min_y", DimensionType.MIN_Y); + rock.addProperty("max_y", DimensionType.MAX_Y); rock.addProperty("weight", 1.0D); rock.addProperty("ore_replaceable", true); rock.add("geomes", new JsonObject()); @@ -323,9 +324,9 @@ boolean configureDefaultVanillaStrata() { } ensureDefaultGeologyRules(); addStarterRock("minecraft:stone", RockFamily.SEDIMENTARY, - 64, 96, -64, 319, 5.0D); + 64, 96, DimensionType.MIN_Y, DimensionType.MAX_Y, 5.0D); addStarterRock("minecraft:deepslate", RockFamily.METAMORPHIC, - -32, 40, -64, 48, 3.0D); + -32, 40, DimensionType.MIN_Y, 48, 3.0D); addStarterRock("minecraft:granite", RockFamily.IGNEOUS_INTRUSIVE, 0, 72, -64, 192, 1.5D); addStarterRock("minecraft:diorite", RockFamily.IGNEOUS_INTRUSIVE, @@ -1744,7 +1745,8 @@ List validate() { errors.add("Invalid family for " + entry.getKey()); continue; } - if (integer(rock, "min_y", -64) > integer(rock, "max_y", 319)) { + if (integer(rock, "min_y", DimensionType.MIN_Y) + > integer(rock, "max_y", DimensionType.MAX_Y)) { errors.add("Minimum Y is above maximum Y for " + entry.getKey()); } if (rock.has("dimensions") && !validIdArray(rock.get("dimensions"))) { diff --git a/src/main/java/zone/moddev/mc/orespawn/client/RockEntryScreen.java b/src/main/java/zone/moddev/mc/orespawn/client/RockEntryScreen.java index 61b0c006..e85aee4d 100644 --- a/src/main/java/zone/moddev/mc/orespawn/client/RockEntryScreen.java +++ b/src/main/java/zone/moddev/mc/orespawn/client/RockEntryScreen.java @@ -12,6 +12,7 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; +import net.minecraft.world.level.dimension.DimensionType; final class RockEntryScreen extends OreSpawnScreen { private final Screen parent; @@ -65,8 +66,8 @@ protected void init() { weight = addField(right, 64, "weight", value(rock, "weight", 1.0D)); peak = addField(right, 86, "depth_peak", value(rock, "depth_peak", 48)); spread = addField(right, 108, "depth_spread", value(rock, "depth_spread", 40)); - minY = addField(right, 130, "min_y", value(rock, "min_y", -64)); - maxY = addField(right, 152, "max_y", value(rock, "max_y", 319)); + minY = addField(right, 130, "min_y", value(rock, "min_y", DimensionType.MIN_Y)); + maxY = addField(right, 152, "max_y", value(rock, "max_y", DimensionType.MAX_Y)); addRenderableWidget(OreSpawnScreenLayout.explain( CycleButton.onOffBuilder(oreReplaceable).create(left, 176, 150, 20, Component.translatable("option.orespawn.ore_replaceable"), @@ -94,7 +95,7 @@ private EditBox addField(int x, int y, String key, String initial) { } private static String rockFieldHelp(String key) { - return "weight".equals(key) ? "tooltip.orespawn.weight" + return "weight".equals(key) ? "tooltip.orespawn.rock.weight" : "tooltip.orespawn.rock." + key; } @@ -115,10 +116,10 @@ private void saveAndClose() { private boolean save() { try { double parsedWeight = number(weight, 0.0D, 1000.0D); - int parsedPeak = integer(peak, -64, 319); + int parsedPeak = integer(peak, DimensionType.MIN_Y, DimensionType.MAX_Y); int parsedSpread = integer(spread, 1, 512); - int parsedMin = integer(minY, -64, 319); - int parsedMax = integer(maxY, -64, 319); + int parsedMin = integer(minY, DimensionType.MIN_Y, DimensionType.MAX_Y); + int parsedMax = integer(maxY, DimensionType.MIN_Y, DimensionType.MAX_Y); if (parsedMin > parsedMax) throw new NumberFormatException(); JsonObject rock = session.rock(blockId); rock.addProperty("enabled", enabled); 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 c22813df..0386759a 100644 --- a/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java +++ b/src/main/java/zone/moddev/mc/orespawn/integration/WorldgenIntegrationManager.java @@ -38,6 +38,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.dimension.DimensionType; import net.neoforged.fml.InterModComms; import net.neoforged.fml.ModList; import net.neoforged.fml.loading.FMLPaths; @@ -580,8 +581,8 @@ private static void validateRock(String idText, JsonObject rock) { throw new JsonSyntaxException("unknown rock block: " + blockId); } RockFamily.fromConfigName(string(rock, "family", "")); - int minY = integer(rock, "min_y", -64); - int maxY = integer(rock, "max_y", 319); + int minY = integer(rock, "min_y", DimensionType.MIN_Y); + int maxY = integer(rock, "max_y", DimensionType.MAX_Y); if (minY < -2048 || maxY > 2048 || minY > maxY || decimal(rock, "weight", 1.0D) < 0.0D) { throw new JsonSyntaxException("invalid rock range or weight: " + idText); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/BakedGeomeConfig.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/BakedGeomeConfig.java index 261dbb34..4a6632dd 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/BakedGeomeConfig.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/BakedGeomeConfig.java @@ -14,9 +14,12 @@ import net.minecraft.resources.ResourceLocation; public final class BakedGeomeConfig { + // Keep vanilla lookup rows unchanged; loaded dimensions can extend the tables. static final int MIN_Y = -64; static final int MAX_Y = 319; - private static final int HEIGHT = MAX_Y - MIN_Y + 1; + private final int minY; + private final int maxY; + private final int height; private static final int LEGACY_MAX_Y = 255; private static final int FORMATION_BUCKETS = 256; private static final int PICKER_SCALE = 1000; @@ -40,6 +43,7 @@ public final class BakedGeomeConfig { private final Set oreReplaceableBlocks; private final Map rockFamilies; private final int familyDiversitySlots; + private final boolean[][] replacementHeights; private int[][][] legacyFamilyThresholds; private WeightedBlockPicker[][][] legacyRockPickers; @@ -52,6 +56,17 @@ public final class BakedGeomeConfig { BakedGeomeConfig(GeomeDefinition[] geomes, double geomeScale, double biomeInfluence, double regionalNoiseInfluence, double boundaryNoiseInfluence, Map biomeWeights, Map biomeWeightsById, RockEntry[] rocks, FormationSettings formations) { + this(geomes, geomeScale, biomeInfluence, regionalNoiseInfluence, boundaryNoiseInfluence, + biomeWeights, biomeWeightsById, rocks, formations, MIN_Y, MAX_Y); + } + + BakedGeomeConfig(GeomeDefinition[] geomes, double geomeScale, double biomeInfluence, + double regionalNoiseInfluence, double boundaryNoiseInfluence, Map biomeWeights, + Map biomeWeightsById, RockEntry[] rocks, FormationSettings formations, + int minY, int maxY) { + this.minY = Math.min(MIN_Y, minY); + this.maxY = Math.max(MAX_Y, maxY); + this.height = this.maxY - this.minY + 1; this.geomes = geomes; this.geomeScale = geomeScale; this.biomeInfluence = biomeInfluence; @@ -83,6 +98,16 @@ public final class BakedGeomeConfig { sedimentaryBlocks = buildBlockSet(rocks, true, false); oreReplaceableBlocks = buildBlockSet(rocks, false, true); rockFamilies = buildFamilyMap(rocks); + replacementHeights = new boolean[geomes.length][height]; + for (int geome = 0; geome < geomes.length; geome++) { + for (RockEntry rock : rocks) { + if (rock.weight <= 0.0D || rock.geomeWeights[geome] <= 0.0D + || geomes[geome].familyWeights[rock.family.ordinal()] <= 0.0D) continue; + for (int y = Math.max(this.minY, rock.minY); y <= Math.min(this.maxY, rock.maxY); y++) { + replacementHeights[geome][y - this.minY] = true; + } + } + } if (formations.usesStableLayers()) { buildStablePickers(rocks); @@ -91,6 +116,19 @@ public final class BakedGeomeConfig { } } + BakedGeomeConfig withWorldHeight(int minBuildY, int maxBuildY) { + int lookupMin = Math.min(MIN_Y, minBuildY); + int lookupMax = Math.max(MAX_Y, maxBuildY - 1); + if (lookupMin == minY && lookupMax == maxY) return this; + return new BakedGeomeConfig(geomes, geomeScale, biomeInfluence, regionalNoiseInfluence, + boundaryNoiseInfluence, biomeWeights, biomeWeightsById, rocks, formations, + lookupMin, lookupMax); + } + + boolean hasReplacementAt(int geomeIndex, int worldY) { + return worldY >= minY && worldY <= maxY && replacementHeights[geomeIndex][worldY - minY]; + } + public int pickGeome(Biome biome, double[] regionalNoise, double boundaryNoise) { return pickGeome(biome, null, regionalNoise, boundaryNoise); } @@ -141,7 +179,7 @@ RockFamily pickFamily(int geomeIndex, int y, int formationValue, int diversitySl return RockFamily.values()[stableFamilyChoices[index] & 0xFF]; } - int[] thresholds = legacyFamilyThresholds[geomeIndex][clampLegacyY(y)]; + int[] thresholds = legacyFamilyThresholds[geomeIndex][legacyYIndex(y)]; int total = thresholds[thresholds.length - 1]; if (total <= 0) { return RockFamily.SEDIMENTARY; @@ -190,7 +228,41 @@ public BlockState pickRock(int geomeIndex, RockFamily family, int y, int formati if (formations.usesStableLayers()) { return pickStableRockAtWorldY(geomeIndex, family, y, y, formationValue); } - return legacyRockPickers[geomeIndex][family.ordinal()][clampLegacyY(y)].pick(formationValue); + return legacyRockPickers[geomeIndex][family.ordinal()][legacyYIndex(y)].pick(formationValue); + } + + BlockState pickExtendedLegacyRock(int geomeIndex, RockFamily family, int worldY, + int formationY, int formationValue) { + int total = legacyFamilyWeight(geomeIndex, family, worldY, formationY); + if (total == 0) { + family = pickFamily(geomeIndex, worldY, formationValue); + total = legacyFamilyWeight(geomeIndex, family, worldY, formationY); + } + if (total == 0) return FALLBACK; + int remaining = positive(formationValue) % total; + for (int index : familyRockIndexes[family.ordinal()]) { + int weight = legacyRockWeight(geomeIndex, index, worldY, formationY); + if (remaining < weight) return rockStates[index]; + remaining -= weight; + } + return FALLBACK; + } + + private int legacyFamilyWeight(int geomeIndex, RockFamily family, int worldY, int formationY) { + if (geomes[geomeIndex].familyWeights[family.ordinal()] <= 0.0D) return 0; + int total = 0; + for (int index : familyRockIndexes[family.ordinal()]) { + total += legacyRockWeight(geomeIndex, index, worldY, formationY); + } + return total; + } + + private int legacyRockWeight(int geomeIndex, int index, int worldY, int formationY) { + RockEntry rock = rocks[index]; + if (worldY < rock.minY || worldY > rock.maxY) return 0; + double weight = rock.weight * rock.geomeWeights[geomeIndex] + * depthWeight(formationY, rock.depthPeak, rock.depthSpread); + return weight <= 0.0D ? 0 : Math.max(1, (int) Math.round(weight * PICKER_SCALE)); } BlockState pickStableRockAtWorldY(int geomeIndex, RockFamily family, int worldY, @@ -335,24 +407,24 @@ private double[] biomeWeightsFor(Biome biome, ResourceLocation biomeId) { } private void buildStablePickers(RockEntry[] rocks) { - stableFamilyChoices = new byte[geomes.length * HEIGHT * FORMATION_BUCKETS * familyDiversitySlots]; - stableRockChoices = new int[geomes.length * RockFamily.values().length * HEIGHT * FORMATION_BUCKETS]; + stableFamilyChoices = new byte[geomes.length * height * FORMATION_BUCKETS * familyDiversitySlots]; + stableRockChoices = new int[geomes.length * RockFamily.values().length * height * FORMATION_BUCKETS]; Arrays.fill(stableRockChoices, -1); int familyCount = RockFamily.values().length; familyRockIndexes = groupRockIndexes(rocks); - stableRockLogWeights = new double[geomes.length][rocks.length][HEIGHT]; + stableRockLogWeights = new double[geomes.length][rocks.length][height]; stableRockPriorities = new double[geomes.length][rocks.length][FORMATION_BUCKETS]; - double[][][] familyLogWeights = new double[geomes.length][familyCount][HEIGHT]; - double[][][] familyWeights = new double[geomes.length][familyCount][HEIGHT]; + double[][][] familyLogWeights = new double[geomes.length][familyCount][height]; + double[][][] familyWeights = new double[geomes.length][familyCount][height]; double[][][] familyPriorities = new double[geomes.length][familyCount][FORMATION_BUCKETS]; for (int geome = 0; geome < geomes.length; geome++) { for (int rockIndex = 0; rockIndex < rocks.length; rockIndex++) { RockEntry rock = rocks[rockIndex]; - for (int y = MIN_Y; y <= MAX_Y; y++) { + for (int y = minY; y <= maxY; y++) { double rawWeight = rock.weight * rock.geomeWeights[geome] * depthWeight(y, rock.depthPeak, rock.depthSpread); - stableRockLogWeights[geome][rockIndex][y - MIN_Y] = rawWeight > 0.0D + stableRockLogWeights[geome][rockIndex][y - minY] = rawWeight > 0.0D ? Math.log(rawWeight) : Double.NEGATIVE_INFINITY; } for (int bucket = 0; bucket < FORMATION_BUCKETS; bucket++) { @@ -364,8 +436,8 @@ private void buildStablePickers(RockEntry[] rocks) { for (RockFamily family : RockFamily.values()) { int familyIndex = family.ordinal(); - for (int y = MIN_Y; y <= MAX_Y; y++) { - int yIndex = y - MIN_Y; + for (int y = minY; y <= maxY; y++) { + int yIndex = y - minY; boolean available = false; for (int rockIndex : familyRockIndexes[familyIndex]) { RockEntry rock = rocks[rockIndex]; @@ -394,8 +466,8 @@ private void buildStablePickers(RockEntry[] rocks) { double[] remainders = new double[familyCount]; boolean[] bonusAwarded = new boolean[familyCount]; for (int geome = 0; geome < geomes.length; geome++) { - for (int y = MIN_Y; y <= MAX_Y; y++) { - int yIndex = y - MIN_Y; + for (int y = minY; y <= maxY; y++) { + int yIndex = y - minY; for (int bucket = 0; bucket < FORMATION_BUCKETS; bucket++) { if (familyDiversitySlots == 1) { double bestFamilyScore = Double.NEGATIVE_INFINITY; @@ -606,14 +678,16 @@ private static double unitDouble(long value) { } private void buildLegacyPickers(RockEntry[] rocks) { - legacyFamilyThresholds = new int[geomes.length][LEGACY_MAX_Y + 1][RockFamily.values().length]; - legacyRockPickers = new WeightedBlockPicker[geomes.length][RockFamily.values().length][LEGACY_MAX_Y + 1]; + familyRockIndexes = groupRockIndexes(rocks); + legacyFamilyThresholds = new int[geomes.length][height][RockFamily.values().length]; + legacyRockPickers = new WeightedBlockPicker[geomes.length][RockFamily.values().length][height]; for (int geome = 0; geome < geomes.length; geome++) { - for (int y = 0; y <= LEGACY_MAX_Y; y++) { + for (int y = minY; y <= maxY; y++) { + int yIndex = y - minY; int familyTotal = 0; for (RockFamily family : RockFamily.values()) { WeightedBlockPicker picker = buildLegacyRockPicker(rocks, geome, family, y); - legacyRockPickers[geome][family.ordinal()][y] = picker; + legacyRockPickers[geome][family.ordinal()][yIndex] = picker; double familyWeight = geomes[geome].familyWeights[family.ordinal()] * familyDepthWeight(family, y); @@ -623,7 +697,7 @@ private void buildLegacyPickers(RockEntry[] rocks) { if (familyWeight > 0.0D) { familyTotal += Math.max(1, (int) Math.round(familyWeight * PICKER_SCALE)); } - legacyFamilyThresholds[geome][y][family.ordinal()] = familyTotal; + legacyFamilyThresholds[geome][yIndex][family.ordinal()] = familyTotal; } } } @@ -672,12 +746,12 @@ private static Map buildFamilyMap(RockEntry[] rocks) { } private int stableFamilyIndex(int geome, int yIndex, int bucket, int diversitySlot) { - return ((((geome * HEIGHT + yIndex) * FORMATION_BUCKETS) + bucket) * familyDiversitySlots) + return ((((geome * height + yIndex) * FORMATION_BUCKETS) + bucket) * familyDiversitySlots) + diversitySlot; } private int stableRockIndex(int geome, int family, int yIndex, int bucket) { - return ((((geome * RockFamily.values().length) + family) * HEIGHT + yIndex) * FORMATION_BUCKETS) + return ((((geome * RockFamily.values().length) + family) * height + yIndex) * FORMATION_BUCKETS) + bucket; } @@ -707,16 +781,19 @@ private static double familyDepthWeight(RockFamily family, int y) { } } - private static int clampStableY(int y) { - return Math.max(MIN_Y, Math.min(MAX_Y, y)) - MIN_Y; + private int clampStableY(int y) { + return Math.max(minY, Math.min(maxY, y)) - minY; } - private static int clampStableValue(int y) { - return Math.max(MIN_Y, Math.min(MAX_Y, y)); + private int clampStableValue(int y) { + return Math.max(minY, Math.min(maxY, y)); } - private static int clampLegacyY(int y) { - return Math.max(0, Math.min(LEGACY_MAX_Y, y)); + private int legacyYIndex(int y) { + // Keep the old smooth algorithm's ordinary-world depth curve unchanged. + int sampleY = y >= MIN_Y && y <= MAX_Y ? Math.max(0, Math.min(LEGACY_MAX_Y, y)) + : Math.max(minY, Math.min(maxY, y)); + return sampleY - minY; } private static int positive(int value) { 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 e1c94369..fc65af36 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeConfig.java @@ -20,6 +20,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -48,6 +49,8 @@ import net.minecraft.core.registries.Registries; import net.minecraft.tags.TagKey; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.biome.Biome; import net.minecraft.core.registries.BuiltInRegistries; @@ -90,6 +93,7 @@ public final class GeomeConfig { private static volatile BakedGeomeConfig bakedConfig = null; private static volatile Map, BakedGeomeConfig> bakedConfigs = Collections.emptyMap(); private static volatile Map, BakedTerrainDimension> terrainDimensions = Collections.emptyMap(); + private static final Map, int[]> worldHeights = new ConcurrentHashMap<>(); private static JsonObject globalConfigRoot = null; private static JsonObject globalBaseConfigRoot = null; private static volatile WorldGeologyProfile globalProfile = null; @@ -152,6 +156,42 @@ public static BakedGeomeConfig baked(ResourceKey dimension) { return bakedConfigs.get(dimension); } + public static BakedGeomeConfig baked(ResourceKey dimension, LevelHeightAccessor level) { + // Height is runtime state, not another setting to copy into saved profiles. + int minY = level.getMinBuildHeight(); + int maxY = level.getMaxBuildHeight(); + int[] known = worldHeights.get(dimension); + if (known == null || known[0] != minY || known[1] != maxY) { + bindWorldHeight(dimension, minY, maxY); + } + return baked(dimension); + } + + static synchronized void bindWorldHeight(ResourceKey dimension, int minY, int maxY) { + // Minecraft's upper build boundary is exclusive; rock max_y is inclusive. + if (minY < DimensionType.MIN_Y || maxY > DimensionType.MAX_Y + 1 || minY >= maxY) { + throw new IllegalArgumentException("Invalid dimension height: " + minY + ".." + maxY); + } + int[] previous = worldHeights.get(dimension); + if (previous != null && previous[0] == minY && previous[1] == maxY) return; + BakedGeomeConfig config = bakedConfigs.get(dimension); + if (config != null) { + BakedGeomeConfig adjusted = config.withWorldHeight(minY, maxY); + if (adjusted != config) { + Map, BakedGeomeConfig> updated = new LinkedHashMap<>(bakedConfigs); + updated.put(dimension, adjusted); + bakedConfigs = Collections.unmodifiableMap(updated); + if (Level.OVERWORLD.equals(dimension)) bakedConfig = adjusted; + } + } + // A second generation worker must not see these bounds before the tables are ready. + worldHeights.put(dimension, new int[] { minY, maxY }); + } + + static synchronized void clearWorldHeights() { + worldHeights.clear(); + } + static BakedTerrainDimension terrainDimension(ResourceKey dimension) { if (bakedConfig == null) { bake(); @@ -320,8 +360,11 @@ private static BakedGeomeConfig bake(JsonObject root, ResourceLocation dimension + "{} resolved biome profiles, {} identifier profiles, and {} formations", dimension, geomes.length, rocks.length, biomeWeights.size(), biomeWeightsById.size(), formations.algorithm.configName); + int[] height = worldHeights.get(ResourceKey.create(Registries.DIMENSION, dimension)); return new BakedGeomeConfig(geomes, geomeScale, biomeInfluence, regionalNoiseInfluence, - boundaryNoiseInfluence, biomeWeights, biomeWeightsById, rocks, formations); + boundaryNoiseInfluence, biomeWeights, biomeWeightsById, rocks, formations, + height == null ? BakedGeomeConfig.MIN_Y : height[0], + height == null ? BakedGeomeConfig.MAX_Y : height[1] - 1); } private static JsonObject applyFreshWorldTemplate(JsonObject root) { @@ -614,10 +657,10 @@ private static RockEntry[] readRocks(JsonObject root, Map geome continue; } - int minY = getBoundedInt(json, "min_y", BakedGeomeConfig.MIN_Y, - BakedGeomeConfig.MIN_Y, BakedGeomeConfig.MAX_Y); - int maxY = getBoundedInt(json, "max_y", BakedGeomeConfig.MAX_Y, - BakedGeomeConfig.MIN_Y, BakedGeomeConfig.MAX_Y); + int minY = getBoundedInt(json, "min_y", DimensionType.MIN_Y, + DimensionType.MIN_Y, DimensionType.MAX_Y); + int maxY = getBoundedInt(json, "max_y", DimensionType.MAX_Y, + DimensionType.MIN_Y, DimensionType.MAX_Y); if (minY > maxY) { LOGGER.warn("Ignoring OreSpawn geome rock '{}' because min_y {} is above max_y {}", id, minY, maxY); continue; @@ -635,7 +678,7 @@ private static RockEntry[] readRocks(JsonObject root, Map geome rocks.add(new RockEntry(state, family, - getBoundedInt(json, "depth_peak", 48, BakedGeomeConfig.MIN_Y, BakedGeomeConfig.MAX_Y), + getBoundedInt(json, "depth_peak", 48, DimensionType.MIN_Y, DimensionType.MAX_Y), getBoundedInt(json, "depth_spread", 48, 1, 512), minY, maxY, @@ -650,14 +693,15 @@ private static RockEntry[] readRocks(JsonObject root, Map geome if (passive) { LOGGER.info("No OreSpawn terrain provider is active; using an internal vanilla-stone sampler fallback"); } else { - LOGGER.warn("OreSpawn geome config produced no valid rock entries; falling back to vanilla stone"); + LOGGER.warn("OreSpawn geome config produced no valid rock entries; using an internal vanilla-stone sampler fallback"); } double[] weights = new double[geomeIndexes.size()]; for (int i = 0; i < weights.length; i++) { weights[i] = 1.0D; } + // Keep the sampler's Stone fallback out of terrain selection. rocks.add(new RockEntry(Blocks.STONE.defaultBlockState(), RockFamily.SEDIMENTARY, 64, 64, - BakedGeomeConfig.MIN_Y, BakedGeomeConfig.MAX_Y, 1.0D, true, weights)); + BakedGeomeConfig.MIN_Y, BakedGeomeConfig.MAX_Y, 0.0D, true, weights)); } return rocks.toArray(new RockEntry[rocks.size()]); } @@ -1930,8 +1974,8 @@ private static void addRock(JsonObject rocks, String id, RockFamily family, int rock.addProperty("family", family.configName); rock.addProperty("depth_peak", peak); rock.addProperty("depth_spread", spread); - rock.addProperty("min_y", BakedGeomeConfig.MIN_Y); - rock.addProperty("max_y", BakedGeomeConfig.MAX_Y); + rock.addProperty("min_y", DimensionType.MIN_Y); + rock.addProperty("max_y", DimensionType.MAX_Y); rock.addProperty("weight", weight); rock.addProperty("ore_replaceable", true); JsonObject geomes = new JsonObject(); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeGeology.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeGeology.java index d560e941..52c01e60 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeGeology.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/GeomeGeology.java @@ -116,6 +116,7 @@ public void replaceStoneInChunk(LevelAccessor world, ChunkAccess chunk, BakedTer formationRegion, x, z, surfaceY, terrain); } else { for (int y = surfaceY; y >= chunk.getMinBuildHeight(); y--) { + if (!config.hasReplacementAt(geomeIndex, y)) continue; cursor.set(x, y, z); if (terrain.isReplaceable(chunk.getBlockState(cursor)) && chunk.getBlockEntity(cursor) == null) { @@ -152,6 +153,8 @@ private boolean replaceStableColumn(ChunkAccess chunk, BlockPos.MutableBlockPos layerIndex, geomeTransitionPhase); } cursor.setY(y); + // An uncovered height keeps the terrain generator's rock, not a Stone fallback. + if (!config.hasReplacementAt(layerGeome, y)) continue; if (terrain.isReplaceable(chunk.getBlockState(cursor)) && chunk.getBlockEntity(cursor) == null) { chunk.setBlockState(cursor, @@ -220,16 +223,21 @@ public String geomeName() { } public BlockState rockAt(int y) { + return pickReplacement(geomeAt(y), stratumOffset, formationRegion, x, y, z); + } + + private int geomeAt(int y) { int selectedGeome = geomeIndex; if (stableLayers) { int layerIndex = Math.floorDiv(stratumOffset + y, layerThickness); selectedGeome = pickStableLayerGeome(geomeScores, geomeIndex, secondGeome, layerIndex, geomeTransitionPhase); } - return pickReplacement(selectedGeome, stratumOffset, formationRegion, x, y, z); + return selectedGeome; } public RockFamily familyAt(int y) { + if (!config.hasReplacementAt(geomeAt(y), y)) return null; return config.familyOf(rockAt(y)); } } @@ -255,6 +263,9 @@ private net.minecraft.world.level.block.state.BlockState pickReplacement(int geo int familyHash = whiteNoiseArray[(layerIndex + (geomeIndex * 37)) & 0xFF]; RockFamily family = pickShapedFamily(geomeIndex, x, y, z, layerY, familyHash); int rockHash = whiteNoiseArray[((layerIndex * 31) + (family.ordinal() * 53) + (geomeIndex * 79)) & 0xFF]; + if (y < BakedGeomeConfig.MIN_Y || y > BakedGeomeConfig.MAX_Y) { + return config.pickExtendedLegacyRock(geomeIndex, family, y, layerY, rockHash); + } return config.pickRock(geomeIndex, family, layerY, rockHash); } diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java index facff2a4..6f1fd13d 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyConfigMigrator.java @@ -358,7 +358,7 @@ private static void writeReport(Path config, List lines) { private static void writeUpgradeReport(Path config, int imported, List detail) { List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.121012 Upgrade Report"); + lines.add("OreSpawn 4.1.1.121012 Upgrade Report"); lines.add("================================"); lines.add(""); lines.add("RESULT: Legacy OreSpawn settings were imported into the OS4 profile."); diff --git a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java index 05a0679e..dfd7798a 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyMineralogyProfileMigration.java @@ -205,7 +205,7 @@ private static void writeUpgradeReport(Path worldRoot, Path configPath, Path report = worldRoot.resolve("serverconfig/orespawn-upgrade-report.txt"); List missing = missingBlocks(igneous, metamorphic, sedimentary); List lines = new ArrayList<>(); - lines.add("OreSpawn 4.1.0.121012 Upgrade Report"); + lines.add("OreSpawn 4.1.1.121012 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/StoneReplacer.java b/src/main/java/zone/moddev/mc/orespawn/worldgen/StoneReplacer.java index b01603f7..4d685d21 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/StoneReplacer.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/StoneReplacer.java @@ -104,7 +104,7 @@ public boolean place(FeaturePlaceContext context) { BlockPos pos = context.origin(); net.minecraft.resources.ResourceKey dimension = world.getLevel().dimension(); BakedTerrainDimension terrain = GeomeConfig.terrainDimension(dimension); - BakedGeomeConfig config = GeomeConfig.baked(dimension); + BakedGeomeConfig config = GeomeConfig.baked(dimension, world); WorldGeologyProfile profile = WorldGeologyProfileManager.activeProfile(); if (!OreSpawnConfig.placeOreSpawnRock() || terrain == null || config == null || (profile.hasLegacyMineralogySnapshot() && !profile.cyanoEnabled())) { @@ -142,15 +142,15 @@ private CachedGeology geology(net.minecraft.resources.ResourceKey dimensi BakedGeomeConfig config) { CachedGeology current = geologyByDimension.get(dimension); GeologyMode mode = WorldGeologyProfileManager.geologyMode(); - if (current == null || current.seed != seed || current.mode != mode) { + if (current == null || current.seed != seed || current.mode != mode || current.config != config) { geologyLock.lock(); try { current = geologyByDimension.get(dimension); - if (current == null || current.seed != seed || current.mode != mode) { + if (current == null || current.seed != seed || current.mode != mode || current.config != config) { WorldGeologyProfile profile = WorldGeologyProfileManager.activeProfile(); current = mode == GeologyMode.LEGACY - ? new CachedGeology(seed, mode, new Geology(seed, profile, config), null) - : new CachedGeology(seed, mode, null, new GeomeGeology(seed, config)); + ? new CachedGeology(seed, mode, config, new Geology(seed, profile, config), null) + : new CachedGeology(seed, mode, config, null, new GeomeGeology(seed, config)); geologyByDimension.put(dimension, current); } } finally { @@ -207,12 +207,14 @@ private void clearCachedGeology() { private static final class CachedGeology { final long seed; final GeologyMode mode; + final BakedGeomeConfig config; final Geology legacy; final GeomeGeology sky; - CachedGeology(long seed, GeologyMode mode, Geology legacy, GeomeGeology sky) { + CachedGeology(long seed, GeologyMode mode, BakedGeomeConfig config, Geology legacy, GeomeGeology sky) { this.seed = seed; this.mode = mode; + this.config = config; this.legacy = legacy; this.sky = sky; } 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 c4b028b8..0f45a967 100644 --- a/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java +++ b/src/main/java/zone/moddev/mc/orespawn/worldgen/WorldGeologyProfileManager.java @@ -125,6 +125,7 @@ public static synchronized boolean reloadActiveProfile() { } public static void onServerAboutToStart(ServerAboutToStartEvent event) { + GeomeConfig.clearWorldHeights(); activeServer = event.getServer(); deferredFreshPolicies.clear(); freshPolicyDiscoveryPending = false; @@ -282,6 +283,7 @@ public static void onServerStopped(ServerStoppedEvent event) { freshPolicyDiscoveryPending = false; activeServer = null; activeProfile = null; + GeomeConfig.clearWorldHeights(); GeomeConfig.applyWorldProfile(globalProfile()); BiomeWorldgenManager.clear(); VanillaOreFeatureGate.clearGates(); @@ -295,6 +297,8 @@ public static void onServerStopped(ServerStoppedEvent event) { public static void onWorldLoad(LevelEvent.Load event) { if (!(event.getLevel() instanceof ServerLevel)) return; + ServerLevel level = (ServerLevel) event.getLevel(); + GeomeConfig.bindWorldHeight(level.dimension(), level.getMinBuildHeight(), level.getMaxBuildHeight()); WorldGeologyProfile profile = activeProfile; if (profile != null) { BiomeWorldgenManager.apply((ServerLevel) event.getLevel(), profile); diff --git a/src/main/resources/assets/orespawn/lang/de_au.json b/src/main/resources/assets/orespawn/lang/de_au.json index 9b4d1c20..9cdb439e 100644 --- a/src/main/resources/assets/orespawn/lang/de_au.json +++ b/src/main/resources/assets/orespawn/lang/de_au.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Vertikaler Bereich, der von Cluster- und Wolkenmustern verwendet wird. Gültiger Bereich: 0 bis 64.", "tooltip.orespawn.ore.node_size": "Blockbudget für jeden Knoten im Clustermuster. Gültiger Bereich: 1 bis 32.", "tooltip.orespawn.rock.family": "Klassifizieren Sie dieses Gestein als sedimentäres, metamorphes, intrusives magmatisches oder vulkanisches magmatisches Gestein für Geome und Tiefenpräferenzen.", - "tooltip.orespawn.rock.depth_peak": "Y-Ebene, auf der dieses Gestein seine stärkste Tiefenpräferenz erhält. Gültiger Bereich: -64 bis 319.", + "tooltip.orespawn.rock.weight": "Relative Auswahlchance unter geeigneten Gesteinen derselben Familie, kein Gesamtprozentsatz. Die Familiengewichte des Geoms bestimmen den Anteil der Familie. Null deaktiviert dieses Gestein.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y-Höhe mit der stärksten Tiefenpräferenz dieses Gesteins (-2032 bis 2031). Die Erzeugung bleibt innerhalb der Baugrenzen der Dimension.", "tooltip.orespawn.rock.depth_spread": "Wie allmählich die Tiefenpräferenz des Gesteins vom Tiefengipfel abfällt. Größere Werte decken einen größeren vertikalen Bereich ab; Gültiger Bereich: 1 bis 512.", - "tooltip.orespawn.rock.min_y": "Niedrigstes Y, wo dieser Fels das Gelände ersetzen kann. Gültiger Bereich: -64 bis 319; es darf das maximale Y nicht überschreiten.", - "tooltip.orespawn.rock.max_y": "Höchstes Y, wo dieser Fels das Gelände ersetzen kann. Gültiger Bereich: -64 bis 319; es darf nicht unter dem Mindest-Y-Wert liegen.", + "tooltip.orespawn.rock.min_y": "Niedrigste erlaubte absolute Y-Höhe dieses Gesteins (-2032 bis 2031). Darf Maximum Y nicht überschreiten; die Erzeugung bleibt im Baubereich der Dimension.", + "tooltip.orespawn.rock.max_y": "Höchste erlaubte absolute Y-Höhe dieses Gesteins (-2032 bis 2031). Darf Minimum Y nicht unterschreiten; die Erzeugung bleibt im Baubereich der Dimension.", "tooltip.orespawn.rock.ore_replaceable": "Erlauben Sie, dass von OreSpawn verwaltete Erze dieses Gestein ersetzen, wenn es als Wirtsfamilie ausgewählt wird.", "tooltip.orespawn.biome.dimension": "Wählen Sie die Dimension aus, deren Biom-Platzierung und Weltmaterialeinstellungen angezeigt werden.", "tooltip.orespawn.biome.palette_enabled": "Aktivieren Sie die vom Anbieter bereitgestellte Biom-Platzierung in dieser Dimension. Wenn Sie es deaktivieren, bleiben die gespeicherten Biomeinträge erhalten.", diff --git a/src/main/resources/assets/orespawn/lang/de_de.json b/src/main/resources/assets/orespawn/lang/de_de.json index 9b4d1c20..9cdb439e 100644 --- a/src/main/resources/assets/orespawn/lang/de_de.json +++ b/src/main/resources/assets/orespawn/lang/de_de.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Vertikaler Bereich, der von Cluster- und Wolkenmustern verwendet wird. Gültiger Bereich: 0 bis 64.", "tooltip.orespawn.ore.node_size": "Blockbudget für jeden Knoten im Clustermuster. Gültiger Bereich: 1 bis 32.", "tooltip.orespawn.rock.family": "Klassifizieren Sie dieses Gestein als sedimentäres, metamorphes, intrusives magmatisches oder vulkanisches magmatisches Gestein für Geome und Tiefenpräferenzen.", - "tooltip.orespawn.rock.depth_peak": "Y-Ebene, auf der dieses Gestein seine stärkste Tiefenpräferenz erhält. Gültiger Bereich: -64 bis 319.", + "tooltip.orespawn.rock.weight": "Relative Auswahlchance unter geeigneten Gesteinen derselben Familie, kein Gesamtprozentsatz. Die Familiengewichte des Geoms bestimmen den Anteil der Familie. Null deaktiviert dieses Gestein.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y-Höhe mit der stärksten Tiefenpräferenz dieses Gesteins (-2032 bis 2031). Die Erzeugung bleibt innerhalb der Baugrenzen der Dimension.", "tooltip.orespawn.rock.depth_spread": "Wie allmählich die Tiefenpräferenz des Gesteins vom Tiefengipfel abfällt. Größere Werte decken einen größeren vertikalen Bereich ab; Gültiger Bereich: 1 bis 512.", - "tooltip.orespawn.rock.min_y": "Niedrigstes Y, wo dieser Fels das Gelände ersetzen kann. Gültiger Bereich: -64 bis 319; es darf das maximale Y nicht überschreiten.", - "tooltip.orespawn.rock.max_y": "Höchstes Y, wo dieser Fels das Gelände ersetzen kann. Gültiger Bereich: -64 bis 319; es darf nicht unter dem Mindest-Y-Wert liegen.", + "tooltip.orespawn.rock.min_y": "Niedrigste erlaubte absolute Y-Höhe dieses Gesteins (-2032 bis 2031). Darf Maximum Y nicht überschreiten; die Erzeugung bleibt im Baubereich der Dimension.", + "tooltip.orespawn.rock.max_y": "Höchste erlaubte absolute Y-Höhe dieses Gesteins (-2032 bis 2031). Darf Minimum Y nicht unterschreiten; die Erzeugung bleibt im Baubereich der Dimension.", "tooltip.orespawn.rock.ore_replaceable": "Erlauben Sie, dass von OreSpawn verwaltete Erze dieses Gestein ersetzen, wenn es als Wirtsfamilie ausgewählt wird.", "tooltip.orespawn.biome.dimension": "Wählen Sie die Dimension aus, deren Biom-Platzierung und Weltmaterialeinstellungen angezeigt werden.", "tooltip.orespawn.biome.palette_enabled": "Aktivieren Sie die vom Anbieter bereitgestellte Biom-Platzierung in dieser Dimension. Wenn Sie es deaktivieren, bleiben die gespeicherten Biomeinträge erhalten.", diff --git a/src/main/resources/assets/orespawn/lang/en_ca.json b/src/main/resources/assets/orespawn/lang/en_ca.json index b48311c5..c88033b8 100644 --- a/src/main/resources/assets/orespawn/lang/en_ca.json +++ b/src/main/resources/assets/orespawn/lang/en_ca.json @@ -264,10 +264,11 @@ "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: -64 to 319.", + "tooltip.orespawn.rock.weight": "Relative chance among eligible rocks in the same family, not an overall percentage. Change the geome's family weights to reduce that family's share. Zero disables this rock.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y where this rock has its strongest depth preference (-2032 to 2031). Generation stays inside the dimension's build limits.", "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: -64 to 319; it must not exceed Maximum Y.", - "tooltip.orespawn.rock.max_y": "Highest Y where this rock may replace terrain. Valid range: -64 to 319; it must not be below Minimum Y.", + "tooltip.orespawn.rock.min_y": "Lowest absolute Y allowed for this rock (-2032 to 2031). Must not exceed Maximum Y; generation is clipped to the dimension's build range.", + "tooltip.orespawn.rock.max_y": "Highest absolute Y allowed for this rock (-2032 to 2031). Must not be below Minimum Y; generation is clipped to the dimension's build range.", "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.", diff --git a/src/main/resources/assets/orespawn/lang/en_en.json b/src/main/resources/assets/orespawn/lang/en_en.json index b48311c5..c88033b8 100644 --- a/src/main/resources/assets/orespawn/lang/en_en.json +++ b/src/main/resources/assets/orespawn/lang/en_en.json @@ -264,10 +264,11 @@ "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: -64 to 319.", + "tooltip.orespawn.rock.weight": "Relative chance among eligible rocks in the same family, not an overall percentage. Change the geome's family weights to reduce that family's share. Zero disables this rock.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y where this rock has its strongest depth preference (-2032 to 2031). Generation stays inside the dimension's build limits.", "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: -64 to 319; it must not exceed Maximum Y.", - "tooltip.orespawn.rock.max_y": "Highest Y where this rock may replace terrain. Valid range: -64 to 319; it must not be below Minimum Y.", + "tooltip.orespawn.rock.min_y": "Lowest absolute Y allowed for this rock (-2032 to 2031). Must not exceed Maximum Y; generation is clipped to the dimension's build range.", + "tooltip.orespawn.rock.max_y": "Highest absolute Y allowed for this rock (-2032 to 2031). Must not be below Minimum Y; generation is clipped to the dimension's build range.", "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.", diff --git a/src/main/resources/assets/orespawn/lang/en_gb.json b/src/main/resources/assets/orespawn/lang/en_gb.json index b48311c5..c88033b8 100644 --- a/src/main/resources/assets/orespawn/lang/en_gb.json +++ b/src/main/resources/assets/orespawn/lang/en_gb.json @@ -264,10 +264,11 @@ "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: -64 to 319.", + "tooltip.orespawn.rock.weight": "Relative chance among eligible rocks in the same family, not an overall percentage. Change the geome's family weights to reduce that family's share. Zero disables this rock.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y where this rock has its strongest depth preference (-2032 to 2031). Generation stays inside the dimension's build limits.", "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: -64 to 319; it must not exceed Maximum Y.", - "tooltip.orespawn.rock.max_y": "Highest Y where this rock may replace terrain. Valid range: -64 to 319; it must not be below Minimum Y.", + "tooltip.orespawn.rock.min_y": "Lowest absolute Y allowed for this rock (-2032 to 2031). Must not exceed Maximum Y; generation is clipped to the dimension's build range.", + "tooltip.orespawn.rock.max_y": "Highest absolute Y allowed for this rock (-2032 to 2031). Must not be below Minimum Y; generation is clipped to the dimension's build range.", "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.", diff --git a/src/main/resources/assets/orespawn/lang/en_pt.json b/src/main/resources/assets/orespawn/lang/en_pt.json index b48311c5..c88033b8 100644 --- a/src/main/resources/assets/orespawn/lang/en_pt.json +++ b/src/main/resources/assets/orespawn/lang/en_pt.json @@ -264,10 +264,11 @@ "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: -64 to 319.", + "tooltip.orespawn.rock.weight": "Relative chance among eligible rocks in the same family, not an overall percentage. Change the geome's family weights to reduce that family's share. Zero disables this rock.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y where this rock has its strongest depth preference (-2032 to 2031). Generation stays inside the dimension's build limits.", "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: -64 to 319; it must not exceed Maximum Y.", - "tooltip.orespawn.rock.max_y": "Highest Y where this rock may replace terrain. Valid range: -64 to 319; it must not be below Minimum Y.", + "tooltip.orespawn.rock.min_y": "Lowest absolute Y allowed for this rock (-2032 to 2031). Must not exceed Maximum Y; generation is clipped to the dimension's build range.", + "tooltip.orespawn.rock.max_y": "Highest absolute Y allowed for this rock (-2032 to 2031). Must not be below Minimum Y; generation is clipped to the dimension's build range.", "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.", diff --git a/src/main/resources/assets/orespawn/lang/en_us.json b/src/main/resources/assets/orespawn/lang/en_us.json index 3c709609..ac2a5f12 100644 --- a/src/main/resources/assets/orespawn/lang/en_us.json +++ b/src/main/resources/assets/orespawn/lang/en_us.json @@ -264,10 +264,11 @@ "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: -64 to 319.", + "tooltip.orespawn.rock.weight": "Relative chance among eligible rocks in the same family, not an overall percentage. Change the geome's family weights to reduce that family's share. Zero disables this rock.", + "tooltip.orespawn.rock.depth_peak": "Absolute Y where this rock has its strongest depth preference (-2032 to 2031). Generation stays inside the dimension's build limits.", "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: -64 to 319; it must not exceed Maximum Y.", - "tooltip.orespawn.rock.max_y": "Highest Y where this rock may replace terrain. Valid range: -64 to 319; it must not be below Minimum Y.", + "tooltip.orespawn.rock.min_y": "Lowest absolute Y allowed for this rock (-2032 to 2031). Must not exceed Maximum Y; generation is clipped to the dimension's build range.", + "tooltip.orespawn.rock.max_y": "Highest absolute Y allowed for this rock (-2032 to 2031). Must not be below Minimum Y; generation is clipped to the dimension's build range.", "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.", diff --git a/src/main/resources/assets/orespawn/lang/es_es.json b/src/main/resources/assets/orespawn/lang/es_es.json index 2b9e2ba2..e131f468 100644 --- a/src/main/resources/assets/orespawn/lang/es_es.json +++ b/src/main/resources/assets/orespawn/lang/es_es.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Rango vertical utilizado por los patrones de clúster y nube. Rango válido: 0 a 64.", "tooltip.orespawn.ore.node_size": "Presupuesto de bloque para cada nodo en el patrón Clústeres. Rango válido: 1 a 32.", "tooltip.orespawn.rock.family": "Clasifique esta roca como sedimentaria, metamórfica, ígnea intrusiva o ígnea volcánica según sus preferencias de geoma y profundidad.", - "tooltip.orespawn.rock.depth_peak": "Nivel Y donde esta roca recibe su preferencia de profundidad más fuerte. Rango válido: -64 a 319.", + "tooltip.orespawn.rock.weight": "Probabilidad relativa entre las rocas aptas de la misma familia, no un porcentaje global. Ajusta los pesos de familia del geoma para reducir la proporción de esa familia. Cero desactiva esta roca.", + "tooltip.orespawn.rock.depth_peak": "Y absoluta donde esta roca tiene su mayor preferencia de profundidad (-2032 a 2031). La generación respeta los límites de construcción de la dimensión.", "tooltip.orespawn.rock.depth_spread": "Cuán gradualmente la preferencia de profundidad de la roca se aleja del Pico de profundidad. Los valores más grandes cubren un rango vertical más amplio; rango válido: 1 a 512.", - "tooltip.orespawn.rock.min_y": "Y más bajo donde esta roca puede reemplazar al terreno. Rango válido: -64 a 319; no debe exceder el Y máximo.", - "tooltip.orespawn.rock.max_y": "Y más alto donde esta roca puede reemplazar el terreno. Rango válido: -64 a 319; no debe estar por debajo del Y mínimo.", + "tooltip.orespawn.rock.min_y": "Y absoluta mínima permitida para esta roca (-2032 a 2031). No puede superar la Y máxima; la generación se limita al rango de construcción de la dimensión.", + "tooltip.orespawn.rock.max_y": "Y absoluta máxima permitida para esta roca (-2032 a 2031). No puede ser inferior a la Y mínima; la generación se limita al rango de construcción de la dimensión.", "tooltip.orespawn.rock.ore_replaceable": "Permitir que los minerales administrados por OreSpawn reemplacen esta roca cuando se seleccione como familia anfitriona.", "tooltip.orespawn.biome.dimension": "Seleccione la dimensión cuya ubicación de bioma y configuración de material mundial se muestran.", "tooltip.orespawn.biome.palette_enabled": "Habilite la ubicación de bioma proporcionada por el proveedor en esta dimensión. Al desactivarlo se conservan las entradas del bioma guardadas.", diff --git a/src/main/resources/assets/orespawn/lang/es_mx.json b/src/main/resources/assets/orespawn/lang/es_mx.json index 2b9e2ba2..e131f468 100644 --- a/src/main/resources/assets/orespawn/lang/es_mx.json +++ b/src/main/resources/assets/orespawn/lang/es_mx.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Rango vertical utilizado por los patrones de clúster y nube. Rango válido: 0 a 64.", "tooltip.orespawn.ore.node_size": "Presupuesto de bloque para cada nodo en el patrón Clústeres. Rango válido: 1 a 32.", "tooltip.orespawn.rock.family": "Clasifique esta roca como sedimentaria, metamórfica, ígnea intrusiva o ígnea volcánica según sus preferencias de geoma y profundidad.", - "tooltip.orespawn.rock.depth_peak": "Nivel Y donde esta roca recibe su preferencia de profundidad más fuerte. Rango válido: -64 a 319.", + "tooltip.orespawn.rock.weight": "Probabilidad relativa entre las rocas aptas de la misma familia, no un porcentaje global. Ajusta los pesos de familia del geoma para reducir la proporción de esa familia. Cero desactiva esta roca.", + "tooltip.orespawn.rock.depth_peak": "Y absoluta donde esta roca tiene su mayor preferencia de profundidad (-2032 a 2031). La generación respeta los límites de construcción de la dimensión.", "tooltip.orespawn.rock.depth_spread": "Cuán gradualmente la preferencia de profundidad de la roca se aleja del Pico de profundidad. Los valores más grandes cubren un rango vertical más amplio; rango válido: 1 a 512.", - "tooltip.orespawn.rock.min_y": "Y más bajo donde esta roca puede reemplazar al terreno. Rango válido: -64 a 319; no debe exceder el Y máximo.", - "tooltip.orespawn.rock.max_y": "Y más alto donde esta roca puede reemplazar el terreno. Rango válido: -64 a 319; no debe estar por debajo del Y mínimo.", + "tooltip.orespawn.rock.min_y": "Y absoluta mínima permitida para esta roca (-2032 a 2031). No puede superar la Y máxima; la generación se limita al rango de construcción de la dimensión.", + "tooltip.orespawn.rock.max_y": "Y absoluta máxima permitida para esta roca (-2032 a 2031). No puede ser inferior a la Y mínima; la generación se limita al rango de construcción de la dimensión.", "tooltip.orespawn.rock.ore_replaceable": "Permitir que los minerales administrados por OreSpawn reemplacen esta roca cuando se seleccione como familia anfitriona.", "tooltip.orespawn.biome.dimension": "Seleccione la dimensión cuya ubicación de bioma y configuración de material mundial se muestran.", "tooltip.orespawn.biome.palette_enabled": "Habilite la ubicación de bioma proporcionada por el proveedor en esta dimensión. Al desactivarlo se conservan las entradas del bioma guardadas.", diff --git a/src/main/resources/assets/orespawn/lang/fr_ca.json b/src/main/resources/assets/orespawn/lang/fr_ca.json index 3fb02ddc..68f16e3d 100644 --- a/src/main/resources/assets/orespawn/lang/fr_ca.json +++ b/src/main/resources/assets/orespawn/lang/fr_ca.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Plage verticale utilisée par les modèles de cluster et de cloud. Plage valide : 0 à 64.", "tooltip.orespawn.ore.node_size": "Budget de bloc pour chaque nœud du modèle Clusters. Plage valide : 1 à 32.", "tooltip.orespawn.rock.family": "Classez cette roche comme ignée sédimentaire, métamorphique, intrusive ou volcanique pour les préférences de géome et de profondeur.", - "tooltip.orespawn.rock.depth_peak": "Niveau Y où cette roche reçoit sa préférence de profondeur la plus forte. Plage valide : -64 à 319.", + "tooltip.orespawn.rock.weight": "Chance relative parmi les roches admissibles de la même famille, pas un pourcentage global. Ajustez les poids des familles du géome pour réduire la part de cette famille. Zéro désactive cette roche.", + "tooltip.orespawn.rock.depth_peak": "Altitude Y absolue où cette roche est la plus favorisée (-2032 à 2031). La génération reste dans les limites de construction de la dimension.", "tooltip.orespawn.rock.depth_spread": "Comment progressivement la préférence de profondeur de la roche s'éloigne du pic de profondeur. Des valeurs plus élevées couvrent une plage verticale plus large ; plage valide : 1 à 512.", - "tooltip.orespawn.rock.min_y": "Y le plus bas où ce rocher peut remplacer le terrain. Plage valide : -64 à 319 ; il ne doit pas dépasser le Y maximum.", - "tooltip.orespawn.rock.max_y": "Y le plus élevé où ce rocher peut remplacer le terrain. Plage valide : -64 à 319 ; il ne doit pas être inférieur au minimum Y.", + "tooltip.orespawn.rock.min_y": "Altitude Y absolue minimale autorisée pour cette roche (-2032 à 2031). Ne doit pas dépasser le Y maximal ; la génération respecte les limites de construction de la dimension.", + "tooltip.orespawn.rock.max_y": "Altitude Y absolue maximale autorisée pour cette roche (-2032 à 2031). Ne doit pas être inférieure au Y minimal ; la génération respecte les limites de construction de la dimension.", "tooltip.orespawn.rock.ore_replaceable": "Autoriser les minerais gérés par OreSpawn à remplacer cette roche lorsqu'elle est sélectionnée comme famille hôte.", "tooltip.orespawn.biome.dimension": "Sélectionnez la dimension dont le placement du biome et les paramètres de matériaux du monde sont affichés.", "tooltip.orespawn.biome.palette_enabled": "Activez le placement du biome fourni par le fournisseur dans cette dimension. Le désactiver préserve les entrées de biome enregistrées.", diff --git a/src/main/resources/assets/orespawn/lang/fr_fr.json b/src/main/resources/assets/orespawn/lang/fr_fr.json index 3fb02ddc..68f16e3d 100644 --- a/src/main/resources/assets/orespawn/lang/fr_fr.json +++ b/src/main/resources/assets/orespawn/lang/fr_fr.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Plage verticale utilisée par les modèles de cluster et de cloud. Plage valide : 0 à 64.", "tooltip.orespawn.ore.node_size": "Budget de bloc pour chaque nœud du modèle Clusters. Plage valide : 1 à 32.", "tooltip.orespawn.rock.family": "Classez cette roche comme ignée sédimentaire, métamorphique, intrusive ou volcanique pour les préférences de géome et de profondeur.", - "tooltip.orespawn.rock.depth_peak": "Niveau Y où cette roche reçoit sa préférence de profondeur la plus forte. Plage valide : -64 à 319.", + "tooltip.orespawn.rock.weight": "Chance relative parmi les roches admissibles de la même famille, pas un pourcentage global. Ajustez les poids des familles du géome pour réduire la part de cette famille. Zéro désactive cette roche.", + "tooltip.orespawn.rock.depth_peak": "Altitude Y absolue où cette roche est la plus favorisée (-2032 à 2031). La génération reste dans les limites de construction de la dimension.", "tooltip.orespawn.rock.depth_spread": "Comment progressivement la préférence de profondeur de la roche s'éloigne du pic de profondeur. Des valeurs plus élevées couvrent une plage verticale plus large ; plage valide : 1 à 512.", - "tooltip.orespawn.rock.min_y": "Y le plus bas où ce rocher peut remplacer le terrain. Plage valide : -64 à 319 ; il ne doit pas dépasser le Y maximum.", - "tooltip.orespawn.rock.max_y": "Y le plus élevé où ce rocher peut remplacer le terrain. Plage valide : -64 à 319 ; il ne doit pas être inférieur au minimum Y.", + "tooltip.orespawn.rock.min_y": "Altitude Y absolue minimale autorisée pour cette roche (-2032 à 2031). Ne doit pas dépasser le Y maximal ; la génération respecte les limites de construction de la dimension.", + "tooltip.orespawn.rock.max_y": "Altitude Y absolue maximale autorisée pour cette roche (-2032 à 2031). Ne doit pas être inférieure au Y minimal ; la génération respecte les limites de construction de la dimension.", "tooltip.orespawn.rock.ore_replaceable": "Autoriser les minerais gérés par OreSpawn à remplacer cette roche lorsqu'elle est sélectionnée comme famille hôte.", "tooltip.orespawn.biome.dimension": "Sélectionnez la dimension dont le placement du biome et les paramètres de matériaux du monde sont affichés.", "tooltip.orespawn.biome.palette_enabled": "Activez le placement du biome fourni par le fournisseur dans cette dimension. Le désactiver préserve les entrées de biome enregistrées.", diff --git a/src/main/resources/assets/orespawn/lang/ja_jp.json b/src/main/resources/assets/orespawn/lang/ja_jp.json index daecc0cf..7256e19f 100644 --- a/src/main/resources/assets/orespawn/lang/ja_jp.json +++ b/src/main/resources/assets/orespawn/lang/ja_jp.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "クラスターおよびクラウド パターンで使用される垂直範囲。有効な範囲: 0 ~ 64。", "tooltip.orespawn.ore.node_size": "クラスター パターン内の各ノードのブロック バジェット。有効な範囲: 1 ~ 32。", "tooltip.orespawn.rock.family": "ジオムと深度の優先度に使用するため、この岩石を堆積岩、変成岩、深成火成岩、火山性火成岩のいずれかに分類します。", - "tooltip.orespawn.rock.depth_peak": "この岩石が最も強い深さの優先度を受ける Y レベル。有効な範囲: -64 ~ 319。", + "tooltip.orespawn.rock.weight": "同じ岩石分類の生成可能な岩石間で使う相対的な重みで、全体に対する割合ではありません。分類全体の割合を減らすには地質地帯の分類の重みを調整します。0 にするとこの岩石を無効にします。", + "tooltip.orespawn.rock.depth_peak": "この岩石の深度の好みが最も強くなる絶対 Y 座標です(-2032~2031)。生成はディメンションの建築高度内に限られます。", "tooltip.orespawn.rock.depth_spread": "岩の深度設定が深度ピークからどの程度徐々に低下するか。値が大きいほど、より広い垂直範囲をカバーします。有効な範囲: 1 ~ 512。", - "tooltip.orespawn.rock.min_y": "この岩が地形に取って代わる可能性がある最も低い Y。有効な範囲: -64 ~ 319。最大 Y を超えてはなりません。", - "tooltip.orespawn.rock.max_y": "この岩が地形に置き換わる可能性のある最大 Y。有効な範囲: -64 ~ 319。最小値 Y を下回ってはなりません。", + "tooltip.orespawn.rock.min_y": "この岩石を生成できる最低の絶対 Y 座標です(-2032~2031)。最大 Y 以下にしてください。実際の生成範囲はディメンションの建築高度に制限されます。", + "tooltip.orespawn.rock.max_y": "この岩石を生成できる最高の絶対 Y 座標です(-2032~2031)。最小 Y 以上にしてください。実際の生成範囲はディメンションの建築高度に制限されます。", "tooltip.orespawn.rock.ore_replaceable": "ホスト ファミリとして選択された場合、OreSpawn 管理の鉱石がこの岩石を置き換えることを許可します。", "tooltip.orespawn.biome.dimension": "バイオームの配置とワールド マテリアルの設定が表示されるディメンションを選択します。", "tooltip.orespawn.biome.palette_enabled": "このディメンションでプロバイダーが提供するバイオームの配置を有効にします。これをオフにすると、保存されたバイオーム エントリが保持されます。", diff --git a/src/main/resources/assets/orespawn/lang/ko_kr.json b/src/main/resources/assets/orespawn/lang/ko_kr.json index fd2cc28b..87820aca 100644 --- a/src/main/resources/assets/orespawn/lang/ko_kr.json +++ b/src/main/resources/assets/orespawn/lang/ko_kr.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "클러스터 및 구름 패턴에 사용되는 수직 범위. 유효한 범위: 0~64.", "tooltip.orespawn.ore.node_size": "클러스터 패턴의 각 노드에 대한 블록 예산입니다. 유효 범위: 1 ~ 32.", "tooltip.orespawn.rock.family": "지옴 및 깊이 선호도에 사용할 수 있도록 이 암석을 퇴적암, 변성암, 관입 화성암 또는 화산 화성암으로 분류합니다.", - "tooltip.orespawn.rock.depth_peak": "이 암석이 가장 강한 깊이 선호도를 받는 Y 수준입니다. 유효한 범위: -64 ~ 319.", + "tooltip.orespawn.rock.weight": "같은 암석 계열의 생성 가능한 암석 사이에서 사용하는 상대 가중치이며 전체 비율이 아닙니다. 계열 전체의 비중을 줄이려면 지질 구역의 계열 가중치를 조정하세요. 0은 이 암석을 비활성화합니다.", + "tooltip.orespawn.rock.depth_peak": "이 암석의 깊이 선호도가 가장 높은 절대 Y 좌표입니다(-2032~2031). 생성은 차원의 건축 높이 범위 안으로 제한됩니다.", "tooltip.orespawn.rock.depth_spread": "암석의 깊이 선호가 Depth Peak에서 얼마나 점차적으로 멀어지는가. 값이 클수록 수직 범위가 더 넓어집니다. 유효한 범위: 1~512.", - "tooltip.orespawn.rock.min_y": "이 암석이 지형을 대체할 수 있는 가장 낮은 Y입니다. 유효 범위: -64~319; 최대 Y를 초과해서는 안 됩니다.", - "tooltip.orespawn.rock.max_y": "이 암석이 지형을 대체할 수 있는 가장 높은 Y입니다. 유효 범위: -64~319; 최소 Y보다 낮아서는 안됩니다.", + "tooltip.orespawn.rock.min_y": "이 암석이 생성될 수 있는 가장 낮은 절대 Y 좌표입니다(-2032~2031). 최대 Y보다 클 수 없으며, 실제 생성 범위는 차원의 건축 높이로 제한됩니다.", + "tooltip.orespawn.rock.max_y": "이 암석이 생성될 수 있는 가장 높은 절대 Y 좌표입니다(-2032~2031). 최소 Y보다 작을 수 없으며, 실제 생성 범위는 차원의 건축 높이로 제한됩니다.", "tooltip.orespawn.rock.ore_replaceable": "이 암석이 호스트 패밀리로 선택될 때 OreSpawn 관리 광석이 이 암석을 대체하도록 허용합니다.", "tooltip.orespawn.biome.dimension": "생물군계 배치 및 세계 물질 설정이 표시되는 차원을 선택합니다.", "tooltip.orespawn.biome.palette_enabled": "이 차원에서 공급자가 제공하는 생물군계 배치를 활성화합니다. 이 기능을 끄면 저장된 생물 군계 항목이 보존됩니다.", diff --git a/src/main/resources/assets/orespawn/lang/pt_br.json b/src/main/resources/assets/orespawn/lang/pt_br.json index f3eb5761..474598e3 100644 --- a/src/main/resources/assets/orespawn/lang/pt_br.json +++ b/src/main/resources/assets/orespawn/lang/pt_br.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Intervalo vertical usado por cluster e padrões de nuvem. Faixa válida: 0 a 64.", "tooltip.orespawn.ore.node_size": "Bloquear orçamento para cada nó no padrão Clusters. Faixa válida: 1 a 32.", "tooltip.orespawn.rock.family": "Classifique esta rocha como sedimentar, metamórfica, ígnea intrusiva ou ígnea vulcânica para preferências de geoma e profundidade.", - "tooltip.orespawn.rock.depth_peak": "Nível Y onde esta rocha recebe sua preferência de profundidade mais forte. Faixa válida: -64 a 319.", + "tooltip.orespawn.rock.weight": "Chance relativa entre as rochas elegíveis da mesma família, não uma porcentagem geral. Ajuste os pesos das famílias do geoma para reduzir a participação dessa família. Zero desativa esta rocha.", + "tooltip.orespawn.rock.depth_peak": "Y absoluto em que esta rocha tem sua maior preferência de profundidade (-2032 a 2031). A geração respeita os limites de construção da dimensão.", "tooltip.orespawn.rock.depth_spread": "Quão gradualmente a preferência de profundidade da rocha se afasta do Pico de Profundidade. Valores maiores cobrem uma faixa vertical mais ampla; intervalo válido: 1 a 512.", - "tooltip.orespawn.rock.min_y": "Y mais baixo onde esta rocha pode substituir o terreno. Faixa válida: -64 a 319; não deve exceder o Y máximo.", - "tooltip.orespawn.rock.max_y": "Y mais alto onde esta rocha pode substituir o terreno. Faixa válida: -64 a 319; ela não deve estar abaixo do mínimo Y.", + "tooltip.orespawn.rock.min_y": "Menor Y absoluto permitido para esta rocha (-2032 a 2031). Não pode superar o Y máximo; a geração fica restrita aos limites de construção da dimensão.", + "tooltip.orespawn.rock.max_y": "Maior Y absoluto permitido para esta rocha (-2032 a 2031). Não pode ser inferior ao Y mínimo; a geração fica restrita aos limites de construção da dimensão.", "tooltip.orespawn.rock.ore_replaceable": "Permitir que minérios gerenciados por OreSpawn substituam esta rocha quando ela for selecionada como uma família hospedeira.", "tooltip.orespawn.biome.dimension": "Selecione a dimensão cujas configurações de posicionamento de bioma e material mundial são mostradas.", "tooltip.orespawn.biome.palette_enabled": "Ativar posicionamento de bioma fornecido pelo provedor nesta dimensão. Desligá-lo preserva as entradas de bioma salvas.", diff --git a/src/main/resources/assets/orespawn/lang/ru_ru.json b/src/main/resources/assets/orespawn/lang/ru_ru.json index 4fa256df..aace2401 100644 --- a/src/main/resources/assets/orespawn/lang/ru_ru.json +++ b/src/main/resources/assets/orespawn/lang/ru_ru.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "Вертикальный диапазон, используемый шаблонами кластеров и облаков. Допустимый диапазон: от 0 до 64.", "tooltip.orespawn.ore.node_size": "Бюджет блока для каждого узла в шаблоне кластеров. Допустимый диапазон: от 1 до 32.", "tooltip.orespawn.rock.family": "Классифицируйте эту породу как осадочную, метаморфическую, интрузивную магматическую или вулканическую магматическую для предпочтений геомов и глубины.", - "tooltip.orespawn.rock.depth_peak": "Уровень Y, на котором эта порода получает наибольшее предпочтение по глубине. Допустимый диапазон: от -64 до 319.", + "tooltip.orespawn.rock.weight": "Относительный шанс среди подходящих пород того же семейства, а не общий процент. Чтобы уменьшить долю семейства, измените веса семейств геома. Ноль отключает эту породу.", + "tooltip.orespawn.rock.depth_peak": "Абсолютная высота Y с наибольшим предпочтением этой породы (-2032–2031). Генерация ограничена высотами строительства измерения.", "tooltip.orespawn.rock.depth_spread": "Как постепенно глубина камня падает от пика глубины. Большие значения охватывают более широкий вертикальный диапазон; допустимый диапазон: от 1 до 512.", - "tooltip.orespawn.rock.min_y": "Самый низкий Y, где этот камень может заменить местность. Допустимый диапазон: от -64 до 319; он не должен превышать Максимальный Y.", - "tooltip.orespawn.rock.max_y": "Самый высокий Y, при котором этот камень может заменять местность. Допустимый диапазон: от -64 до 319; оно не должно быть ниже минимального Y.", + "tooltip.orespawn.rock.min_y": "Минимальная абсолютная высота Y для этой породы (-2032–2031). Не должна превышать максимальную Y; генерация ограничена высотами строительства измерения.", + "tooltip.orespawn.rock.max_y": "Максимальная абсолютная высота Y для этой породы (-2032–2031). Не должна быть ниже минимальной Y; генерация ограничена высотами строительства измерения.", "tooltip.orespawn.rock.ore_replaceable": "Разрешить рудам, управляемым OreSpawn, заменить эту породу, когда она выбрана в качестве принимающего семейства.", "tooltip.orespawn.biome.dimension": "Выберите измерение, настройки размещения биома и мирового материала которого показаны.", "tooltip.orespawn.biome.palette_enabled": "Включите размещение биома, предоставленное провайдером, в этом измерении. При его отключении сохраняются сохраненные записи биома.", diff --git a/src/main/resources/assets/orespawn/lang/zh_cn.json b/src/main/resources/assets/orespawn/lang/zh_cn.json index 560c7057..2224e98a 100644 --- a/src/main/resources/assets/orespawn/lang/zh_cn.json +++ b/src/main/resources/assets/orespawn/lang/zh_cn.json @@ -77,10 +77,11 @@ "tooltip.orespawn.ore.vertical_spread": "集群和云模式使用的垂直范围。有效范围:0 到 64。", "tooltip.orespawn.ore.node_size": "集群模式中每个节点的块预算。有效范围:1 到 32。", "tooltip.orespawn.rock.family": "根据地质域和深度偏好,将这类岩石归为沉积岩、变质岩、侵入火成岩或火山火成岩。", - "tooltip.orespawn.rock.depth_peak": "此岩石接收最强深度偏好的 Y 级别。有效范围:-64 到 319。", + "tooltip.orespawn.rock.weight": "同一岩石类别中符合条件的岩石之间的相对权重,并非总体百分比。要减少整个类别的占比,请调整地下地貌的类别权重。设为零可禁用此岩石。", + "tooltip.orespawn.rock.depth_peak": "此岩石深度偏好最强的绝对 Y 高度(-2032 至 2031)。生成范围受该维度的建造高度限制。", "tooltip.orespawn.rock.depth_spread": "岩石的深度偏好逐渐远离深度峰值的程度。较大的值涵盖更广泛的垂直范围;有效范围:1 到 512。", - "tooltip.orespawn.rock.min_y": "该岩石可能取代地形的最低 Y 值。有效范围:-64至319;它不得超过最大 Y。", - "tooltip.orespawn.rock.max_y": "此岩石可能取代地形的最高 Y。有效范围:-64至319;它不得低于最低 Y。", + "tooltip.orespawn.rock.min_y": "允许此岩石生成的最低绝对 Y 高度(-2032 至 2031)。不得高于最大 Y;实际生成范围受该维度的建造高度限制。", + "tooltip.orespawn.rock.max_y": "允许此岩石生成的最高绝对 Y 高度(-2032 至 2031)。不得低于最小 Y;实际生成范围受该维度的建造高度限制。", "tooltip.orespawn.rock.ore_replaceable": "在选择该岩石作为宿主族时,允许 OreSpawn 管理的矿石替换该岩石。", "tooltip.orespawn.biome.dimension": "选择显示生物群落放置和世界材料设置的维度。", "tooltip.orespawn.biome.palette_enabled": "在此维度中启用提供者提供的生物群系放置。关闭它会保留保存的生物群落条目。", diff --git a/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java b/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java index c54664db..61faa7bc 100644 --- a/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/LocalizationParityTest.java @@ -81,7 +81,7 @@ class LocalizationParityTest { void everyLocaleMatchesEnglishKeysAndFormatting() throws Exception { JsonObject english = read(LANG_DIR.resolve("en_us.json")); Set englishKeys = JsonCopies.keys(english); - assertEquals(512, englishKeys.size(), + assertEquals(513, englishKeys.size(), "The target locale contract changed; review every shipped translation"); Set localeFiles = new HashSet<>(); Set observedIntentionalEnglishValues = new HashSet<>(); diff --git a/src/test/java/zone/moddev/mc/orespawn/api/WorldgenProviderTest.java b/src/test/java/zone/moddev/mc/orespawn/api/WorldgenProviderTest.java index fd727ce7..7ddb8cc8 100644 --- a/src/test/java/zone/moddev/mc/orespawn/api/WorldgenProviderTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/api/WorldgenProviderTest.java @@ -13,6 +13,23 @@ import net.minecraft.resources.ResourceLocation; class WorldgenProviderTest { + @Test + void rockDefaultsCoverTheDimensionAndExplicitDepthRangesRemainExact() { + WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1) + .rock(id("examplemod:full_height"), id("minecraft:stone"), GeologyFamily.METAMORPHIC, rock -> {}) + .rock(id("examplemod:deep_only"), id("minecraft:basalt"), GeologyFamily.METAMORPHIC, + rock -> rock.yRange(-128, -65).depth(-96, 24)) + .build(); + JsonObject rocks = provider.toJson().getAsJsonObject("rocks"); + assertEquals(net.minecraft.world.level.dimension.DimensionType.MIN_Y, + rocks.getAsJsonObject("examplemod:full_height").get("min_y").getAsInt()); + assertEquals(net.minecraft.world.level.dimension.DimensionType.MAX_Y, + rocks.getAsJsonObject("examplemod:full_height").get("max_y").getAsInt()); + assertEquals(-128, rocks.getAsJsonObject("examplemod:deep_only").get("min_y").getAsInt()); + assertEquals(-65, rocks.getAsJsonObject("examplemod:deep_only").get("max_y").getAsInt()); + assertEquals(-96, rocks.getAsJsonObject("examplemod:deep_only").get("depth_peak").getAsInt()); + } + @Test void terrainHostContractRetainsNaturalSourceOrder() { ResourceLocation dimension = id("surfaceprobe:the_end"); diff --git a/src/test/java/zone/moddev/mc/orespawn/client/GeologyEditorSessionTest.java b/src/test/java/zone/moddev/mc/orespawn/client/GeologyEditorSessionTest.java index 83842f93..0e5b1275 100644 --- a/src/test/java/zone/moddev/mc/orespawn/client/GeologyEditorSessionTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/client/GeologyEditorSessionTest.java @@ -29,6 +29,23 @@ class GeologyEditorSessionTest { @BeforeAll static void registerVanilla() { MinecraftTestBootstrap.registerVanilla(); } + @Test + void rockEditorKeepsExtendedHeightsAndDoesNotRewriteSavedRanges() { + GeologyEditorSession session = new GeologyEditorSession(WorldGeologyProfile.recommended(false)); + session.configureDefaultVanillaStrata(); + assertEquals(net.minecraft.world.level.dimension.DimensionType.MIN_Y, + session.rock("minecraft:deepslate").get("min_y").getAsInt()); + JsonObject rock = session.rock("minecraft:deepslate"); + rock.addProperty("min_y", -128); + rock.addProperty("max_y", -65); + rock.addProperty("depth_peak", -96); + assertTrue(session.validate().isEmpty(), session.validate().toString()); + GeologyEditorSession reopened = new GeologyEditorSession(session.profile()); + assertEquals(rock, reopened.rock("minecraft:deepslate")); + assertEquals(-128, reopened.rock("minecraft:deepslate").get("min_y").getAsInt()); + assertEquals(-65, reopened.rock("minecraft:deepslate").get("max_y").getAsInt()); + } + @Test void worldCreationRegistrySuppliesTheBiomeDirectoryWhenForgeStaticRegistryIsEmpty() { MappedRegistry loaded = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable()); diff --git a/src/test/java/zone/moddev/mc/orespawn/worldgen/ExtendedWorldHeightTest.java b/src/test/java/zone/moddev/mc/orespawn/worldgen/ExtendedWorldHeightTest.java new file mode 100644 index 00000000..8a1a9705 --- /dev/null +++ b/src/test/java/zone/moddev/mc/orespawn/worldgen/ExtendedWorldHeightTest.java @@ -0,0 +1,321 @@ +package zone.moddev.mc.orespawn.worldgen; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.mojang.serialization.Lifecycle; +import java.util.EnumSet; +import java.util.Set; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import net.minecraft.core.BlockPos; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.registries.VanillaRegistries; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.dimension.DimensionType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import zone.moddev.mc.orespawn.test.MinecraftTestBootstrap; + +class ExtendedWorldHeightTest { + @BeforeAll + static void bootstrap() { + MinecraftTestBootstrap.registerVanilla(); + } + + @AfterEach + void clearDimensionBounds() { + GeomeConfig.clearWorldHeights(); + } + + @Test + void configuredRockCanGenerateBelowTheVanillaFloor() { + BakedGeomeConfig config = bake(-128, -65); + assertEquals(Blocks.BASALT, config.pickRock(0, RockFamily.METAMORPHIC, -96, 0).getBlock(), + "the configured -128..-65 range must not be clamped to -64"); + assertEquals(Blocks.STONE, config.pickRock(0, RockFamily.METAMORPHIC, -129, 0).getBlock()); + assertEquals(Blocks.STONE, config.pickRock(0, RockFamily.METAMORPHIC, -64, 0).getBlock()); + } + + @Test + void configuredRockCanGenerateAboveTheVanillaCeiling() { + BakedGeomeConfig config = bake(320, 511); + assertEquals(Blocks.BASALT, config.pickRock(0, RockFamily.METAMORPHIC, 400, 0).getBlock(), + "the configured 320..511 range must not be clamped to 319"); + } + + @Test + void actualDimensionHeightIsDetectedAndSurvivesProfileRebaking() { + BakedGeomeConfig ordinary = bake(-128, 511); + BakedGeomeConfig extended = GeomeConfig.baked(Level.OVERWORLD, + LevelHeightAccessor.create(-128, 640)); + assertNotSame(ordinary, extended); + assertTrue(extended.hasReplacementAt(0, -128)); + assertTrue(extended.hasReplacementAt(0, 511)); + assertFalse(extended.hasReplacementAt(0, -129)); + assertFalse(extended.hasReplacementAt(0, 512)); + assertSame(extended, GeomeConfig.baked(Level.OVERWORLD, + LevelHeightAccessor.create(-128, 640))); + assertTrue(bake(-128, 511).hasReplacementAt(0, -128)); + GeomeConfig.clearWorldHeights(); + assertFalse(bake(-128, 511).hasReplacementAt(0, -128), + "the next server must not inherit the previous world's height cache"); + } + + @Test + void narrowerDimensionsKeepTheExistingPickerAndCannotChangeTheOverworld() { + BakedGeomeConfig ordinary = bake(-128, 511); + assertSame(ordinary, GeomeConfig.baked(Level.OVERWORLD, + LevelHeightAccessor.create(-64, 384))); + GeomeConfig.bindWorldHeight(Level.NETHER, -256, 768); + assertSame(ordinary, GeomeConfig.baked(Level.OVERWORLD)); + assertSame(ordinary, ordinary.withWorldHeight(0, 128)); + } + + @Test + void everyOriginalHeightKeepsTheSameChoicesWhenTablesAreExtended() { + JsonObject root = profile(-128, 511); + JsonObject rocks = root.getAsJsonObject("rocks"); + JsonObject tuff = rocks.getAsJsonObject("minecraft:basalt").deepCopy(); + tuff.addProperty("depth_peak", 80); + tuff.addProperty("weight", 0.3); + rocks.add("minecraft:tuff", tuff); + JsonObject granite = tuff.deepCopy(); + granite.addProperty("family", "igneous_intrusive"); + rocks.add("minecraft:granite", granite); + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig ordinary = GeomeConfig.baked(Level.OVERWORLD); + BakedGeomeConfig extended = ordinary.withWorldHeight(-128, 512); + for (int y = -64; y <= 319; y++) { + for (int bucket = 0; bucket < 256; bucket++) { + assertEquals(ordinary.pickFamily(0, y, bucket), extended.pickFamily(0, y, bucket)); + for (RockFamily family : RockFamily.values()) { + assertEquals(ordinary.pickRock(0, family, y, bucket), + extended.pickRock(0, family, y, bucket)); + } + } + } + } + + @Test + void depthPreferencesAreEvaluatedAtTheRealExtendedHeight() { + JsonObject root = profile(-128, 511); + JsonObject basalt = root.getAsJsonObject("rocks").getAsJsonObject("minecraft:basalt"); + basalt.addProperty("depth_peak", -112); + basalt.addProperty("depth_spread", 8); + JsonObject tuff = basalt.deepCopy(); + tuff.addProperty("depth_peak", -16); + root.getAsJsonObject("rocks").add("minecraft:tuff", tuff); + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig config = GeomeConfig.baked(Level.OVERWORLD, LevelHeightAccessor.create(-128, 640)); + int deepBasalt = 0; + int upperTuff = 0; + for (int bucket = 0; bucket < 256; bucket++) { + if (config.pickRock(0, RockFamily.METAMORPHIC, -112, bucket).is(Blocks.BASALT)) deepBasalt++; + if (config.pickRock(0, RockFamily.METAMORPHIC, -16, bucket).is(Blocks.TUFF)) upperTuff++; + } + assertTrue(deepBasalt > 210, "deep preference was flattened at vanilla's floor: " + deepBasalt); + assertTrue(upperTuff > 210, "the shallower preference must remain independent: " + upperTuff); + } + + @Test + void uncoveredDepthKeepsNativeDeepslateWithoutMarkingTheChunkChanged() { + BakedGeomeConfig config = bake(-64, 319).withWorldHeight(-128, 512); + ProtoChunk chunk = chunkWithDeepslate(-96); + replace(config, chunk); + assertTrue(chunk.getBlockState(new BlockPos(-32, -96, 48)).is(Blocks.DEEPSLATE)); + assertFalse(chunk.isUnsaved()); + Biome biome = chunk.getNoiseBiome(-8, -24, 12).value(); + assertNull(new GeomeGeology(0L, config) + .sampleColumn(biome, Biomes.PLAINS.location(), -32, 48).familyAt(-96)); + } + + @Test + void realChunkGenerationAndColumnSamplingAgreeBelowAndAboveVanillaHeights() { + for (int y : new int[] { -128, -96, 400, 511 }) { + BakedGeomeConfig config = bake(-128, 511).withWorldHeight(-128, 512); + ProtoChunk chunk = chunkWithDeepslate(y); + replace(config, chunk); + assertTrue(chunk.getBlockState(new BlockPos(-32, y, 48)).is(Blocks.BASALT), "Y=" + y); + GeomeGeology geology = new GeomeGeology(0L, config); + Biome biome = chunk.getNoiseBiome(-8, Math.floorDiv(y, 4), 12).value(); + assertTrue(geology.sampleColumn(biome, Biomes.PLAINS.location(), -32, 48).rockAt(y).is(Blocks.BASALT)); + assertTrue(chunk.getBlockState(new BlockPos(-31, y, 48)).is(Blocks.WATER)); + assertTrue(chunk.getBlockState(new BlockPos(-30, y, 48)).is(Blocks.BEDROCK)); + } + } + + @Test + void originalSmoothSkyAlgorithmAlsoUsesExtendedBounds() { + JsonObject root = profile(-128, -65); + root.getAsJsonObject("formations").addProperty("algorithm", "sky_v1"); + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig config = GeomeConfig.baked(Level.OVERWORLD, LevelHeightAccessor.create(-128, 640)); + ProtoChunk chunk = chunkWithDeepslate(-65); + replace(config, chunk); + assertTrue(chunk.getBlockState(new BlockPos(-32, -65, 48)).is(Blocks.BASALT)); + } + + @Test + void omittedEndpointsUseTheActualBoundsOfACustomDimension() { + JsonObject root = profile(-128, 511); + JsonObject basalt = root.getAsJsonObject("rocks").getAsJsonObject("minecraft:basalt"); + basalt.remove("min_y"); + basalt.remove("max_y"); + ResourceKey custom = ResourceKey.create(Registries.DIMENSION, + ResourceLocation.parse("test:very_deep")); + JsonArray dimensions = new JsonArray(); + dimensions.add("minecraft:overworld"); + dimensions.add(custom.location().toString()); + basalt.add("dimensions", dimensions); + root.getAsJsonObject("terrain_dimensions").add(custom.location().toString(), + root.getAsJsonObject("terrain_dimensions").get("minecraft:overworld").deepCopy()); + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig config = GeomeConfig.baked(custom, + LevelHeightAccessor.create(DimensionType.MIN_Y, 4064)); + assertTrue(config.hasReplacementAt(0, DimensionType.MIN_Y)); + assertTrue(config.hasReplacementAt(0, DimensionType.MAX_Y)); + assertTrue(config.pickRock(0, RockFamily.METAMORPHIC, + DimensionType.MIN_Y, 7).is(Blocks.BASALT)); + assertFalse(GeomeConfig.baked(Level.OVERWORLD).hasReplacementAt(0, DimensionType.MIN_Y)); + } + + @Test + void zeroFamilyOrGeomeWeightDoesNotMakeFallbackStoneReplaceNativeRock() { + for (boolean familyDisabled : new boolean[] { true, false }) { + JsonObject root = profile(-128, 511); + if (familyDisabled) { + root.getAsJsonObject("geomes").getAsJsonObject("test:deep") + .getAsJsonObject("families").addProperty("metamorphic", 0); + } else { + JsonObject weights = new JsonObject(); + weights.addProperty("test:deep", 0); + root.getAsJsonObject("rocks").getAsJsonObject("minecraft:basalt").add("geomes", weights); + } + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig config = GeomeConfig.baked(Level.OVERWORLD, + LevelHeightAccessor.create(-128, 640)); + assertFalse(config.hasReplacementAt(0, -96)); + ProtoChunk chunk = chunkWithDeepslate(-96); + replace(config, chunk); + assertTrue(chunk.getBlockState(new BlockPos(-32, -96, 48)).is(Blocks.DEEPSLATE)); + assertFalse(chunk.isUnsaved()); + } + } + + @Test + void parallelFirstUseOnlyPublishesCompleteHeightTables() throws Exception { + bake(-128, 511); + CountDownLatch start = new CountDownLatch(1); + try (var workers = Executors.newFixedThreadPool(8)) { + var results = new ArrayList>(); + for (int i = 0; i < 32; i++) { + results.add(workers.submit(() -> { + assertTrue(start.await(10, TimeUnit.SECONDS)); + return GeomeConfig.baked(Level.OVERWORLD, LevelHeightAccessor.create(-128, 640)); + })); + } + start.countDown(); + for (Future result : results) { + BakedGeomeConfig config = result.get(10, TimeUnit.SECONDS); + assertTrue(config.hasReplacementAt(0, -128)); + assertTrue(config.hasReplacementAt(0, 511)); + } + } + } + + @Test + void disablingEveryRockKeepsNativeTerrainAndOnlyRetainsASamplerFallback() { + for (String algorithm : new String[] { "stable_layers", "sky_v1" }) { + JsonObject root = profile(-128, 511); + root.getAsJsonObject("rocks").getAsJsonObject("minecraft:basalt").addProperty("enabled", false); + root.getAsJsonObject("formations").addProperty("algorithm", algorithm); + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(root)); + BakedGeomeConfig config = GeomeConfig.baked(Level.OVERWORLD, + LevelHeightAccessor.create(-128, 640)); + ProtoChunk chunk = chunkWithDeepslate(-32); + replace(config, chunk); + assertTrue(chunk.getBlockState(new BlockPos(-32, -32, 48)).is(Blocks.DEEPSLATE), algorithm); + assertFalse(chunk.isUnsaved()); + assertTrue(config.pickRock(0, RockFamily.SEDIMENTARY, -32, 0).is(Blocks.STONE)); + assertTrue(config.isOreReplaceable(Blocks.STONE.defaultBlockState())); + } + } + + private static ProtoChunk chunkWithDeepslate(int y) { + MappedRegistry biomes = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable()); + biomes.register(Biomes.PLAINS, VanillaRegistries.createLookup().lookupOrThrow(Registries.BIOME) + .getOrThrow(Biomes.PLAINS).value(), RegistrationInfo.BUILT_IN); + ProtoChunk chunk = new ProtoChunk(new ChunkPos(-2, 3), UpgradeData.EMPTY, + LevelHeightAccessor.create(-128, 640), biomes.freeze(), null); + chunk.setPersistedStatus(ChunkStatus.BIOMES); + chunk.setBlockState(new BlockPos(-32, y, 48), Blocks.DEEPSLATE.defaultBlockState(), false); + chunk.setBlockState(new BlockPos(-31, y, 48), Blocks.WATER.defaultBlockState(), false); + chunk.setBlockState(new BlockPos(-30, y, 48), Blocks.BEDROCK.defaultBlockState(), false); + Heightmap.primeHeightmaps(chunk, EnumSet.of(Heightmap.Types.WORLD_SURFACE_WG)); + chunk.setUnsaved(false); + return chunk; + } + + private static void replace(BakedGeomeConfig config, ProtoChunk chunk) { + BakedTerrainDimension terrain = new BakedTerrainDimension(Level.OVERWORLD, Set.of(), Set.of(), + Set.of(Blocks.STONE, Blocks.DEEPSLATE)); + new GeomeGeology(0L, config).replaceStoneInChunk(null, chunk, terrain); + } + + private static BakedGeomeConfig bake(int min, int max) { + GeomeConfig.applyWorldProfile(WorldGeologyProfile.recommended(false).withRoot(profile(min, max))); + return GeomeConfig.baked(Level.OVERWORLD); + } + + private static JsonObject profile(int min, int max) { + JsonObject root = WorldGeologyProfile.recommended(false).rootCopy(); + JsonObject geomes = new JsonObject(); + JsonObject geome = new JsonObject(); + geome.addProperty("base", 1.0); + JsonObject families = new JsonObject(); + families.addProperty("metamorphic", 1.0); + geome.add("families", families); + geomes.add("test:deep", geome); + root.add("geomes", geomes); + root.add("biomes", new JsonObject()); + root.add("biome_dictionary", new JsonObject()); + JsonObject rocks = new JsonObject(); + JsonObject rock = new JsonObject(); + rock.addProperty("enabled", true); + rock.addProperty("family", "metamorphic"); + rock.addProperty("min_y", min); + rock.addProperty("max_y", max); + rock.addProperty("depth_peak", (min + max) / 2); + rock.addProperty("weight", 1.0); + rocks.add("minecraft:basalt", rock); + root.add("rocks", rocks); + JsonObject terrain = new JsonObject(); + JsonObject dimension = new JsonObject(); + JsonArray hosts = new JsonArray(); + hosts.add("minecraft:stone"); + hosts.add("minecraft:deepslate"); + dimension.add("host_blocks", hosts); + terrain.add("minecraft:overworld", dimension); + root.add("terrain_dimensions", terrain); + return root; + } +}