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
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
* text=auto
# Store and check out repository text as LF on every platform. Windows command
# files are the sole exception below.
* text=auto eol=lf

*.bat text eol=crlf
#*.bat text eol=lf
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
Version 4.0.5

* Complete native translations for every shipped non-English locale
* Add automatic fresh-and-reload validation for provider-owned custom biomes
* Correct NeoForge biome terminology and Minecraft 1.21.11 Identifier documentation
* Preserve public API major 1 and provider/global/world schemas 4/6/5

Version 3.3.1

* Fix several bugs
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MMD OreSpawn

OreSpawn 4 is a provider-driven world-generation engine for Minecraft 1.21.1.
OreSpawn 4 is a provider-driven world-generation engine for Minecraft 1.21.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.
Expand Down Expand Up @@ -79,10 +79,19 @@ exported to `config/orespawn-guide/` without overwriting existing files.
Use Java 21 from the repository root:

```powershell
.\gradlew.bat test processResources build javadoc --no-daemon
.\gradlew.bat clean build javadoc --no-daemon
.\gradlew.bat eclipse --no-daemon
```

`build` runs the standard `check` lifecycle. In addition to the JUnit suite,
that lifecycle packages a test-only provider mod, loads its custom biome in
normal noise terrain, and verifies both fresh generation and reopening the
same saved world. The fixture is not included in OreSpawn's published jars.

Import or refresh the project with Eclipse Buildship. NeoGradle supplies the
Eclipse model and run configurations through the `eclipse` task; this branch
does not use ForgeGradle's `genEclipseRuns` task.

Machine-specific `AGENTS.md` and `agent-notes/` files are intentionally ignored.
Public developer and AI integration guidance lives in `docs/` and is included
in the built jar.
Expand Down
80 changes: 80 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ runs {
file('src/generated/resources/').absolutePath, '--existing',
file('src/main/resources/').absolutePath
}

['Fresh', 'Reload'].each { String phase ->
register("biomeIntegration${phase}") {
runType 'gameTestServer'
workingDirectory layout.buildDirectory.dir('biome-integration-run')
systemProperty 'neoforge.enabledGameTestNamespaces', 'cakeworldprobe'
systemProperty 'forge.logging.console.level', 'info'
systemProperty 'cakeworld.biomeIntegrationPhase', phase.toLowerCase(Locale.ROOT)
modSource project.sourceSets.main
}
}
}

configurations {
Expand Down Expand Up @@ -188,6 +199,75 @@ tasks.named('test', Test).configure {
useJUnitPlatform()
}

def biomeIntegrationClasses = layout.buildDirectory.dir('biome-integration-fixture/classes')
def compileBiomeIntegrationTestMod = tasks.register('compileBiomeIntegrationTestMod', JavaCompile) {
dependsOn tasks.named('classes')
source fileTree('src/biomeIntegrationTest/java')
classpath = files(sourceSets.main.output, sourceSets.main.compileClasspath)
destinationDirectory.set(biomeIntegrationClasses)
javaCompiler.set(javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(21)
})
options.release = 16
options.encoding = 'UTF-8'
}

def biomeIntegrationTestModJar = tasks.register('biomeIntegrationTestModJar', Jar) {
dependsOn compileBiomeIntegrationTestMod
archiveFileName = 'cakeworldprobe.jar'
destinationDirectory = layout.buildDirectory.dir('biome-integration-fixture')
manifest {
attributes 'MixinConfigs': 'cakeworldprobe.mixins.json'
}
from biomeIntegrationClasses
from 'src/biomeIntegrationTest/resources'
}

def biomeIntegrationRunDirectory = layout.buildDirectory.dir('biome-integration-run')
def prepareBiomeIntegrationTest = tasks.register('prepareBiomeIntegrationTest') {
dependsOn biomeIntegrationTestModJar
doLast {
delete biomeIntegrationRunDirectory
copy {
from biomeIntegrationTestModJar.flatMap { it.archiveFile }
into biomeIntegrationRunDirectory.map { it.dir('mods') }
}
}
}

tasks.configureEach {
if (name == 'runBiomeIntegrationFresh') {
dependsOn prepareBiomeIntegrationTest
} else if (name == 'runBiomeIntegrationReload') {
dependsOn 'runBiomeIntegrationFresh'
}
}

