306 lines
11 KiB
Python
306 lines
11 KiB
Python
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
|