README as a small game website in Crookfinger's voice; INSTALL.md, BESTIARY.md, playable standalone file
- README: logo, badges (Rust, WebAssembly, Claude Code, MIT), intro addressed to the master, features with screenshots, the household, how to play, licences and credits - INSTALL.md: server, PWA, building, tests, savegame service and API, languages, how to refresh generated files - BESTIARY.md generated from the game's data (tools/make_bestiary.py) with exported sprites and icons (docs/img, test export_doc_images) - play/DungeonCraft.html: the whole game in one file to download and play offline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ce2c2919e
commit
b3583f5e7e
124 files changed
+1178
-104
No files matched your search
@@ -0,0 +1,130 @@
|
||||
#!/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'<img src="docs/img/units/{uid}_1.png" width="72" alt="{name}, level 1"> '
|
||||
f'<img src="docs/img/units/{uid}_5.png" width="72" alt="{name}, level 5"> '
|
||||
f'<img src="docs/img/units/{uid}_10.png" width="72" alt="{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'| <img src="docs/img/icons/{icon}.png" width="40"> | **{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",
|
||||
"",
|
||||
'<p align="center"><img src="docs/screenshots/bestiary.png" width="720" alt="The bestiary in the game"></p>',
|
||||
"",
|
||||
"> *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']}", "", '<img src="docs/img/units/dwarf_5.png" width="72" alt="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'| <img src="docs/img/icons/{icon}.png" width="40"> | **{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'| <img src="docs/img/icons/{key}.png" width="40"> | **{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")
|
||||
Reference in new issue
Block a user