Done and checked: surface and cave-floor ore nodes (companion mod ore/OreNodes.kt), bigger and rarer underground veins; template with nodes passed the world check and the island quick check (node self-tests). Built, not yet tested: catching rules (catch rate falls with the level gap to the trainer's strongest Pokemon, failed catch enrages, strong wild Pokemon hunt weaker trainers; catching/, Fight or Flight config), bee nests emptied when their bee is turned away. In progress: the Deep Dark no longer fills the deepslate layer (split by temperature: cold = Deep Dark, hot = basalt deltas, warm = ordinary caves; world/biomes.json), zone layout regenerated; cave zone types added to world/zones.json but tools/zone_spawns.py doesn't use them yet; template not rebuilt for the new caves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
4.8 KiB
Python
113 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Write the island world type from world/biomes.json.
|
|
|
|
Vanilla's overworld biome parameters come from Minecraft's own data generator
|
|
(run on the vanilla server jar that the NeoForge installer put in
|
|
build/server; no server is started). Every entry is sent to its target biome
|
|
from world/biomes.json ("remap"), or cut into pieces along one climate
|
|
parameter ("split": each piece of the parameter's range goes to its own
|
|
biome; the parts no piece covers are dropped, so the biome around them takes
|
|
over), the extra entries are appended ("add"), and the result becomes the
|
|
inline biome list of WorldGen Editor's delegate in
|
|
datapacks/core/data/cobblesurvival/worldgen/world_preset/island.json.
|
|
|
|
The world type has no Nether dimension.
|
|
|
|
Usage: tools/biome_layout.py (run tools/smoke_test.py once first so build/server exists)
|
|
"""
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from mcserver import CACHE, PACK, ROOT, SERVER # noqa: E402
|
|
from packwiz_sync import java # noqa: E402
|
|
|
|
PRESET = ROOT / "datapacks/core/data/cobblesurvival/worldgen/world_preset/island.json"
|
|
|
|
|
|
def vanilla_parameters():
|
|
version = PACK["versions"]["minecraft"]
|
|
report = CACHE / f"datagen-{version}" / "reports/biome_parameters/minecraft/overworld.json"
|
|
if not report.exists():
|
|
jar = SERVER / "libraries/net/minecraft/server" / version / f"server-{version}.jar"
|
|
if not jar.exists():
|
|
sys.exit(f"{jar} is missing; run tools/smoke_test.py once so the NeoForge installer fetches it")
|
|
work = CACHE / f"datagen-{version}"
|
|
work.mkdir(parents=True, exist_ok=True)
|
|
subprocess.run([java(), "-DbundlerMainClass=net.minecraft.data.Main", "-jar", str(jar),
|
|
"--reports", "--output", str(work)], cwd=work, check=True, stdout=subprocess.DEVNULL)
|
|
return json.loads(report.read_text())["biomes"]
|
|
|
|
|
|
def split(entry, rule):
|
|
"""The pieces of a vanilla entry along rule["parameter"], each sent to its own biome."""
|
|
name = rule["parameter"]
|
|
value = entry["parameters"][name]
|
|
low, high = (value, value) if isinstance(value, (int, float)) else value
|
|
pieces = []
|
|
for piece in rule["pieces"]:
|
|
a, b = max(low, piece["range"][0]), min(high, piece["range"][1])
|
|
if a < b or (a == b and low == high):
|
|
pieces.append({"biome": piece["to"], "parameters": {**entry["parameters"], name: [a, b]}})
|
|
return pieces
|
|
|
|
|
|
def main():
|
|
spec = json.loads((ROOT / "world/biomes.json").read_text())
|
|
targets = set(spec["allowed"]) | set(spec["water"])
|
|
entries, unmapped = [], set()
|
|
splits = {s["biome"]: s for s in spec.get("split", [])}
|
|
for entry in vanilla_parameters():
|
|
if entry["biome"] in splits:
|
|
entries += split(entry, splits[entry["biome"]])
|
|
continue
|
|
target = spec["remap"].get(entry["biome"])
|
|
if target is None:
|
|
unmapped.add(entry["biome"])
|
|
continue
|
|
entries.append({"biome": target, "parameters": entry["parameters"]})
|
|
for extra in spec["add"]:
|
|
entries.append({"biome": extra["biome"], "parameters": extra["parameters"]})
|
|
bad_targets = {e["biome"] for e in entries} - targets
|
|
if unmapped or bad_targets:
|
|
sys.exit(f"vanilla biomes without a target: {sorted(unmapped)}; targets outside the list: {sorted(bad_targets)}")
|
|
|
|
preset = {
|
|
"dimensions": {
|
|
"minecraft:overworld": {
|
|
"type": "minecraft:overworld",
|
|
"generator": {
|
|
"type": "minecraft:noise",
|
|
"biome_source": {
|
|
"type": "worldgen_editor:island_biome_source",
|
|
"preset": "cobblesurvival_island",
|
|
"delegate": {"type": "minecraft:multi_noise", "biomes": "@BIOMES@"},
|
|
},
|
|
"settings": "worldgen_editor:island_overworld",
|
|
},
|
|
},
|
|
"minecraft:the_end": {
|
|
"type": "minecraft:the_end",
|
|
"generator": {"type": "minecraft:noise", "biome_source": {"type": "minecraft:the_end"},
|
|
"settings": "minecraft:end"},
|
|
},
|
|
}
|
|
}
|
|
# One biome entry per line keeps the 7,600-entry file diffable.
|
|
body = ",\n".join(" " + json.dumps(e, separators=(",", ":")) for e in entries)
|
|
text = json.dumps(preset, indent=2).replace('"@BIOMES@"', "[\n" + body + "\n ]")
|
|
json.loads(text)
|
|
PRESET.write_text(text + "\n")
|
|
counts = {}
|
|
for e in entries:
|
|
counts[e["biome"]] = counts.get(e["biome"], 0) + 1
|
|
print(f"wrote {PRESET.relative_to(ROOT)}: {len(entries)} entries")
|
|
for biome, n in sorted(counts.items(), key=lambda kv: -kv[1]):
|
|
print(f" {n:5} {biome}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|