Ore veins: make_world.py loads the island again row by row until Regenerating Ore Veins has filled every chunk (/cobblesurvival veins unfilled), cave tiers sit at their biome's depths, small zones get denser veins, showcase veins are full size. The world report checks surface tiers against the zone grid, cave tiers within two biome cells of their cave, and fails on unfilled chunks; the template passes (37,689 chunks). Drops: tools/drop_audit.py checks every creature drop a recipe or brewing needs (docs/pokemon-drops.md). Blaze rods, ghast tears, breeze rods, shulker shells and nether stars now drop from Pokemon through species additions (self-test drop_additions_apply). The field guide counts vanilla mobs as absent, applies species additions and reads the veins as sources. Cobblebase tried (loads, self-tests pass) and left out until Adrian decides its scope. Phase 4 checklist with the open questions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
155 lines
7.5 KiB
Python
155 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the field guide: where every Pokémon lives on the island, how to get every mod item,
|
|
and what each item is for, from the pack's own data.
|
|
|
|
Reads vanilla data (build/server/libraries), NeoForge, every jar listed in pack/mods (copied to
|
|
build/fieldguide-cache/jars from build/stage/full, build/server or the Prism instance), then
|
|
datapacks/core on top, plus world/biomes.json and world/world.json for the island. The recipes the
|
|
pack's KubeJS scripts add, change or remove come from `/kubejs export` on the test server
|
|
(kubejs_export.py); when that export is older than the pack, this boots the test server once to
|
|
refresh it (about a minute) unless --no-server is given.
|
|
|
|
Writes build/field-guide/:
|
|
fieldguide.json the single source every renderer reads
|
|
index.html, pokemon.html, items.html, data.js, assets/, icons/ the HTML guide
|
|
ingame/ftbquests/ FTB Quests "Field Guide" chapters (not installed)
|
|
ingame/kubejs/ Pokédex "Found in" lang override (not installed)
|
|
|
|
Usage: tools/fieldguide/build.py [--out DIR] [--no-icons] [--no-server] [--from-json FILE] [--extra FILE ...]
|
|
--no-server never start Minecraft: use the last KubeJS recipe export even if the pack changed since
|
|
--from-json skip extraction and render an existing fieldguide.json (e.g. one a runtime exporter wrote)
|
|
--extra add sources from a JSON file (format in docs/field-guide.md), e.g. trades; the ore veins
|
|
(world/vein-sources.json, written by tools/zone_ores.py) are always added
|
|
"""
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.dont_write_bytecode = True # keep tools/fieldguide free of __pycache__
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
from fieldguide import datasource, export_ftbquests, export_lang, guide, kubejs_export, render_html # noqa: E402
|
|
from fieldguide.icons import Icons # noqa: E402
|
|
from fieldguide.model import Model # noqa: E402
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent.parent
|
|
VEIN_SOURCES = ROOT / "world/vein-sources.json" # the regenerating ore veins (tools/zone_ores.py), always read
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
ap.add_argument("--out", type=Path, default=ROOT / "build/field-guide")
|
|
ap.add_argument("--no-icons", action="store_true", help="skip rendering item icons")
|
|
ap.add_argument("--no-server", action="store_true", help="use the last KubeJS recipe export as it is")
|
|
ap.add_argument("--from-json", type=Path, help="render this fieldguide.json instead of extracting")
|
|
ap.add_argument("--extra", type=Path, action="append", default=[], help="extra sources JSON (repeatable)")
|
|
args = ap.parse_args()
|
|
out = args.out
|
|
started = time.time()
|
|
|
|
if args.from_json:
|
|
data = json.loads(args.from_json.read_text())
|
|
lang = {}
|
|
else:
|
|
print("reading the pack's data")
|
|
stack = datasource.build_stack(log=_log_once())
|
|
extras = [VEIN_SOURCES] + args.extra if VEIN_SOURCES.exists() else args.extra
|
|
model = Model(stack, log=lambda msg: print(" " + msg), extras=extras, kubejs=_kubejs(args.no_server))
|
|
icons = {}
|
|
if not args.no_icons:
|
|
wanted = set(model.mod_items) | _vanilla_mentions(model)
|
|
ic = Icons(stack, out)
|
|
icons = ic.write_all(wanted)
|
|
print(f" icons: {len(icons)} rendered, {len(ic.missing)} without a usable model")
|
|
data = guide.build(model, icons)
|
|
guide.write(data, out / "fieldguide.json")
|
|
lang = model.lang
|
|
render_html.write(data, out)
|
|
chapters = export_ftbquests.write(data, out / "ingame")
|
|
if lang:
|
|
n, missing = export_lang.write(data, lang, out / "ingame")
|
|
print(f" Pokédex override: {n} keys" + (f", {len(missing)} keys not in the jar's lang" if missing else ""))
|
|
(out / "ingame/README.md").write_text(INGAME_README)
|
|
_summary(data, chapters, out, time.time() - started)
|
|
|
|
|
|
def _kubejs(no_server):
|
|
"""The pack's KubeJS recipe changes; refreshed from the test server when the pack changed since."""
|
|
data = kubejs_export.load()
|
|
if data and not data["stale"]:
|
|
print(f" KubeJS recipes: export from {data['exported']}")
|
|
return data
|
|
why = "no KubeJS recipe export yet" if not data else "pack/kubejs, pack/mods or datapacks/core changed since the last KubeJS recipe export"
|
|
if no_server:
|
|
print(f" warning: {why}; --no-server, so the guide " +
|
|
("uses the old export" if data else "shows no pack recipes"))
|
|
return data
|
|
print(f" {why}: booting the test server for /kubejs export")
|
|
try:
|
|
return kubejs_export.export(log=lambda msg: print(" " + msg))
|
|
except (SystemExit, Exception) as e: # e.g. no EULA, a server already running, mods failing to load
|
|
print(f" warning: KubeJS recipe export failed ({type(e).__name__}: {e}); the guide " +
|
|
("uses the old export" if data else "shows no pack recipes"))
|
|
return data
|
|
|
|
|
|
def _vanilla_mentions(model):
|
|
"""Vanilla items that show up in recipes or sources of mod items (they get icons too)."""
|
|
out = set()
|
|
mods = set(model.mod_items)
|
|
for r in model.recipes.values():
|
|
if r["result"] in mods or any(i in mods for s in r["slots"] for i in s["items"]):
|
|
out.add(r["result"])
|
|
for s in r["slots"]:
|
|
out.update(s["items"][:8])
|
|
for d in model.species.values():
|
|
for e in (d.get("drops") or {}).get("entries", []):
|
|
out.add(e["item"] if ":" in e["item"] else "minecraft:" + e["item"])
|
|
return {i for i in out if i.startswith("minecraft:")}
|
|
|
|
|
|
def _log_once():
|
|
seen = []
|
|
|
|
def log(msg):
|
|
if msg.startswith("warning:") and "not found in" in msg:
|
|
seen.append(msg.split()[1])
|
|
if len(seen) == 1:
|
|
print(" some pack/mods jars are not downloaded yet (run tools/build.py); continuing without them")
|
|
return
|
|
print(" " + msg)
|
|
return log
|
|
|
|
|
|
def _summary(data, chapters, out, secs):
|
|
sp = data["species"]
|
|
items = data["items"]
|
|
wild = sum(1 for s in sp.values() if s["wild"] == "available")
|
|
other = sum(1 for s in sp.values() if s["wild"] != "available" and s["status"] == "available")
|
|
cond = sum(1 for s in sp.values() if s["status"] == "conditional")
|
|
none = [s for s in sp.values() if s["status"] == "unavailable"]
|
|
nosrc = [i for i, v in items.items() if v["status"] == "unavailable" and not v["technical"]]
|
|
print(f"\nPokémon: {wild} wild on the island, {other} more by evolution/fossil/starter, {cond} only in the End, "
|
|
f"{len(none)} not obtainable ({sum(1 for s in none if not s['implemented'])} of them not implemented)")
|
|
for loc in data["locations"]:
|
|
n = len({m["species"] for i in loc["spawns"] for m in data["spawns"][i]["members"]})
|
|
print(f" {loc['name']:<24} {loc['kind']:<8} {n:>4} species")
|
|
print(f"Items: {len(items)} mod items, {len(nosrc)} with no source on the island")
|
|
print(f"FTB Quests chapters: " + ", ".join(f"{k} ({v} quests)" for k, v in chapters.items()))
|
|
print(f"Wrote {out} in {secs:.1f}s; open {out / 'index.html'}")
|
|
|
|
|
|
INGAME_README = """# In-game exports (generated, not installed)
|
|
|
|
- `ftbquests/`: FTB Quests "Field Guide" chapters. See `ftbquests/README.md`.
|
|
- `kubejs/`: Pokédex description override that appends "Found in: …". See `kubejs/README.md`.
|
|
|
|
Both are generated by `python3 tools/fieldguide/build.py` from `../fieldguide.json`.
|
|
Installing them is a separate decision; nothing here is copied into pack/.
|
|
"""
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|