From a5d2b192fe4c78dc6238c3e54eaca65ec7eb979d Mon Sep 17 00:00:00 2001 From: bridge Date: Mon, 1 Dec 2025 01:03:10 +0800 Subject: [PATCH] update relationship --- src/classes/avatar.py | 4 +- src/classes/relation.py | 109 ++++++++++-------- src/sim/new_avatar.py | 25 ++-- .../game/panels/info/AvatarDetail.vue | 2 +- 4 files changed, 83 insertions(+), 57 deletions(-) diff --git a/src/classes/avatar.py b/src/classes/avatar.py index 761f68d..e9514ce 100644 --- a/src/classes/avatar.py +++ b/src/classes/avatar.py @@ -36,7 +36,7 @@ from src.classes.magic_stone import MagicStone from src.classes.hp_and_mp import HP, HP_MAX_BY_REALM from src.utils.id_generator import get_avatar_id from src.utils.config import CONFIG -from src.classes.relation import Relation, get_reciprocal +from src.classes.relation import Relation, get_reciprocal, get_relation_label from src.run.log import get_logger from src.classes.alignment import Alignment from src.utils.params import filter_kwargs_for_callable @@ -384,7 +384,7 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin): relations_list.append({ "target_id": other.id, "name": other.name, - "relation": str(relation), + "relation": get_relation_label(relation, self, other), # 可以加更多 info,比如境界,用于列表中展示 "realm": other.cultivation_progress.realm.value, "sect": other.sect.name if other.sect else "散修" diff --git a/src/classes/relation.py b/src/classes/relation.py index 10eeb87..c6ce0c3 100644 --- a/src/classes/relation.py +++ b/src/classes/relation.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional from collections import defaultdict @@ -86,15 +86,12 @@ def get_reciprocal(relation: Relation) -> Relation: 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"): "父亲", @@ -104,72 +101,92 @@ GENDERED_DISPLAY: dict[tuple[Relation, str], str] = { (Relation.PARENT, "female"): "女儿", } +# 显示顺序配置 +DISPLAY_ORDER = [ + "师傅", "徒弟", "道侣", + "父亲", "母亲", + "儿子", "女儿", + "哥哥", "弟弟", "姐姐", "妹妹", + "兄弟", "姐妹", "兄弟姐妹", # 兜底 + "朋友", "仇人", + "亲属" +] -def _label_from_self_perspective(relation: Relation, other_gender: object | None = None) -> str: - # 优先使用性别化细化 - if other_gender is not None: - gender_value = getattr(other_gender, "value", None) or str(other_gender) - s = GENDERED_DISPLAY.get((relation, gender_value)) - if s: - return s - # 其余关系:以“我”为参照,取对偶再显示(MASTER -> 徒弟) - counterpart = get_reciprocal(relation) - return relation_display_names.get(counterpart, str(counterpart)) +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 = _label_from_self_perspective(rel, getattr(other, "gender", None)) + label = get_relation_label(rel, avatar, other) grouped[label].append(other.name) lines: list[str] = [] - - # 2. 定义显示顺序配置:(兜底显示名, [该类别下的具体标签列表]) - # 注意:"父母"和"子女"包含了性别化标签(父亲/母亲)和通用标签(父母) - display_config = [ - ("师傅", ["师傅"]), - ("徒弟", ["徒弟"]), - ("道侣", ["道侣"]), - ("父母", ["父亲", "母亲", "父母"]), - ("子女", ["儿子", "女儿", "子女"]), - ("兄弟姐妹", ["兄弟姐妹"]), - ("朋友", ["朋友"]), - ("仇人", ["仇人"]), - ] - processed_labels = set() - # 3. 遍历配置生成显示文本 - for fallback_label, target_labels in display_config: - found_any = False - for label in target_labels: - if label in grouped: - names = ",".join(grouped[label]) - lines.append(f"{label}:{names}") - found_any = True - processed_labels.add(label) + # 2. 按照预定义顺序输出 + for label in DISPLAY_ORDER: + if label in grouped: + names = ",".join(grouped[label]) + lines.append(f"{label}:{names}") + processed_labels.add(label) - if not found_any: - lines.append(f"{fallback_label}:无") - - # 4. 处理未在配置中列出的其他关系 + # 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) - return sep.join(lines) if lines else "无" + # 如果只有一行且是"无",直接返回 + if len(lines) == 1 and lines[0] == "无": + return "无" + return sep.join(lines) diff --git a/src/sim/new_avatar.py b/src/sim/new_avatar.py index 3a325a4..3141fb4 100644 --- a/src/sim/new_avatar.py +++ b/src/sim/new_avatar.py @@ -342,7 +342,7 @@ class PopulationPlanner: planned_surname[a] = surname planned_surname[b] = surname planned_gender[a] = Gender.MALE - planned_relations[(a, b)] = Relation.PARENT + planned_relations[(a, b)] = Relation.CHILD else: mother = a if random.random() < 0.5 else b child = b if mother == a else a @@ -354,7 +354,7 @@ class PopulationPlanner: if s != mom_surname: planned_surname[child] = s break - planned_relations[(mother, child)] = Relation.PARENT + planned_relations[(mother, child)] = Relation.CHILD leftover = unused_indices[:] @@ -392,7 +392,8 @@ class PopulationPlanner: if random.random() < MASTER_PAIR_PROB: master, apprentice = members[j], members[j + 1] if (master, apprentice) not in planned_relations and (apprentice, master) not in planned_relations: - planned_relations[(master, apprentice)] = Relation.MASTER + # MASTER -> APPRENTICE (Master.relations[Apprentice] = APPRENTICE) + planned_relations[(master, apprentice)] = Relation.APPRENTICE j += 2 # — 朋友/仇人 — @@ -551,9 +552,17 @@ class AvatarFactory: if attach_relations: if plan.parent_avatar is not None: - plan.parent_avatar.set_relation(avatar, Relation.PARENT) + # plan.parent_avatar 是长辈 + # 设置关系:长辈.set_relation(自己, CHILD) + # 底层逻辑:长辈.relations[自己] = CHILD (长辈认为自己是孩子) + # 自己.relations[长辈] = PARENT (自己认为长辈是父母) + plan.parent_avatar.set_relation(avatar, Relation.CHILD) if plan.master_avatar is not None: - plan.master_avatar.set_relation(avatar, Relation.MASTER) + # plan.master_avatar 是师傅 + # 设置关系:师傅.set_relation(自己, APPRENTICE) + # 底层逻辑:师傅.relations[自己] = APPRENTICE (师傅认为自己是徒弟) + # 自己.relations[师傅] = MASTER (自己认为师傅是师傅) + plan.master_avatar.set_relation(avatar, Relation.APPRENTICE) if avatar.technique is not None: mapped = attribute_to_root(avatar.technique.attribute) @@ -583,19 +592,19 @@ class AvatarFactory: levels: list[int] = [random.randint(LEVEL_MIN, LEVEL_MAX) for _ in range(n)] for (a, b), rel in list(planned_relations.items()): - if rel is Relation.PARENT: + if rel is Relation.CHILD: if ages[a] <= ages[b] + (PARENT_MIN_DIFF - 1): ages[a] = min(PARENT_AGE_CAP, ages[b] + random.randint(PARENT_MIN_DIFF, PARENT_MAX_DIFF)) for (a, b), rel in list(planned_relations.items()): - if rel is Relation.PARENT: + if rel is Relation.CHILD: if levels[a] <= levels[b]: levels[a] = min(LEVEL_MAX, levels[b] + 1) if levels[a] < levels[b] + PARENT_LEVEL_MIN_DIFF: levels[a] = min(LEVEL_MAX, levels[b] + PARENT_LEVEL_MIN_DIFF + random.randint(0, PARENT_LEVEL_EXTRA_MAX)) for (a, b), rel in list(planned_relations.items()): - if rel is Relation.MASTER: + if rel is Relation.APPRENTICE: if levels[a] < levels[b] + MASTER_LEVEL_MIN_DIFF: levels[a] = min(LEVEL_MAX, levels[b] + MASTER_LEVEL_MIN_DIFF + random.randint(0, MASTER_LEVEL_EXTRA_MAX)) diff --git a/web/src/components/game/panels/info/AvatarDetail.vue b/web/src/components/game/panels/info/AvatarDetail.vue index 894d6d1..f12f577 100644 --- a/web/src/components/game/panels/info/AvatarDetail.vue +++ b/web/src/components/game/panels/info/AvatarDetail.vue @@ -163,7 +163,7 @@ async function handleClearObjective() { v-for="rel in data.relations" :key="rel.target_id" :name="rel.name" - :meta="rel.relation" + :meta="`${data.name}的${rel.relation}`" :sub="`${rel.sect} · ${rel.realm}`" @click="jumpToAvatar(rel.target_id)" />