#!/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()