Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down
6 changes: 6 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrePatternType>` using
Expand Down
22 changes: 21 additions & 1 deletion docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/PLAYER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
13 changes: 13 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docs/VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
6 changes: 3 additions & 3 deletions docs/schemas/profile-common.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions gradle/release/artifacts.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -63,9 +63,9 @@ tasks.register('verifyReleaseConfiguration') {
throw new GradleException('Unexpected dispatcher or Java target metadata')
}
List<String> 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) {
Expand Down
2 changes: 1 addition & 1 deletion gradle/verification/client-and-benchmark.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/zone/moddev/mc/orespawn/api/GeologyColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<GeologyFamily> familyAt(int y);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<ResourceLocation, Double> geomes = new LinkedHashMap<>();
Expand Down
Loading
Loading