#!/usr/bin/env python3 """Write the pack's versions of Cobblebase's mining and finder loot tables: a pasture only yields its zone's ores. Adrian (2026-10-31): Cobblebase's Mining skill should only dig up the ores of the zone its pasture is in. Its tables, and those of the Ore Finder and the Evolution-item Finder, which hand out the same ores and evolution stones, ignore the island's zones. This copies them from the Cobblebase jar in build/server/mods and puts the loot condition `cobblesurvival:in_zone` on every ore and evolution stone, naming the zone of its tier in world/zones.json. The mining tables also lose the type gems (crafting shortcuts to gate materials such as the Heat Rock; ruins archaeology still has them) and gain the zone ores Cobblebase leaves out (quartz, the evolution stones). The companion mod takes Cobblebase's built-in copies of these tables out at server start (ore/BaseLoot.kt), so these files are what is rolled. Output: datapacks/core/data/cobblebase/loot_table/.json (derived from Cobblebase, MPL-2.0). Usage: tools/cobblebase_loot.py """ import json import sys import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parent.parent OUT = ROOT / "datapacks/core/data/cobblebase/loot_table" CONDITION = "cobblesurvival:in_zone" # What an ore tier of world/zones.json drops; evolution-stone tiers drop their stone. TIER_ITEMS = { "coal": ["minecraft:coal"], "copper": ["minecraft:raw_copper"], "iron": ["minecraft:raw_iron"], "gold": ["minecraft:raw_gold"], "redstone": ["minecraft:redstone"], "lapis": ["minecraft:lapis_lazuli"], "emerald": ["minecraft:emerald"], "amethyst": ["minecraft:amethyst_shard"], "diamond": ["minecraft:diamond"], "quartz": ["minecraft:quartz"], "netherite": ["minecraft:ancient_debris", "minecraft:netherite_scrap"], } # Table families: which items get the zone condition, which are dropped, and where missing zone items are added # (rarity suffix and weight, like Cobblebase's neighbouring entries). FAMILIES = { "mining_": {"zoned": ("ore", "stone"), "drop_gems": True, "add": {"ore": ("uncommon", 8), "stone": ("rare", 3)}}, "finder_ore_": {"zoned": ("ore",), "drop_gems": False, "add": {"ore": ("uncommon", 8)}}, "finder_evo_": {"zoned": ("stone",), "drop_gems": False, "add": {"stone": ("common", 10)}}, } def cobblebase_jar(): jars = sorted((ROOT / "build/server/mods").glob("cobblebase-*.jar")) if not jars: sys.exit("no Cobblebase jar in build/server/mods: run tools/build.py and tools/selftest.py (or the quick check) first") return jars[-1] def zone_items(): """item id -> (kind, zone) for every ore tier of world/zones.json.""" ores = json.loads((ROOT / "world/zones.json").read_text())["ores"] items = {} for tier, ore in ores.items(): if tier.endswith("_stone"): drops, kind = [f"cobblemon:{tier}"], "stone" elif tier in TIER_ITEMS: drops, kind = TIER_ITEMS[tier], "ore" else: sys.exit(f"ore tier {tier} has no drop in TIER_ITEMS") for item in drops: items[item] = (kind, ore["zone"]) return items def rewrite(table, family, items): spec = FAMILIES[family] seen = set() for pool in table["pools"]: kept = [] for entry in pool["entries"]: name = entry.get("name", "") if spec["drop_gems"] and name.endswith("_gem"): continue kind, zone = items.get(name, (None, None)) if kind in spec["zoned"]: entry = dict(entry, conditions=[{"condition": CONDITION, "zones": [zone]}]) seen.add(name) kept.append(entry) pool["entries"] = kept return seen def main(): items = zone_items() jar = zipfile.ZipFile(cobblebase_jar()) prefix = "data/cobblebase/loot_table/" names = sorted(n[len(prefix):-5] for n in jar.namelist() if n.startswith(prefix) and n.endswith(".json")) OUT.mkdir(parents=True, exist_ok=True) written = [] for family, spec in FAMILIES.items(): tables = {name: json.loads(jar.read(f"{prefix}{name}.json")) for name in names if name.startswith(family)} if not tables: sys.exit(f"Cobblebase has no {family}* loot tables any more") seen = set() for table in tables.values(): seen |= rewrite(table, family, items) for item, (kind, zone) in sorted(items.items()): if kind not in spec["zoned"] or item in seen: continue rarity, weight = spec["add"][kind] target = tables.get(f"{family}{rarity}") if target is None: sys.exit(f"no table {family}{rarity} to add {item} to") target["pools"][0]["entries"].append({ "type": "minecraft:item", "name": item, "weight": weight, "conditions": [{"condition": CONDITION, "zones": [zone]}], }) for name, table in tables.items(): (OUT / f"{name}.json").write_text(json.dumps(table, indent=2, ensure_ascii=False) + "\n") written.append(name) print(f"wrote {len(written)} tables to {OUT.relative_to(ROOT)}: {', '.join(written)}") by_zone = {} for item, (kind, zone) in items.items(): by_zone.setdefault(zone, []).append(item.split(":")[1]) for zone, zoned in sorted(by_zone.items()): print(f" {zone}: {', '.join(sorted(zoned))}") if __name__ == "__main__": main()