"""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_pip > 0 # advanced skills must be unlocked (a profession proficiency does it for free) 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)}