chore(lint): restore green gate

This commit is contained in:
2026-09-04 22:55:49 +02:00
parent bc5bf918b5
commit 7336109ab2
9 changed files with 733 additions and 540 deletions
+113 -39
View File
@@ -17,10 +17,10 @@ from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Literal, cast
from typing import TYPE_CHECKING, Literal, TypeGuard, get_args
from pf1e_simulator.dice import DiceExpr, parse_dice
from pf1e_simulator.effects import BonusType, StatModifier
from pf1e_simulator.effects import BonusType, StatModifier, StatTarget
if TYPE_CHECKING:
from pf1e_simulator.loaders.foundry import Json
@@ -57,7 +57,7 @@ class BuffEffect:
@dataclass(frozen=True)
class AreaEffect:
"""Area of effect spell (burst only for Phase 0).
- shape: only "burst" supported (circle around target point)
- radius_ft: radius in feet (e.g., 20 for fireball)
- effects: the effects applied to each creature in the area
@@ -71,7 +71,7 @@ class AreaEffect:
@dataclass(frozen=True)
class SummonEffect:
"""Summoning spell effect.
- creature: path to the creature JSON file (e.g., "data/monsters/celestial_wolf.json")
- duration_rounds: how long the summoned creature stays
"""
@@ -106,64 +106,138 @@ def spell_range_ft(spell: SpellSpec, caster_level: int) -> int:
return 400 + 40 * caster_level
def _parse_effect(data: dict[str, Json]) -> SpellEffect:
effect_type = str(data["type"])
def _as_dict(value: Json) -> dict[str, Json]:
if isinstance(value, dict):
return value
msg = f"expected object, got {value!r}"
raise ValueError(msg)
def _as_list(value: Json) -> list[Json]:
if isinstance(value, list):
return value
msg = f"expected list, got {value!r}"
raise ValueError(msg)
def _as_str(value: Json) -> str:
if isinstance(value, str):
return value
msg = f"expected string, got {value!r}"
raise ValueError(msg)
def _as_int(value: Json) -> int:
if isinstance(value, int):
return value
if isinstance(value, float):
return int(value)
if isinstance(value, str):
return int(value)
msg = f"expected integer, got {value!r}"
raise ValueError(msg)
def _as_optional_str(value: Json) -> str | None:
if value is None:
return None
return _as_str(value)
def _is_spell_range(value: str) -> TypeGuard[SpellRange]:
return value in ("personal", "touch", "close", "medium", "long")
def _is_save_type(value: str) -> TypeGuard[SaveType]:
return value in ("fort", "ref", "will", "none")
def _is_stat_target(value: str) -> TypeGuard[StatTarget]:
return value in get_args(StatTarget)
def _is_bonus_type(value: str) -> TypeGuard[BonusType]:
return value in get_args(BonusType)
def _parse_modifier(value: Json) -> StatModifier:
m = _as_dict(value)
target = _as_str(m["target"])
if not _is_stat_target(target):
msg = f"unknown stat target: {target!r}"
raise ValueError(msg)
bonus = _as_str(m.get("bonus_type", ""))
if not _is_bonus_type(bonus):
msg = f"unknown bonus type: {bonus!r}"
raise ValueError(msg)
return StatModifier(
target=target,
value=_as_int(m["value"]),
bonus_type=bonus,
condition=_as_optional_str(m.get("condition")),
weapon_filter=_as_optional_str(m.get("weapon_filter")),
)
def _parse_effect(data: Json) -> SpellEffect:
effect = _as_dict(data)
effect_type = _as_str(effect["type"])
if effect_type == "damage":
return DamageEffect(
formula=parse_dice(str(data["formula"])),
types=tuple(str(t) for t in data["types"]), # type: ignore[index]
half_on_save=bool(data.get("half_on_save", True)),
formula=parse_dice(_as_str(effect["formula"])),
types=tuple(_as_str(t) for t in _as_list(effect["types"])),
half_on_save=bool(effect.get("half_on_save", True)),
)
if effect_type == "condition":
return ConditionEffect(
condition_name=str(data["condition_name"]),
duration_rounds=int(data["duration_rounds"]), # type: ignore[arg-type]
save_negates=bool(data.get("save_negates", True)),
condition_name=_as_str(effect["condition_name"]),
duration_rounds=_as_int(effect["duration_rounds"]),
save_negates=bool(effect.get("save_negates", True)),
)
if effect_type == "heal":
return HealEffect(
formula=parse_dice(str(data["formula"])),
formula=parse_dice(_as_str(effect["formula"])),
)
if effect_type == "buff":
return BuffEffect(
modifiers=tuple(
StatModifier(
target=str(m["target"]), # type: ignore[index]
value=int(m["value"]), # type: ignore[arg-type]
bonus_type=cast("BonusType", str(m.get("bonus_type", ""))), # type: ignore[union-attr]
condition=m.get("condition"), # type: ignore[assignment]
weapon_filter=m.get("weapon_filter"), # type: ignore[assignment]
)
for m in cast("list[dict[str, Json]]", data["modifiers"])
),
duration_rounds=int(data.get("duration_rounds", 0)), # type: ignore[arg-type]
modifiers=tuple(_parse_modifier(m) for m in _as_list(effect["modifiers"])),
duration_rounds=_as_int(effect.get("duration_rounds", 0)),
)
if effect_type == "area":
return AreaEffect(
radius_ft=int(data.get("radius_ft", 20)), # type: ignore[arg-type]
effects=tuple(_parse_effect(e) for e in data["effects"]), # type: ignore[index]
save_for_half=bool(data.get("save_for_half", True)),
radius_ft=_as_int(effect.get("radius_ft", 20)),
effects=tuple(_parse_effect(e) for e in _as_list(effect["effects"])),
save_for_half=bool(effect.get("save_for_half", True)),
)
if effect_type == "summon":
return SummonEffect(
creature=str(data["creature"]), # type: ignore[index]
duration_rounds=int(data["duration_rounds"]), # type: ignore[arg-type]
creature=_as_str(effect["creature"]),
duration_rounds=_as_int(effect["duration_rounds"]),
)
raise ValueError(f"Unknown effect type: {effect_type}")
msg = f"Unknown effect type: {effect_type}"
raise ValueError(msg)
def load_spell(path: str | Path) -> SpellSpec:
data: dict[str, Json] = json.loads(Path(path).read_text())
effects = tuple(_parse_effect(e) for e in data.get("effects", [])) # type: ignore[arg-type]
data = _as_dict(json.loads(Path(path).read_text()))
effects = tuple(_parse_effect(e) for e in _as_list(data.get("effects", [])))
range_raw = _as_str(data.get("range", "medium"))
if not _is_spell_range(range_raw):
msg = f"unknown spell range: {range_raw!r}"
raise ValueError(msg)
save_raw = _as_str(data.get("save", "none"))
if not _is_save_type(save_raw):
msg = f"unknown save type: {save_raw!r}"
raise ValueError(msg)
return SpellSpec(
name=str(data["name"]),
level=int(data["level"]), # type: ignore[arg-type]
school=str(data["school"]),
range=str(data.get("range", "medium")), # type: ignore[assignment]
save=str(data.get("save", "none")), # type: ignore[assignment]
base_dc=int(data.get("base_dc", 10)), # type: ignore[arg-type]
name=_as_str(data["name"]),
level=_as_int(data["level"]),
school=_as_str(data["school"]),
range=range_raw,
save=save_raw,
base_dc=_as_int(data.get("base_dc", 10)),
effects=effects,
description=str(data.get("description", "")),
description=_as_str(data.get("description", "")),
)