complete regen of hero classes, spells, races, etc
This commit is contained in:
93
app/game/models/professions.py
Normal file
93
app/game/models/professions.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, Optional
|
||||
|
||||
from app.game.models.enemies import EnemyProfile
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Profession:
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
primary_stat: str
|
||||
base_hp: int
|
||||
base_mp: int
|
||||
physical_attack_per_level: float
|
||||
physical_defense_per_level: float
|
||||
magic_attack_per_level: float
|
||||
magic_defense_per_level: float
|
||||
|
||||
tags: list[str] = field(default_factory=list) # e.g., {"playable"}, {"leader","elite"}
|
||||
enemy: Optional[EnemyProfile] = None # ⬅ optional enemy-only tuning
|
||||
|
||||
@property
|
||||
def is_playable(self) -> bool:
|
||||
return "playable" in self.tags
|
||||
|
||||
@staticmethod
|
||||
def from_yaml(data: Dict[str, object]) -> "Profession":
|
||||
# ---- validation of required profession fields ----
|
||||
required = [
|
||||
"id", "name", "description", "primary_stat",
|
||||
"base_hp", "base_mp",
|
||||
"physical_attack_per_level", "physical_defense_per_level",
|
||||
"magic_attack_per_level", "magic_defense_per_level"
|
||||
]
|
||||
missing = [k for k in required if k not in data]
|
||||
if missing:
|
||||
raise ValueError(f"Profession missing required fields: {missing}")
|
||||
|
||||
# ---- cast helpers (robust to str/int/float in YAML) ----
|
||||
def as_int(x, field_name):
|
||||
try: return int(x)
|
||||
except Exception: raise ValueError(f"{field_name} must be int, got {x!r}")
|
||||
def as_float(x, field_name):
|
||||
try: return float(x)
|
||||
except Exception: raise ValueError(f"{field_name} must be float, got {x!r}")
|
||||
|
||||
# ---- tags (optional) ----
|
||||
tags = list(data.get("tags", []) or [])
|
||||
|
||||
# ---- enemy block (optional) ----
|
||||
enemy_block = data.get("enemy")
|
||||
enemy: Optional[EnemyProfile] = None
|
||||
if enemy_block:
|
||||
eb = enemy_block or {}
|
||||
# typed extraction with defaults from EnemyProfile
|
||||
enemy = EnemyProfile(
|
||||
level_bias=as_int(eb.get("level_bias", 0), "enemy.level_bias"),
|
||||
level_variance=as_int(eb.get("level_variance", 1), "enemy.level_variance"),
|
||||
min_level=as_int(eb.get("min_level", 1), "enemy.min_level"),
|
||||
max_level=as_int(eb.get("max_level", 999), "enemy.max_level"),
|
||||
hp_mult=as_float(eb.get("hp_mult", 1.0), "enemy.hp_mult"),
|
||||
dmg_mult=as_float(eb.get("dmg_mult", 1.0), "enemy.dmg_mult"),
|
||||
armor_mult=as_float(eb.get("armor_mult", 1.0), "enemy.armor_mult"),
|
||||
speed_mult=as_float(eb.get("speed_mult", 1.0), "enemy.speed_mult"),
|
||||
stat_weights={str(k): float(v) for k, v in (eb.get("stat_weights", {}) or {}).items()},
|
||||
xp_base=as_int(eb.get("xp_base", 10), "enemy.xp_base"),
|
||||
xp_per_level=as_int(eb.get("xp_per_level", 5), "enemy.xp_per_level"),
|
||||
loot_tier=str(eb.get("loot_tier", "common")),
|
||||
)
|
||||
# sanity checks
|
||||
if enemy.min_level < 1:
|
||||
raise ValueError("enemy.min_level must be >= 1")
|
||||
if enemy.max_level < enemy.min_level:
|
||||
raise ValueError("enemy.max_level must be >= enemy.min_level")
|
||||
if any(v < 0 for v in (enemy.hp_mult, enemy.dmg_mult, enemy.armor_mult, enemy.speed_mult)):
|
||||
raise ValueError("enemy multipliers must be >= 0")
|
||||
|
||||
# ---- construct Profession ----
|
||||
return Profession(
|
||||
id=str(data["id"]),
|
||||
name=str(data["name"]),
|
||||
description=str(data["description"]),
|
||||
primary_stat=str(data["primary_stat"]),
|
||||
base_hp=as_int(data["base_hp"], "base_hp"),
|
||||
base_mp=as_int(data["base_mp"], "base_mp"),
|
||||
physical_attack_per_level=as_float(data["physical_attack_per_level"], "physical_attack_per_level"),
|
||||
physical_defense_per_level=as_float(data["physical_defense_per_level"], "physical_defense_per_level"),
|
||||
magic_attack_per_level=as_float(data["magic_attack_per_level"], "magic_attack_per_level"),
|
||||
magic_defense_per_level=as_float(data["magic_defense_per_level"], "magic_defense_per_level"),
|
||||
tags=tags,
|
||||
enemy=enemy,
|
||||
)
|
||||
Reference in New Issue
Block a user