Files
cultivation-world-simulator/src/classes/relation.py
2025-12-29 21:46:26 +08:00

232 lines
8.4 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 __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING, List, Optional
from collections import defaultdict
class Relation(Enum):
# —— 血缘(先天) ——
PARENT = "parent" # 父/母 -> 子(有向)
CHILD = "child" # 子 -> 父/母(有向)
SIBLING = "sibling" # 兄弟姐妹(对称)
KIN = "kin" # 其他亲属(对称,泛化)
# —— 后天(社会/情感) ——
MASTER = "master" # 师傅 -> 徒弟(有向)
APPRENTICE = "apprentice" # 徒弟 -> 师傅(有向)
LOVERS = "lovers" # 道侣(对称)
FRIEND = "friend" # 朋友(对称)
ENEMY = "enemy" # 仇人/敌人(对称)
def __str__(self) -> str:
return relation_display_names.get(self, self.value)
@classmethod
def from_chinese(cls, name_cn: str) -> "Relation|None":
"""
依据中文显示名解析关系;无法解析返回 None。
"""
if not name_cn:
return None
s = str(name_cn).strip()
for rel, cn in relation_display_names.items():
if s == cn:
return rel
return None
relation_display_names = {
# 血缘(先天)
Relation.PARENT: "父母",
Relation.CHILD: "子女",
Relation.SIBLING: "兄弟姐妹",
Relation.KIN: "亲属",
# 后天(社会/情感)
Relation.MASTER: "师傅",
Relation.APPRENTICE: "徒弟",
Relation.LOVERS: "道侣",
Relation.FRIEND: "朋友",
Relation.ENEMY: "仇人",
}
# 关系是否属于“先天”(血缘),其余为“后天”
INNATE_RELATIONS: set[Relation] = {
Relation.PARENT, Relation.CHILD, Relation.SIBLING, Relation.KIN,
}
# —— 规则定义 ——
ADD_RELATION_RULES: dict[Relation, str] = {
Relation.LOVERS: "【道侣】需双方为异性。必须是双方非常相互认可且情投意合。",
Relation.FRIEND: "【朋友】友善互动(交谈、切磋点到为止、治疗)。无实质利益冲突。",
Relation.ENEMY: "【仇人】发生过实质性伤害(攻击致伤、偷窃、羞辱)。单次严重伤害或多次轻微摩擦。",
Relation.MASTER: "【师傅】需境界显著高于徒弟例如金丹vs练气",
Relation.APPRENTICE: "【徒弟】相对于师傅的身份,通常由师傅关系自动确立。",
}
CANCEL_RELATION_RULES: dict[Relation, str] = {
Relation.LOVERS: "【解除道侣】冲突、感情破裂、发生严重背叛。",
Relation.FRIEND: "【绝交】发生利益冲突、背叛或长期无互动导致疏远。",
Relation.ENEMY: "【化敌为友】一方主动示好并被接受,或共同经历生死患难,或仇恨被冲淡。",
Relation.MASTER: "【逐出师门/叛出师门】徒弟大逆不道或师傅无力教导。",
Relation.APPRENTICE: "【解除师徒】同上。",
}
def get_relation_rules_desc() -> str:
"""获取关系规则的描述文本,用于 Prompt"""
lines = ["【建立关系规则】"]
for rel, desc in ADD_RELATION_RULES.items():
lines.append(f"- {desc}")
lines.append("\n【取消关系规则】")
for rel, desc in CANCEL_RELATION_RULES.items():
lines.append(f"- {desc}")
return "\n".join(lines)
def is_innate(relation: Relation) -> bool:
return relation in INNATE_RELATIONS
# 有向关系的对偶映射;对称关系映射到自身
RECIPROCAL_RELATION: dict[Relation, Relation] = {
# 血缘
Relation.PARENT: Relation.CHILD, # 父母 -> 子女
Relation.CHILD: Relation.PARENT, # 子女 -> 父母
Relation.SIBLING: Relation.SIBLING, # 兄弟姐妹 -> 兄弟姐妹
Relation.KIN: Relation.KIN, # 亲属 -> 亲属
# 后天
Relation.MASTER: Relation.APPRENTICE, # 师傅 -> 徒弟
Relation.APPRENTICE: Relation.MASTER, # 徒弟 -> 师傅
Relation.LOVERS: Relation.LOVERS, # 道侣 -> 道侣
Relation.FRIEND: Relation.FRIEND, # 朋友 -> 朋友
Relation.ENEMY: Relation.ENEMY, # 仇人 -> 仇人
}
def get_reciprocal(relation: Relation) -> Relation:
"""
给定 A->B 的关系,返回应当写入 B->A 的关系。
对于对称关系(如 FRIEND/ENEMY/LOVERS/SIBLING/KIN返回其本身。
"""
return RECIPROCAL_RELATION.get(relation, relation)
if TYPE_CHECKING:
from src.classes.avatar import Avatar
# ——— 显示层:性别化称谓映射与标签工具 ———
GENDERED_DISPLAY: dict[tuple[Relation, str], str] = {
# 我 -> 对方CHILD我为子对方为父/母) → 显示对方为 父亲/母亲
(Relation.CHILD, "male"): "父亲",
(Relation.CHILD, "female"): "母亲",
# 我 -> 对方PARENT我为父/母,对方为子) → 显示对方为 儿子/女儿
(Relation.PARENT, "male"): "儿子",
(Relation.PARENT, "female"): "女儿",
}
# 显示顺序配置
DISPLAY_ORDER = [
"师傅", "徒弟", "道侣",
"父亲", "母亲",
"儿子", "女儿",
"哥哥", "弟弟", "姐姐", "妹妹",
"兄弟", "姐妹", "兄弟姐妹", # 兜底
"朋友", "仇人",
"亲属"
]
def get_relation_label(relation: Relation, self_avatar: "Avatar", other_avatar: "Avatar") -> str:
"""
获取 self_avatar 视角的 other_avatar 的称谓。
例如relation=CHILD (self是子), other是男 -> "父亲"
relation=SIBLING, other比self大, other是女 -> "姐姐"
"""
# 1. 处理兄弟姐妹 (涉及长幼比较)
if relation == Relation.SIBLING:
is_older = False
# 比较出生时间 (MonthStamp 越小越早出生,年龄越大)
if hasattr(other_avatar, "birth_month_stamp") and hasattr(self_avatar, "birth_month_stamp"):
if other_avatar.birth_month_stamp < self_avatar.birth_month_stamp:
is_older = True
elif other_avatar.birth_month_stamp == self_avatar.birth_month_stamp:
# 同月生,简单按 ID 排序保证一致性
is_older = str(other_avatar.id) < str(self_avatar.id)
gender_val = getattr(getattr(other_avatar, "gender", None), "value", "male")
if gender_val == "male":
return "哥哥" if is_older else "弟弟"
else:
return "姐姐" if is_older else "妹妹"
# 2. 查表处理通用性别化称谓
other_gender = getattr(other_avatar, "gender", None)
gender_val = getattr(other_gender, "value", "male")
label = GENDERED_DISPLAY.get((relation, gender_val))
if label:
return label
# 3. 回退到默认显示名
return relation_display_names.get(relation, relation.value)
def get_relations_strs(avatar: "Avatar", max_lines: int = 12) -> list[str]:
"""
以“我”的视角整理关系,输出若干行。
"""
relations = getattr(avatar, "relations", {}) or {}
# 1. 收集并根据标签分组所有关系
grouped: dict[str, list[str]] = defaultdict(list)
for other, rel in relations.items():
label = get_relation_label(rel, avatar, other)
display_name = other.name
# 死亡标记
if getattr(other, "is_dead", False):
# death_info 是一个可选的字典,其中 'reason' 已经被 handle_death 格式化好了
d_info = getattr(other, "death_info", None)
reason = d_info["reason"] if d_info and "reason" in d_info else "未知原因"
display_name = f"{other.name}(已故:{reason})"
grouped[label].append(display_name)
lines: list[str] = []
processed_labels = set()
# 2. 按照预定义顺序输出
for label in DISPLAY_ORDER:
if label in grouped:
names = "".join(grouped[label])
lines.append(f"{label}{names}")
processed_labels.add(label)
# 3. 处理未在配置中列出的其他关系(按字典序)
for label in sorted(grouped.keys()):
if label not in processed_labels:
names = "".join(grouped[label])
lines.append(f"{label}{names}")
processed_labels.add(label)
# 4. 若无任何关系
if not lines:
return [""]
return lines[:max_lines]
def relations_to_str(avatar: "Avatar", sep: str = "", max_lines: int = 6) -> str:
lines = get_relations_strs(avatar, max_lines=max_lines)
# 如果只有一行且是"无",直接返回
if len(lines) == 1 and lines[0] == "":
return ""
return sep.join(lines)