Files
cultivation-world-simulator/src/classes/sect.py
2025-11-27 22:15:40 +08:00

306 lines
11 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.
from dataclasses import dataclass, field
from pathlib import Path
import json
from src.classes.alignment import Alignment
from src.utils.df import game_configs, get_str, get_float, get_int
from src.classes.effect import load_effect_from_str
from src.utils.config import CONFIG
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from src.classes.avatar import Avatar
from src.classes.technique import Technique
from src.classes.sect_ranks import SectRank
"""
宗门、宗门总部基础数据。
驻地名称与描述已迁移到 sect_region.csv供地图区域系统使用。
此处仅保留宗门本体信息与头像编辑所需的静态字段。
"""
# 宗门驻地(基础展示数据,具体地图位置在 sect_region.csv 中定义)
@dataclass
class SectHeadQuarter:
"""
宗门总部
"""
name: str
desc: str
image: Path
@dataclass
class Sect:
"""
宗门
"""
id: int
name: str
desc: str
member_act_style: str
alignment: Alignment
headquarter: SectHeadQuarter
# 本宗关联的功法名称(来自 technique.csv 的 sect 列)
technique_names: list[str]
# 随机选择宗门时使用的权重默认1
weight: float = 1.0
# 宗门倾向的兵器类型(字符串,如"剑"、"刀"等)
preferred_weapon: str = ""
# 影响角色或系统的效果
effects: dict[str, object] = field(default_factory=dict)
effect_desc: str = ""
# 宗门自定义职位名称可选SectRank -> 名称
rank_names: dict[str, str] = field(default_factory=dict)
# 运行时成员列表Avatar ID -> Avatar
members: dict[str, "Avatar"] = field(default_factory=dict, init=False)
# 功法对象列表Technique
techniques: list["Technique"] = field(default_factory=list, init=False)
def __post_init__(self):
self.members = {}
self.techniques = []
def add_member(self, avatar: "Avatar") -> None:
"""添加成员到宗门"""
if avatar.id not in self.members:
self.members[avatar.id] = avatar
def remove_member(self, avatar: "Avatar") -> None:
"""从宗门移除成员"""
if avatar.id in self.members:
del self.members[avatar.id]
def get_info(self) -> str:
hq = self.headquarter
return f"{self.name}(阵营:{self.alignment},驻地:{hq.name}"
def get_detailed_info(self) -> str:
# 详细描述:风格、阵营、驻地
hq = self.headquarter
effect_part = f" 效果:{self.effect_desc}" if self.effect_desc else ""
return f"{self.name}(阵营:{self.alignment},风格:{self.member_act_style},驻地:{hq.name}{effect_part}"
def get_rank_name(self, rank: "SectRank") -> str:
"""
获取宗门的职位名称(支持自定义)
Args:
rank: 宗门职位枚举
Returns:
职位名称字符串
"""
from src.classes.sect_ranks import SectRank, DEFAULT_RANK_NAMES
# 优先使用自定义名称,否则使用默认名称
return self.rank_names.get(rank.value, DEFAULT_RANK_NAMES.get(rank, "弟子"))
def get_structured_info(self) -> dict:
hq = self.headquarter
from src.classes.sect_ranks import RANK_ORDER
from src.server.main import resolve_avatar_pic_id
from src.classes.technique import techniques_by_name
# 成员列表:直接从 self.members 获取
members_list = []
for a in self.members.values():
rank_enum = getattr(a, "sect_rank", None)
sort_val = 999
if rank_enum and rank_enum in RANK_ORDER:
sort_val = RANK_ORDER[rank_enum]
members_list.append({
"id": str(a.id),
"name": a.name,
"pic_id": resolve_avatar_pic_id(a),
"gender": a.gender.value if hasattr(a.gender, "value") else "male",
"rank": a.get_sect_rank_name(),
"realm": a.cultivation_progress.realm.value if hasattr(a, 'cultivation_progress') else "未知",
"_sort_val": sort_val
})
# 按职位排序
members_list.sort(key=lambda x: x["_sort_val"])
# 清理排序字段
for m in members_list:
del m["_sort_val"]
# 填充 techniques
# 使用 technique_names 从全局字典中查找对应的 Technique 对象并序列化
techniques_data = []
for t_name in self.technique_names:
t_obj = techniques_by_name.get(t_name)
if t_obj:
techniques_data.append(t_obj.get_structured_info())
else:
# Fallback for missing techniques: create a minimal structure
techniques_data.append({
"name": t_name,
"desc": "(未知功法)",
"grade": "",
"color": (200, 200, 200), # Gray
"attribute": "",
"effect_desc": ""
})
return {
"id": self.id,
"name": self.name,
"desc": self.desc,
"alignment": str(self.alignment), # 直接返回中文
"style": self.member_act_style,
"hq_name": hq.name,
"hq_desc": hq.desc,
"effect_desc": self.effect_desc,
"techniques": techniques_data,
# 兼容旧字段,如果前端还要用的话(建议迁移后废弃)
"technique_names": self.technique_names,
"preferred_weapon": self.preferred_weapon,
"members": members_list
}
def _split_names(value: object) -> list[str]:
raw = "" if value is None or str(value) == "nan" else str(value)
sep = CONFIG.df.ids_separator
parts = [x.strip() for x in raw.split(sep) if x.strip()] if raw else []
return parts
def _load_sects() -> tuple[dict[int, Sect], dict[str, Sect]]:
"""从配表加载 sect 数据"""
sects_by_id: dict[int, Sect] = {}
sects_by_name: dict[str, Sect] = {}
df = game_configs["sect"]
# 读取宗门驻地映射(优先从 sect_region.csv 获取驻地地名/描述)
sect_region_df = game_configs.get("sect_region")
hq_by_sect_id: dict[int, tuple[str, str]] = {}
if sect_region_df is not None:
for sr in sect_region_df:
sid = get_int(sr, "sect_id", -1)
if sid == -1:
continue
hq_name = get_str(sr, "headquarter_name")
hq_desc = get_str(sr, "headquarter_desc")
hq_by_sect_id[sid] = (hq_name, hq_desc)
# 可能不存在 technique 配表或未添加 sect 列,做容错
tech_df = game_configs.get("technique")
assets_base = Path("assets/sects")
for row in df:
name = get_str(row, "name")
image_path = assets_base / f"{name}.png"
# 收集该宗门下配置的功法名称
technique_names: list[str] = []
# 检查 tech_df 是否存在以及是否有数据
if tech_df:
# 检查是否存在 sect 字段 (检查第一行或当前行)
technique_names = [
get_str(t, "name")
for t in tech_df
if get_str(t, "sect") == name and get_str(t, "name")
]
weight = get_float(row, "weight", 1.0)
# 读取 effects
effects = load_effect_from_str(get_str(row, "effects"))
from src.utils.effect_desc import format_effects_to_text
effect_desc = format_effects_to_text(effects)
# 读取倾向兵器类型
preferred_weapon = get_str(row, "preferred_weapon")
# 解析自定义职位
raw_ranks = get_str(row, "rank_names")
rank_names_map = {}
if raw_ranks:
# 格式:掌门;长老;内门;外门
parts = [x.strip() for x in raw_ranks.split(";") if x.strip()]
if len(parts) == 4:
from src.classes.sect_ranks import SectRank
rank_names_map = {
SectRank.Patriarch.value: parts[0],
SectRank.Elder.value: parts[1],
SectRank.InnerDisciple.value: parts[2],
SectRank.OuterDisciple.value: parts[3],
}
# 从 sect_region.csv 中优先取驻地名称/描述;否则兼容旧列或退回宗门名
sid = get_int(row, "id")
csv_hq = hq_by_sect_id.get(sid)
hq_name_from_csv = (csv_hq[0] if csv_hq else "").strip() if csv_hq else ""
hq_desc_from_csv = (csv_hq[1] if csv_hq else "").strip() if csv_hq else ""
hq_name = hq_name_from_csv or get_str(row, "headquarter_name") or name
hq_desc = hq_desc_from_csv or get_str(row, "headquarter_desc")
sect = Sect(
id=sid,
name=name,
desc=get_str(row, "desc"),
member_act_style=get_str(row, "member_act_style"),
alignment=Alignment.from_str(get_str(row, "alignment")),
headquarter=SectHeadQuarter(
name=hq_name,
desc=hq_desc,
image=image_path,
),
technique_names=technique_names,
weight=weight,
preferred_weapon=preferred_weapon,
effects=effects,
effect_desc=effect_desc,
rank_names=rank_names_map,
)
sects_by_id[sect.id] = sect
sects_by_name[sect.name] = sect
return sects_by_id, sects_by_name
# 导出:从配表加载 sect 数据
sects_by_id, sects_by_name = _load_sects()
def get_sect_info_with_rank(avatar: "Avatar", detailed: bool = False) -> str:
"""
获取包含职位的宗门信息字符串
Args:
avatar: 角色对象
detailed: 是否包含宗门详细信息(阵营、风格、驻地等)
Returns:
- 散修:返回"散修"
- detailed=False返回"明心剑宗长老"
- detailed=True返回"明心剑宗长老(阵营:正,风格:...,驻地:..."
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from src.classes.avatar import Avatar
# 散修直接返回
if avatar.sect is None:
return "散修"
# 获取职位+宗门名(如"明心剑宗长老"
sect_rank_str = avatar.get_sect_str()
# 如果不需要详细信息,直接返回职位字符串
if not detailed:
return sect_rank_str
# 需要详细信息:拼接宗门的详细描述
sect_detail = avatar.sect.get_detailed_info() # "明心剑宗(阵营:正,..."
# 提取括号及其内容
if "" in sect_detail:
detail_part = sect_detail[sect_detail.index(""):]
return f"{sect_rank_str}{detail_part}"
# 如果没有括号(理论上不应该出现),直接返回职位字符串
return sect_rank_str