Phase 5: Cobblebase jobs take back gated work, dig only zone ores
From Adrian's Mining test (2026-10-31): - A gated job assigned to a pastured Pokemon that doesn't pass its gate is taken back at once (BaseManager assignment cleared, Cobblebase's assignment sync sent, owner told what the Pokemon needs), so it no longer sticks or shows under Buffs -> Active. - Mining, Ore Finder and Evolution-item Finder only give the ores and evolution stones of the pasture's zone: new loot condition cobblesurvival:in_zone, the pack's versions of the 12 Cobblebase tables (tools/cobblebase_loot.py), Cobblebase's built-in copies removed at server start (ore/BaseLoot.kt). Mining adds quartz and the evolution stones, drops the type gems. - Mining's loot reaching the chest needs an Item Gatherer in the same pasture (Cobblebase's design); documented, not changed. - Self-tests gated_jobs_are_taken_back, base_jobs_dig_their_zones_ores; docs, balance table, licenses, Phase 5 checklist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
70cc2cad50
commit
11a583db01
27 files changed
+1458
-28
No files matched your search
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/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/<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()
|
||||
Reference in new issue
Block a user