Files
cobblemon-survival-pack/tools/zone_ores.py
T
claudeandClaude Opus 5.5 5d275e4e14 Phase 4: regenerating ore veins, each tier in one zone
Regenerating Ore Veins 1.4.0 (CurseForge only, MIT). tools/zone_ores.py
writes its config from world/zones.json: surface zones as boxes from the
zone grid, cave tiers by cave biome, 21 tiers including Cobblemon's
evolution stones (their natural ores are off). The world report now
allows ore only in tracked veins inside their tier's zone. Self-tests:
a mined vein regrows after its chunk unloads and after a restart (new
after_restart pass in selftest.py and quick_check.py). make_world places
showcase veins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:30:34 +01:00

172 lines
7.8 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Write Regenerating Ore Veins' config from the zone plan, so every ore tier only has veins in its zone.
world/zones.json → "ores" lists the tiers. This tool writes pack/config/regenerating_ore_veins/:
* areas.json: each surface zone that has a tier, as boxes from the zone grid (build/zone-layout.json,
24-block cells; rows of cells merged into rectangles), named <zone>_<n>, reaching from bedrock to
the build limit. The mod's boxes are centre plus half-size, so a box covers its cells except their
last row and column of blocks: no box reaches into a neighbouring zone.
* veins.json: one vein per tier, or two when the tier has "stone" and "deepslate" blocks (an upper vein
from y 0 up, "<tier>_deep" below). A surface tier's veins are limited to its zone's boxes and kept out
of the cave biomes, which are zones of their own; a cave tier's veins to its cave biome. The mod checks
the area for every block and the biome at a vein's centre. One vein only for the self-tests comes
last ("selftest", a single coal block); its area lies far outside the world border, so it never
generates, and the tests place it by command.
* global.json: regenerators can't be broken by players (allow_breaking false), so nobody can remove a
vein for good; explosions don't destroy veins.
* vein_locator.json: the Vein Locator from a compass and a copper ingot (mid-game) instead of netherite.
Re-run after changing the zone plan or the grid (tools/zone_layout.py) and commit the result.
Usage: tools/zone_ores.py
"""
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "pack/config/regenerating_ore_veins"
LAYOUT = ROOT / "build/zone-layout.json"
BOTTOM, TOP = -64, 320
SELFTEST_AREA = "selftest_nowhere"
def rectangles(mask, size):
"""Cover the True cells of a size×size row-major mask with rectangles (col0, row0, col1, row1)."""
open_runs, done = {}, []
for row in range(size + 1):
runs = set()
col = 0
while row < size and col < size:
if mask[row * size + col]:
start = col
while col < size and mask[row * size + col]:
col += 1
runs.add((start, col - 1))
col += 1
for run in list(open_runs):
if run not in runs:
done.append((run[0], open_runs.pop(run), run[1], row - 1))
for run in runs:
open_runs.setdefault(run, row)
return sorted(done, key=lambda r: (r[1], r[0]))
def zone_areas(layout, zone_id):
size, step, radius = layout["size"], layout["step"], layout["radius"]
index = layout["zones"].index(zone_id)
mask = [value == index for value in layout["grid"]]
areas = []
for n, (c0, r0, c1, r1) in enumerate(rectangles(mask, size)):
# Cell i covers -radius - step/2 + i*step ... +step-1 (as in zone/Zones.kt).
x0, z0 = c0 * step - radius - step // 2, r0 * step - radius - step // 2
w, h = (c1 - c0 + 1) * step, (r1 - r0 + 1) * step
areas.append({"dimension": "minecraft:overworld", "name": f"{zone_id}_{n}", "type": "box",
"x": x0 + w // 2 - 1, "y": (BOTTOM + TOP) // 2, "z": z0 + h // 2 - 1,
"dimx": w // 2 - 1, "dimy": (TOP - BOTTOM) // 2, "dimz": h // 2 - 1})
return areas
def vein(vein_id, blocks, spec, defaults, y_range, where):
seconds = spec["regen_minutes"] * 60
jitter = round(seconds * spec.get("jitter", defaults["jitter"]))
return {
"id": vein_id,
"blocks": [b for b, _ in blocks],
"weights": [w for _, w in blocks],
"dimension_whitelist": ["minecraft:overworld"],
**where,
"shape": "sphere",
"min_radius": spec["radius"][0],
"max_radius": spec["radius"][1],
"attempts": spec.get("attempts", defaults["attempts"]),
"min_y": y_range[0],
"max_y": y_range[1],
"chunk_minimum_generation_separation": spec["spacing"],
"fill_factor": spec.get("fill", defaults["fill"]),
"regeneration_interval_seconds": seconds,
"regeneration_interval_jitter": {"range_min": -jitter, "range_max": jitter},
}
def selftest_vein():
return {"id": "selftest", "blocks": ["minecraft:coal_ore"], "dimension_whitelist": ["minecraft:overworld"],
"area_whitelist": [SELFTEST_AREA], "shape": "sphere", "min_radius": 0, "max_radius": 0,
"attempts": 1, "min_y": 0, "max_y": 0, "chunk_minimum_generation_separation": 64,
"fill_factor": 1.0, "regeneration_interval_seconds": 3600,
"regeneration_interval_jitter": {"range_min": 0, "range_max": 0}}
def main():
if not LAYOUT.exists():
sys.exit(f"{LAYOUT.relative_to(ROOT)} is missing: run tools/zone_layout.py first.")
layout = json.loads(LAYOUT.read_text())
plan = json.loads((ROOT / "world/zones.json").read_text())
defaults = plan["ore_defaults"]
caves = {c["id"]: c["biome"] for c in plan["caves"]}
surface = {z["id"] for z in plan["zones"]}
areas, area_names, veins = [], {}, []
for tier, spec in plan["ores"].items():
zone = spec["zone"]
if zone in caves:
where = {"biome_whitelist": [caves[zone]]}
elif zone in surface:
if zone not in area_names:
boxes = zone_areas(layout, zone)
if not boxes:
sys.exit(f"zone {zone} (ore tier {tier}) has no cells in the grid")
areas += boxes
area_names[zone] = [a["name"] for a in boxes]
where = {"area_whitelist": area_names[zone], "biome_blacklist": sorted(caves.values())}
else:
sys.exit(f"ore tier {tier}: unknown zone {zone}")
low, high = spec["y"]
if "blocks" in spec:
veins.append(vein(tier, spec["blocks"], spec, defaults, (low, high), where))
continue
if spec.get("stone") and high >= 0:
veins.append(vein(tier, [(spec["stone"], 1)], spec, defaults, (max(low, 0), high), where))
if spec.get("deepslate") and low < 0:
deep_id = f"{tier}_deep" if spec.get("stone") and high >= 0 else tier
veins.append(vein(deep_id, [(spec["deepslate"], 1)], spec, defaults, (low, min(high, -1)), where))
areas.append({"dimension": "minecraft:overworld", "name": SELFTEST_AREA, "type": "box",
"x": 29_000_000, "y": 0, "z": 29_000_000, "dimx": 0, "dimy": 0, "dimz": 0})
veins.append(selftest_vein())
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "areas.json").write_text(json.dumps(areas, indent=1) + "\n")
(OUT / "veins.json").write_text(json.dumps(veins, indent=1) + "\n")
(OUT / "global.json").write_text(json.dumps({
"allow_breaking": False,
"break_hardness": 5.0,
"default_regeneration_seconds": 3600,
"default_jitter_interval": {"range_min": 0, "range_max": 0},
"regeneration_smoke_particles": True,
"destroyed_by_explosives": False,
"default_fill_factor": defaults["fill"],
}, indent=2) + "\n")
(OUT / "vein_locator.json").write_text(json.dumps({
"allow_crafting": True,
"allow_use": True,
"crafting_recipe": ["minecraft:compass", "minecraft:copper_ingot"],
"craft_any_order": True,
"chance_to_break": 0.75,
"use_durability": False,
"durability": 2,
"stack_size": 64,
}, indent=2) + "\n")
per_zone = {}
for tier, spec in plan["ores"].items():
per_zone.setdefault(spec["zone"], []).append(tier)
print(f"wrote {len(veins)} veins ({len(plan['ores'])} tiers) and {len(areas)} areas to {OUT.relative_to(ROOT)}")
for zone, tiers in per_zone.items():
boxes = len(area_names.get(zone, []))
print(f" {zone}: {', '.join(tiers)}" + (f" ({boxes} boxes)" if boxes else " (cave biome)"))
if __name__ == "__main__":
main()