from __future__ import annotations from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Union from src.utils.df import game_configs, get_str, get_int from src.classes.effect import load_effect_from_str, format_effects_to_text from src.classes.cultivation import Realm class ElixirType(Enum): """丹药类型""" Breakthrough = "Breakthrough" # 破境 Lifespan = "Lifespan" # 延寿 BurnBlood = "BurnBlood" # 燃血 Heal = "Heal" # 疗伤 Unknown = "Unknown" @dataclass class Elixir: """ 丹药类 字段与 static/game_configs/elixir.csv 对应 """ id: int name: str realm: Realm type: ElixirType desc: str price: int effects: Union[dict[str, object], list[dict[str, object]]] = field(default_factory=dict) effect_desc: str = "" def get_info(self, detailed: bool = False) -> str: """获取信息""" if detailed: return self.get_detailed_info() return f"{self.name}" def get_detailed_info(self) -> str: """获取详细信息""" effect_part = f" 效果:{self.effect_desc}" if self.effect_desc else "" return f"{self.name}({self.realm.value}·{self._get_type_name()},{self.desc}){effect_part}" def _get_type_name(self) -> str: type_names = { ElixirType.Breakthrough: "破境", ElixirType.Lifespan: "延寿", ElixirType.BurnBlood: "燃血", ElixirType.Heal: "疗伤", } return type_names.get(self.type, "未知") def get_colored_info(self) -> str: """获取带颜色标记的信息,供前端渲染使用""" # 使用对应境界的颜色 r, g, b = self.realm.color_rgb return f"{self.get_info()}" def get_structured_info(self) -> dict: return { "name": self.name, "desc": self.desc, "grade": self.realm.value, "type": self.type.value, "type_name": self._get_type_name(), "price": self.price, "color": self.realm.color_rgb, "effect_desc": self.effect_desc, } @dataclass class ConsumedElixir: """ 已服用的丹药记录 """ elixir: Elixir consume_time: int # 服用时的 MonthStamp _expire_time: Union[int, float] = field(init=False) def __post_init__(self): self._expire_time = self.consume_time + self._get_max_duration() def _get_max_duration(self) -> Union[int, float]: """获取丹药的最长持续时间""" effects = self.elixir.effects if isinstance(effects, dict): effects = [effects] max_d = 0 for eff in effects: # 如果没有 duration_month 字段,视为永久效果 if "duration_month" not in eff: return float('inf') max_d = max(max_d, int(eff.get("duration_month", 0))) return max_d def is_completely_expired(self, current_month: int) -> bool: """ 判断丹药是否彻底失效(所有效果都过期) """ return current_month >= self._expire_time def get_active_effects(self, current_month: int) -> List[dict[str, object]]: """ 获取当前时间点仍然有效的 effects 列表 """ active = [] effects = self.elixir.effects if isinstance(effects, dict): effects = [effects] for eff in effects: # 永久效果 if "duration_month" not in eff: active.append(eff) continue # 有时限效果 duration = int(eff.get("duration_month", 0)) if duration > 0: if current_month < self.consume_time + duration: active.append(eff) return active def _load_elixirs() -> tuple[Dict[int, Elixir], Dict[str, List[Elixir]]]: """ 加载丹药配置 :return: (id索引字典, name索引字典(值为list)) """ elixirs_by_id: Dict[int, Elixir] = {} elixirs_by_name: Dict[str, List[Elixir]] = {} if "elixir" not in game_configs: return elixirs_by_id, elixirs_by_name df = game_configs["elixir"] for row in df: elixir_id = get_int(row, "id") name = get_str(row, "name") desc = get_str(row, "desc") price = get_int(row, "price") # 解析境界 realm_str = get_str(row, "realm") # 尝试匹配 Realm 枚举 realm = Realm.Qi_Refinement # 默认 for r in Realm: if r.value == realm_str or r.name == realm_str: realm = r break # 解析类型 elixir_type = ElixirType(get_str(row, "type")) # 解析 effects effects = load_effect_from_str(get_str(row, "effects")) effect_desc = format_effects_to_text(effects) elixir = Elixir( id=elixir_id, name=name, realm=realm, type=elixir_type, desc=desc, price=price, effects=effects, effect_desc=effect_desc ) elixirs_by_id[elixir_id] = elixir if name not in elixirs_by_name: elixirs_by_name[name] = [] elixirs_by_name[name].append(elixir) return elixirs_by_id, elixirs_by_name # 导出全局变量 elixirs_by_id, elixirs_by_name = _load_elixirs() def get_elixirs_by_realm(realm: Realm) -> List[Elixir]: """获取指定境界的所有丹药""" return [e for e in elixirs_by_id.values() if e.realm == realm]