- Login with registration code, sessions, rate limiting, per-user characters - Share characters with other users (read-only), admin CLI - Rules presets with a Presets tab, applied/pending changes with diff in a side sheet - Equipment as tiles with amounts, starting kit removed - Rename nav tab to "Character", add "New character" button - Stop tracking _to_delete/ Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
55 lines
2.9 KiB
Python
55 lines
2.9 KiB
Python
"""Computes modifiers, derived stats, skill totals and carried weight (mirror of frontend/src/calc.js)."""
|
|
|
|
|
|
def kit_items(ch: dict, rules: dict) -> list:
|
|
"""Returns [(category, item, qty)] for the carried items. Old starting-kit slots count as one item each."""
|
|
items = {e["id"]: e for e in rules.get("equipment", [])}
|
|
cats = {c["id"]: c["name"] for c in rules.get("equipment_categories", [])}
|
|
kit = ch.get("kit") or {}
|
|
qty: dict = {}
|
|
for iid in [kit.get("weapon"), kit.get("armor"), kit.get("tool"), *(kit.get("resources") or [])]:
|
|
if iid:
|
|
qty[iid] = qty.get(iid, 0) + 1
|
|
for iid, n in (kit.get("extras") or {}).items():
|
|
qty[iid] = qty.get(iid, 0) + int(n)
|
|
return [(cats.get(items[i]["cat"], ""), items[i], n) for i, n in qty.items() if i in items and n > 0]
|
|
|
|
|
|
def compute(ch: dict, rules: dict) -> dict:
|
|
cr = {**rules["creation"], **(ch.get("creation") or {})} # per-character rules preset overrides
|
|
prof_id = ch.get("profession")
|
|
bonus_attr = ch.get("profession_bonus_attr") if prof_id else None
|
|
|
|
attrs = {}
|
|
for a in rules["attributes"]:
|
|
base = int((ch.get("attributes") or {}).get(a["id"], cr["attr_start"]))
|
|
attrs[a["id"]] = base + (cr["profession_attr_bonus"] if a["id"] == bonus_attr else 0)
|
|
mods = {k: v // 10 for k, v in attrs.items()}
|
|
|
|
derived = {d["id"]: sum(mods[x] for x in d["formula"]) for d in rules["derived"]}
|
|
|
|
skills = []
|
|
for s in rules["skills"]:
|
|
pts = int((ch.get("skill_points") or {}).get(s["id"], 0))
|
|
# Proficiency "pips": 1 from the profession plus free picks, at most 2 (2 = Expertise)
|
|
prof_pip = 1 if (ch.get("proficiencies") or {}).get(s["id"]) else 0
|
|
free_pips = min(int((ch.get("proficiency_picks") or {}).get(s["id"], 0)), cr.get("max_proficiency_pips", 2) - prof_pip)
|
|
pips = prof_pip + max(0, free_pips)
|
|
prof = [0, cr["proficiency_bonus"], cr["expertise_bonus"]][pips]
|
|
unlocked = s["id"] in (ch.get("unlocked") or [])
|
|
usable = s["type"] == "basic" or unlocked or prof > 0 # advanced skills must be unlocked
|
|
best = max(attrs[a] for a in s["attrs"]) # with several attributes the higher one counts
|
|
skills.append({**s, "points": pts, "prof": prof, "pips": pips, "usable": usable, "unlocked": unlocked,
|
|
"total": best + pts + prof})
|
|
|
|
aug_by_id = {a["id"]: a for a in rules["augments"]}
|
|
chosen = [aug_by_id[i] for i in ch.get("augments", []) if i in aug_by_id]
|
|
used = sum(a["cost"] for a in chosen)
|
|
|
|
weight = round(sum(item["kg"] * qty for _, item, qty in kit_items(ch, rules)), 1)
|
|
|
|
return {"attrs": attrs, "mods": mods, "derived": derived, "skills": skills,
|
|
"capacity_used": used, "capacity_max": derived.get("augmentation", 0),
|
|
"augments": chosen, "weight": weight,
|
|
"carry_max": max(0, mods["strength"]) * cr.get("carry_per_strength_mod", 10)}
|