#!/usr/bin/env python3 """Writes BESTIARY.md from the game's own data: the bestiary texts in lang/en.lang, the unit stats in src/game.rs and the personalities in src/personality.rs. Pictures come from docs/img (refresh them with `cargo test --release export_doc_images -- --ignored`). usage: python3 tools/make_bestiary.py """ import pathlib import re root = pathlib.Path(__file__).resolve().parent.parent lang = {} for line in (root / "lang/en.lang").read_text().splitlines(): if " = " in line and not line.startswith("#"): k, v = line.split(" = ", 1) lang[k.strip()] = v.strip() game = (root / "src/game.rs").read_text() stats = {} for m in re.finditer(r'EK::(\w+) => s\("unit\.(\w+)", ([\d.]+), ([\d.]+), ([\d.]+), ([\d.]+), ([\d.]+), (\d+), (\d+)\)', game): kind, uid, hp, atk, rng, spd, itv, wage, bounty = m.groups() stats[kind] = dict(id=uid, hp=float(hp), atk=float(atk), range=float(rng), speed=float(spd), interval=float(itv), wage=int(wage), bounty=int(bounty)) pers = {} src = (root / "src/personality.rs").read_text() for m in re.finditer(r'EK::(\w+) => p\(Kind::(\w+), &\[([^\]]*)\], (?:Some\(\((\d+), "(\w+)"\)\)|None)\)', src): kind, likes, hates, lv, ab = m.groups() pers[kind] = dict(likes=likes, hates=[h.replace("EK::", "").strip() for h in hates.split(",") if h.strip()], lv=lv, ab=ab) room_key = {"Treasury": "treasury", "Lair": "lair", "Farm": "farm", "Training": "training", "Library": "library", "Torture": "torture", "Temple": "temple", "Aerie": "aerie", "Forge": "forge", "Prison": "prison", "Graveyard": "graveyard", "GuardPost": "guardpost", "Arena": "arena", "Altar": "altar"} def para(text): return "\n\n".join(p.strip() for p in text.split(" | ")) def unit(kind, human=False): s = stats[kind] uid = s["id"] name = lang.get(f"unit.{uid}", uid) out = [f"### {name}", ""] out.append(f'{name}, level 1 ' f'{name}, level 5 ' f'{name}, level 10') out.append("") out.append("*Level 1, 5 and 10 - the gear grows with the rank.*") out.append("") dps = s["atk"] / s["interval"] reach = "close combat" if s["range"] <= 1.5 else f"{s['range']:.1f} tiles" row = f"| {s['hp']:.0f} | {s['atk']:.0f} every {s['interval']:.1f} s ({dps:.0f}/s) | {reach} | {s['speed']:.1f} tiles/s |" if human: out += ["| Health | Attack | Range | Speed | Bounty |", "|---|---|---|---|---|", row + f" {s['bounty']} gold |"] else: out += ["| Health | Attack | Range | Speed | Wage |", "|---|---|---|---|---|", row + f" {s['wage']} gold |"] out.append("") if not human: lure = lang.get(f"best.{uid}.lure") if lure: out.append(f"**Comes for:** {lure} ") p = pers.get(kind) if p: out.append(f"**Loves:** {lang.get('room.' + room_key[p['likes']], p['likes'])} ") if p["hates"]: out.append(f"**Cannot stand:** {', '.join(lang.get('unit.' + stats[h]['id'], h) for h in p['hates'] if h in stats)} ") if p["ab"]: out.append(f"**From level {p['lv']}:** {lang.get('ability.' + p['ab'] + '.name')} - {lang.get('ability.' + p['ab'] + '.desc')}") out.append("") out.append(para(lang.get(f"best.{uid}.text", ""))) out.append("") return out def building(key, icon, title=None): name = title or lang.get(f"room.{key}") or lang.get(f"tool.{key}.name") or lang.get(f"trap.{key}") or key text = lang.get(f"best.b.{key}.text", "") desc = lang.get(f"tool.{key}.desc", "") return [f'| | **{name}** | {desc} *{text}* |'] creatures = ["Worker", "Kobold", "Spider", "Mage", "Ogre", "Jailer", "Priestess", "Chainbreaker", "Cogling", "Revenant", "Bat", "Gargoyle", "Owl", "Wyrm"] humans = ["Knight", "Archer", "Priest", "Dwarf", "Fairy", "BattleMage", "Giant", "Paladin"] md = [] md += [ "# Crookfinger's Bestiary", "", '

The bestiary in the game

', "", "> *Ah, master, you want to know who lives in your dungeon? Sit down. Mind the bones. I have written everything down -", "> lovingly about our own, and with a peg on my nose about the others.*", "> *- Crookfinger, imp, advisor, keeper of the tea*", "", "Every creature gains experience by fighting, training, studying and sparring. With every level it grows tougher", "(+15% health, +12% attack per level), its badge changes (bronze chevrons, silver bars, golden stars) and its gear grows", "(shoulder pieces, helmet, cape, golden armour with a crown and a halo). Its attacks look bigger, too.", "", "- [Your creatures](#your-creatures)", "- [The humans](#the-humans)", "- [The neutrals](#the-neutrals)", "- [Rooms of the dungeon](#rooms-of-the-dungeon)", "- [Towers, traps and doors](#towers-traps-and-doors)", "- [Spells](#spells)", "", "## Your creatures", "", ] for k in creatures: md += unit(k) md += ["## The humans", "", "> *Hold your nose, master. Here come the heroes.*", ""] for k in humans: md += unit(k, human=True) md += ["## The neutrals", "", f"### {lang['best.cavedwarf.name']}", "", 'cave dwarf', "", para(lang["best.cavedwarf.text"]), ""] md += ["## Rooms of the dungeon", "", "| | Room | |", "|---|---|---|"] for key in ["treasury", "lair", "farm", "training", "library", "torture", "prison", "temple", "aerie", "forge", "graveyard", "guardpost", "arena", "altar"]: md += building(key, key) md += ["", "## Towers, traps and doors", "", "| | | |", "|---|---|---|"] for key, icon in [("arrow", "arrow"), ("cannon", "cannon"), ("magic", "magic"), ("rune", "rune")]: md += [f'| | **{lang.get("tool." + key + ".name", key)}** | {lang.get("tool." + key + ".desc", "")} *{lang.get("best.b." + key + ".text", "")}* |'] for key in ["spikes", "poison", "fire", "roller", "alarm", "mine", "frost", "door_wood", "door_iron", "door_magic"]: md += building(key, key, lang.get(f"trap.{key}")) md += ["", "## Spells", "", "| | Spell | Mana | |", "|---|---|---|---|"] costs = {"lightning": 50, "heal": 60, "summon": "80+"} spells = (root / "src/spells.rs").read_text() for m in re.finditer(r'Spell::(\w+) => ([\d.]+),', spells.split("pub fn cost")[1].split("}")[0]): costs[m.group(1).lower()] = int(float(m.group(2))) for key in ["lightning", "heal", "summon", "eye", "cavein", "haste", "stoneskin", "goldrain", "invisible", "plague", "quake"]: md += [f'| | **{lang.get("tool." + key + ".name", key)}** | {costs.get(key, "")} | {lang.get("tool." + key + ".desc", "")} |'] md += ["", "---", "", "*This page is written from the game's own data: `python3 tools/make_bestiary.py`.*", ""] (root / "BESTIARY.md").write_text("\n".join(md)) print("BESTIARY.md written")