def biomeIntegrationTest = tasks.register('biomeIntegrationTest') {
group = 'verification'
description = 'Verifies a provider-owned custom biome in fresh and reloaded normal terrain.'
dependsOn 'runBiomeIntegrationReload'
doLast {
File marker = biomeIntegrationRunDirectory.get().file(
'gametestserver/gametestworld/cakeworld-biome-integration.properties').asFile
if (!marker.isFile()) {
throw new GradleException("Biome integration completion marker is missing: ${marker}")
}
Properties result = new Properties()
marker.withInputStream { result.load(it) }
if (result.getProperty('reload_verified') != 'true') {
throw new GradleException("Biome integration reload was not verified: ${marker}")
}
logger.lifecycle('Custom-biome integration verified: {} chunks, {} top blocks, {} filler blocks, fresh + reload',
result.getProperty('matching_chunks'), result.getProperty('pink_surface'),
result.getProperty('white_filler'))
}
}

tasks.named('check') {
dependsOn biomeIntegrationTest
}

idea {
module {
downloadSources = true
Expand Down
85 changes: 12 additions & 73 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,15 @@
# OreSpawn Integration Notes For Coding Agents
# OreSpawn Documentation Map

OreSpawn 4.0 is a required NeoForge mod and declarative world-generation engine.
Public API major version 1 consists only of `zone.moddev.mc.orespawn.api`. Treat
every other Java package as internal and unstable.
This index is for navigating the documentation to learn how to integrate with
and use OreSpawn with a mod or modpack.
Start with [DEVELOPER_GUIDE.md](DEVELOPER_GUIDE.md).

Integration entry points:
Use the focused guides for implementation details:

- Java declarations: `OreSpawnApi.enqueue(WorldgenProvider)` during
`InterModEnqueueEvent`.
- Packaged declarations: `data/<modid>/orespawn/provider.json`.
- Pack overrides: `config/<modid>-orespawn.json`.
- Active queries: `getActiveProfile(MinecraftServer)` and
`createSampler(ServerLevel)`.
- Native-ore takeover: disable only when `isOreTakeoverActive(modid)` is true.

Configuration contracts:

- Global `config/orespawn-worldgen.json`: schema 6.
- World `serverconfig/orespawn-worldgen.json`: schema 5.
- Provider files: schema 4; legacy schemas 1-3 remain accepted.
- Ore placement accepts fixed `quantity` or paired inclusive
`min_quantity`/`max_quantity` values in the range 1-64. A complete range is
authoritative when both forms exist.
- `dimension_selectors.orespawn:all_except_nether_end` applies to ordinary
dimensions but never Nether or End. Explicit dimension entries override it
per ore and must also drive vanilla-feature suppression.
- JSON Schemas and examples are under `META-INF/orespawn/docs/` in the jar.
- Schema 4 providers may declare `biome_palettes` and `dimension_materials`.
Palettes wrap the native dimension biome source. Region presets are 128,
256, 512, 1024, and 2048 blocks.

Lifecycle and ownership:

- NeoForge setup is parallel. Never mutate OreSpawn internals directly.
- A pack override file is authoritative over packaged and API definitions for
the same provider. A malformed override fails closed.
- Provider rule IDs use the provider namespace. A rule's `block` or weighted
output may reference any installed block.
- Definitions freeze at load completion and change only after restart or an
operator `/orespawn reload`.
- Auto-selected templates apply only to fresh worlds with no explicit
`default_template`. Highest priority wins, then lexical ID. Existing world
profiles never auto-switch.

Performance constraints:

- Do not request callbacks in block-generation loops.
- Registry IDs remain `ResourceLocation` values until setup-time baking.
- Dimension, tag, alias, biome, geome, family, pattern, and block-state
resolution occurs before generation.
- Biome palettes bake holders, climate bounds, namespace filters, weights,
surfaces, and dimension materials. Provider callbacks never run in selection.
- Ore rules support `uniform`, `triangle`, `bottom_triangle`, and
`uniform_bottom_triangle` height distributions plus a 0-1
`discard_chance_on_air_exposure` value for buried deposits.
- The chunk hot path must contain no config reads, registry access, strings,
logging, reflection, or per-block allocation.
- Cache biome filters as registry keys, never `Biome` object identities;
dynamic-registry biome instances are not identity-stable.
- Ore and flat-bedrock retrogen are bounded and marker-based. Terrain strata
are never retrogened.

Compatibility defaults:

- Standalone OreSpawn is passive: no rocks, terrain dimensions, fluid deposits, ore
suppression, retrogen, or flat bedrock are enabled by default.
- The Overworld is the conventional geology target, but a provider must opt it
in. Nether and End terrain remain untouched unless explicitly configured.
- Mineralogy 6 is a provider, not a public-API compatibility facade. Do not use
removed `zone.moddev.mc.mineralogy.api` classes.

Common tasks are documented in `API.md`, `PROVIDERS.md`, `FEATURES.md`,
`TEMPLATES.md`, `BIOMES.md`, and `DIMENSIONS.md`. Start with
`DEVELOPER_GUIDE.md` when the task is broader than one isolated schema or API
question.
- [API.md](API.md) for the supported Java API;
- [PROVIDERS.md](PROVIDERS.md) for packaged and configurable providers;
- [FEATURES.md](FEATURES.md) for rocks, ores, deposits, and geology;
- [BIOMES.md](BIOMES.md) and [DIMENSIONS.md](DIMENSIONS.md) for world integration;
- [TEMPLATES.md](TEMPLATES.md) for selectable world styles;
- [CONFIGURATION.md](CONFIGURATION.md) for configuration behavior;
- [README.md](README.md) for schemas, examples, and the complete documentation index.
34 changes: 17 additions & 17 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Submit declarations during `InterModEnqueueEvent`:

```java
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.rock(ResourceLocation.fromNamespaceAndPath("examplemod", "slate"), GeologyFamily.METAMORPHIC, rock -> rock
.rock(Identifier.fromNamespaceAndPath("examplemod", "slate"), GeologyFamily.METAMORPHIC, rock -> rock
.depth(12, 36)
.weight(1.2)
.oreReplaceable(true))
Expand All @@ -33,7 +33,7 @@ For a complete ore-only Java example, including dimensions, height curves,
patterns, and host tags, see `DEVELOPER_GUIDE.md`.

Definitions are immutable after `build()`. Registry references remain
`ResourceLocation` values until OreSpawn validates and bakes them. Provider
`Identifier` values until OreSpawn validates and bakes them. Provider
messages are processed through NeoForge IMC and frozen at load completion; direct
cross-mod mutation during parallel setup is unsupported.

Expand All @@ -56,17 +56,17 @@ FormationDefinition formations = FormationDefinition.builder()
.waviness(FormationPreset.LARGE)
.build();
FluidDepositDefinition brine = FluidDepositDefinition.builder(
ResourceLocation.fromNamespaceAndPath("examplemod", "fluid_deposit/brine"),
ResourceLocation.fromNamespaceAndPath("examplemod", "brine"))
.dimension(ResourceLocation.fromNamespaceAndPath("minecraft", "overworld"), placement -> placement
Identifier.fromNamespaceAndPath("examplemod", "fluid_deposit/brine"),
Identifier.fromNamespaceAndPath("examplemod", "brine"))
.dimension(Identifier.fromNamespaceAndPath("minecraft", "overworld"), placement -> placement
.yRange(-48, 32)
.attempts(0.05)
.radius(4, 10)
.verticalRadius(2, 4)
.maxLobes(3)
.minSolidCover(2)
.minSolidShell(1)
.hostTag(ResourceLocation.fromNamespaceAndPath("minecraft", "stone_ore_replaceables")))
.hostTag(Identifier.fromNamespaceAndPath("minecraft", "stone_ore_replaceables")))
.build();

WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
Expand All @@ -77,14 +77,14 @@ WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
`OilDefinition` and template `.oil(...)` remain deprecated migration adapters
for one legacy oil rule. New integrations should use `FluidDepositDefinition`.

NeoForge 21.1 biomes are data-driven registry entries. Package biome JSON under
Minecraft 1.21.11 biomes are data-driven registry entries. Package biome JSON under
`data/<modid>/worldgen/biome/`, or generate it with a
`DatapackBuiltinEntriesProvider`. `OreSpawnBiomes.copyAndRegister` is an
optional bootstrap/datagen convenience for cloning a known biome:

```java
public static final ResourceKey<Biome> CANDY_PLAINS = ResourceKey.create(
Registries.BIOME, ResourceLocation.fromNamespaceAndPath("examplemod", "candy_plains"));
Registries.BIOME, Identifier.fromNamespaceAndPath("examplemod", "candy_plains"));

public static final RegistrySetBuilder BIOME_BUILDER = new RegistrySetBuilder()
.add(Registries.BIOME, context -> {
Expand All @@ -99,21 +99,21 @@ materials through the same OreSpawn provider:

```java
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.biomePalette(ResourceLocation.fromNamespaceAndPath("examplemod", "overworld"),
ResourceLocation.fromNamespaceAndPath("minecraft", "overworld"), palette -> palette
.biomePalette(Identifier.fromNamespaceAndPath("examplemod", "overworld"),
Identifier.fromNamespaceAndPath("minecraft", "overworld"), palette -> palette
.mode(BiomePlacementMode.REPLACE)
.scope(BiomeReplacementScope.MINECRAFT_ONLY)
.regionSize(BiomeRegionSize.LARGE)
.coverage(1.0)
.fallbackWeight(0.0)
.biome(ResourceLocation.fromNamespaceAndPath("examplemod", "candy_plains"), biome -> biome
.biome(Identifier.fromNamespaceAndPath("examplemod", "candy_plains"), biome -> biome
.weight(3.0)
.similarBiome(ResourceLocation.fromNamespaceAndPath("minecraft", "plains"))))
.dimensionMaterials(ResourceLocation.fromNamespaceAndPath("examplemod", "overworld_materials"),
ResourceLocation.fromNamespaceAndPath("minecraft", "overworld"), materials -> materials
.defaultFluid(ResourceLocation.fromNamespaceAndPath("examplemod", "lemonade"))
.snowBlock(ResourceLocation.fromNamespaceAndPath("examplemod", "icing"))
.iceBlock(ResourceLocation.fromNamespaceAndPath("examplemod", "frozen_lemonade")))
.similarBiome(Identifier.fromNamespaceAndPath("minecraft", "plains"))))
.dimensionMaterials(Identifier.fromNamespaceAndPath("examplemod", "overworld_materials"),
Identifier.fromNamespaceAndPath("minecraft", "overworld"), materials -> materials
.defaultFluid(Identifier.fromNamespaceAndPath("examplemod", "lemonade"))
.snowBlock(Identifier.fromNamespaceAndPath("examplemod", "icing"))
.iceBlock(Identifier.fromNamespaceAndPath("examplemod", "frozen_lemonade")))
.build();
```

Expand Down
4 changes: 2 additions & 2 deletions docs/BIOMES.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ known biome's complete builder inside a `RegistrySetBuilder` bootstrap:

```java
public static final ResourceKey<Biome> CANDY_PLAINS = ResourceKey.create(
Registries.BIOME, ResourceLocation.fromNamespaceAndPath("examplemod", "candy_plains"));
Registries.BIOME, Identifier.fromNamespaceAndPath("examplemod", "candy_plains"));

public static final RegistrySetBuilder BIOME_BUILDER = new RegistrySetBuilder()
.add(Registries.BIOME, context -> {
Expand All @@ -114,7 +114,7 @@ public static final RegistrySetBuilder BIOME_BUILDER = new RegistrySetBuilder()
datagen that deliberately supplies every required climate, effects, spawn, and
generation field. Both helpers create datapack content; live placement belongs
in the provider declaration. Do not use a static `DeferredRegister<Biome>`:
NeoForge 21.1 biomes belong to the dynamic world registry.
Minecraft 1.21.11 biomes belong to the dynamic world registry.

## Surfaces And Materials

Expand Down
14 changes: 11 additions & 3 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ import zone.moddev.mc.orespawn.api.OreDimensionSelector;
import zone.moddev.mc.orespawn.api.OrePattern;
import zone.moddev.mc.orespawn.api.OreSpawnApi;
import zone.moddev.mc.orespawn.api.WorldgenProvider;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.neoforged.fml.event.lifecycle.InterModEnqueueEvent;

private void enqueueWorldgen(InterModEnqueueEvent event) {
ResourceLocation tin = ResourceLocation.fromNamespaceAndPath("examplemod", "tin_ore");
Identifier tin = Identifier.fromNamespaceAndPath("examplemod", "tin_ore");
WorldgenProvider provider = WorldgenProvider.builder("examplemod", 1)
.ore(tin, ore -> ore
.retrogen(false)
Expand All @@ -100,7 +100,7 @@ private void enqueueWorldgen(InterModEnqueueEvent event) {
.quantityRange(4, 11)
.pattern(OrePattern.VEIN)
.heightDistribution(OreHeightDistribution.TRIANGLE)
.hostTag(ResourceLocation.fromNamespaceAndPath("minecraft", "stone_ore_replaceables"))))
.hostTag(Identifier.fromNamespaceAndPath("minecraft", "stone_ore_replaceables"))))
.build();

OreSpawnApi.enqueue(provider);
Expand Down Expand Up @@ -197,3 +197,11 @@ bounded ore or bedrock retrogen is enabled.
and without compatibility mods.
6. Confirm the provider appears in `/orespawn status`.
7. Test a new world; profile edits do not rewrite already generated terrain.

OreSpawn's own standard `check` lifecycle includes a consumer-style biome
integration test. It loads a separate test provider and datapack biome, proves
the provider is active, verifies biome selection, climate and configured
surface blocks in non-flat terrain, then reopens and rechecks the same saved
world. Run `gradlew check` (or `gradlew build`, which includes it) before
publishing any change to biome registration, palettes, surfaces or profile
persistence.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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
# The mod version. See https://semver.org/
mod_version=4.0.4
mod_version=4.0.5
# 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
Loading
Loading