# race_catalog.py from __future__ import annotations from dataclasses import dataclass from pathlib import Path from typing import Dict, List, Any import yaml from app.models.enums import Race from app.models.primitives import Attributes, Resources from app.models.races import RaceSheet, RacialTrait from app.utils.hero_catalog import SkillDef, SpellDef class RaceDataRegistry: """ In-memory catalog of YAML-defined race sheets. """ def __init__(self) -> None: self._by_race: Dict[Race, RaceSheet] = {} # promoted global catalogs for cross-use at runtime (optional) self._skills: Dict[str, SkillDef] = {} self._spells: Dict[str, SpellDef] = {} def load_dir(self, directory: str | Path) -> None: directory = Path(directory) for path in sorted(directory.glob("*.y*ml")): with path.open("r", encoding="utf-8") as f: raw = yaml.safe_load(f) or {} key_raw = raw.get("key") if not key_raw: raise ValueError(f"{path.name}: missing required 'key'") race = Race(key_raw) # validates enum base_attributes = Attributes(**(raw.get("base_attributes") or {})) starting_resources = Resources(**(raw.get("starting_resources") or {})) sheet = RaceSheet( key=race, display_name=raw.get("display_name", race.value.title()), base_attributes=base_attributes, starting_resources=starting_resources, starting_skills=list(raw.get("starting_skills") or []), starting_spells=list(raw.get("starting_spells") or []), ) # Local skill/spell catalogs (optional) for sid, sdef in (raw.get("skills") or {}).items(): s = SkillDef.from_yaml(sid, sdef) sheet.skills[sid] = s self._skills[sid] = s for spid, spdef in (raw.get("spells") or {}).items(): sp = SpellDef.from_yaml(spid, spdef) sheet.spells[spid] = sp self._spells[spid] = sp # Traits traits_raw = raw.get("traits") or [] for t in traits_raw: sheet.traits.append(RacialTrait(id=t.get("id"), data=dict(t.get("data") or {}))) self._by_race[race] = sheet def for_race(self, race: Race) -> RaceSheet: if race not in self._by_race: raise KeyError(f"No race sheet loaded for {race.value}") return self._by_race[race] # Optional global lookups (if you want to fetch a skill from race-only files) def get_skill(self, skill_id: str) -> SkillDef: return self._skills[skill_id] def get_spell(self, spell_id: str) -> SpellDef: return self._spells[spell_id]