Files
cultivation-world-simulator/src/classes/persona.py
2026-01-04 22:03:05 +08:00

151 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import random
from dataclasses import dataclass
from typing import List, Optional, TYPE_CHECKING
from src.utils.df import game_configs, get_str, get_list_str, get_int
from src.utils.config import CONFIG
from src.classes.effect import load_effect_from_str
from src.classes.rarity import Rarity, get_rarity_from_str
if TYPE_CHECKING:
# 仅用于类型检查,避免运行时循环导入
from src.classes.avatar import Avatar
@dataclass
class Persona:
"""
角色特质
包含个性、天赋等角色特征
"""
id: int
name: str
desc: str
exclusion_names: List[str]
rarity: Rarity
condition: str
effects: dict[str, object]
effect_desc: str = ""
@property
def weight(self) -> float:
"""根据稀有度获取采样权重"""
return self.rarity.weight
def get_info(self) -> str:
return self.name
def get_detailed_info(self) -> str:
desc_part = f"{self.desc}" if self.desc else ""
effect_part = f"\n效果:{self.effect_desc}" if self.effect_desc else ""
return f"{self.name}{desc_part}{effect_part}"
def get_colored_info(self) -> str:
"""获取带颜色标记的信息,供前端渲染使用"""
r, g, b = self.rarity.color_rgb
return f"<color:{r},{g},{b}>{self.name}</color>"
def get_structured_info(self) -> dict:
return {
"name": self.name,
"desc": self.desc,
"rarity": self.rarity.level.value,
"color": self.rarity.color_rgb,
"effect_desc": self.effect_desc,
}
def _load_personas() -> tuple[dict[int, Persona], dict[str, Persona]]:
"""从配表加载persona数据"""
personas_by_id: dict[int, Persona] = {}
personas_by_name: dict[str, Persona] = {}
persona_df = game_configs["persona"]
for row in persona_df:
# 解析exclusion_names字符串转换为字符串列表
exclusion_names = get_list_str(row, "exclusion_names")
# 解析稀有度(缺失或为 NaN 时默认为 N
rarity_str = get_str(row, "rarity", "N").upper()
rarity = get_rarity_from_str(rarity_str) if rarity_str and rarity_str != "NAN" else get_rarity_from_str("N")
# 条件
condition = get_str(row, "condition")
# 解析effects
effects = load_effect_from_str(get_str(row, "effects"))
from src.classes.effect import format_effects_to_text
effect_desc = format_effects_to_text(effects)
persona = Persona(
id=get_int(row, "id"),
name=get_str(row, "name"),
desc=get_str(row, "desc"),
exclusion_names=exclusion_names,
rarity=rarity,
condition=condition,
effects=effects,
effect_desc=effect_desc,
)
personas_by_id[persona.id] = persona
personas_by_name[persona.name] = persona
return personas_by_id, personas_by_name
# 从配表加载persona数据
personas_by_id, personas_by_name = _load_personas()
def _is_persona_allowed(persona_id: int, already_selected_ids: set[int], avatar: Optional["Avatar"]) -> bool:
"""
统一判断persona 是否允许被选择(条件 + 互斥)。
- 条件:当存在 avatar 且配置了 condition 时,通过安全 eval 判断。
- 互斥:与已选 persona 双向互斥(通过名字比较)。
"""
persona = personas_by_id[persona_id]
# 条件判定
if avatar is not None and persona.condition:
allowed = bool(eval(persona.condition, {"__builtins__": {}}, {"avatar": avatar}))
if not allowed:
return False
# 与已选互斥检查(双向,通过名字)
for sid in already_selected_ids:
other = personas_by_id[sid]
# 检查当前persona是否在对方的互斥列表中或对方是否在当前persona的互斥列表中
if (persona.name in other.exclusion_names) or (other.name in persona.exclusion_names):
return False
return True
def get_random_compatible_personas(num_personas: int = 2, avatar: Optional["Avatar"] = None) -> List[Persona]:
"""
随机选择指定数量的互相不冲突的persona
Args:
num_personas: 需要选择的persona数量默认为2
avatar: 可选,若提供则按 persona.condition 过滤
Returns:
List[Persona]: 互相不冲突的persona列表
Raises:
ValueError: 如果无法找到足够数量的兼容persona
"""
# 初始候选:若提供 avatar则先按条件过滤否则全量
initial_ids = set(personas_by_id.keys())
if avatar is not None:
initial_ids = {pid for pid in initial_ids if _is_persona_allowed(pid, set(), avatar)}
selected_personas: List[Persona] = []
selected_ids: set[int] = set()
for i in range(num_personas):
# 按当前已选进行二次过滤(互斥 + 条件)
available_ids = [pid for pid in initial_ids if pid not in selected_ids and _is_persona_allowed(pid, selected_ids, avatar)]
if not available_ids:
raise ValueError(f"只能找到{i}个兼容的persona无法满足需要的{num_personas}")
candidates: List[Persona] = [personas_by_id[pid] for pid in available_ids]
weights: List[float] = [max(0.0, c.weight) for c in candidates]
selected_persona = random.choices(candidates, weights=weights, k=1)[0]
selected_personas.append(selected_persona)
selected_ids.add(selected_persona.id)
return selected_personas