#!/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 _, 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, "_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. Each tier with "showcase" also gets "_showcase", a full radius-2 vein in the same place-only area, which tools/make_world.py places by hand near the zone's sample point. * 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. It also writes world/vein-sources.json, the veins as item sources for the field guide (tools/fieldguide/build.py reads it by default): what each tier's blocks drop and in which zone. 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" SOURCES = ROOT / "world/vein-sources.json" # What an ore block drops, where it isn't the block itself or "_ore" → "". DROPS = {"iron": "raw_iron", "copper": "raw_copper", "gold": "raw_gold", "lapis": "lapis_lazuli", "nether_quartz": "quartz", "amethyst_block": "amethyst_block", "budding_amethyst": "amethyst_shard"} def drop_of(block): namespace, name = block.split(":") base = name.removeprefix("deepslate_").removeprefix("dripstone_").removeprefix("terracotta_") base = base.removesuffix("_ore") if base.endswith("_ore") else base return f"{namespace}:{DROPS.get(base, base)}" 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 spec.get("showcase"): blocks = [(spec["stone"], 1)] if spec.get("stone") else [tuple(b) for b in spec["blocks"]] showcase = vein(f"{tier}_showcase", blocks, spec, defaults, (0, 0), {"area_whitelist": [SELFTEST_AREA]}) veins.append({**showcase, "min_radius": 2, "max_radius": 2, "fill_factor": 0.8}) 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") names = {z["id"]: z["name"] for z in plan["zones"] + plan["caves"]} sources = [] for tier, spec in plan["ores"].items(): blocks = [b for b, _ in spec.get("blocks", [])] + [spec[k] for k in ("stone", "deepslate") if spec.get(k)] for item in dict.fromkeys(drop_of(b) for b in blocks): sources.append({"item": item, "how": f"Regenerating {tier.replace('_', ' ')} vein in {names[spec['zone']]}", "kind": "ore vein", "needs": []}) SOURCES.write_text(json.dumps({ "_comment": "Generated by tools/zone_ores.py from world/zones.json; extra item sources for the field guide.", "sources": sources}, indent=1) + "\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()