Feat/relation (#139)
* update relation * feat: add relation_type to avatar info structure and update related components - Added `relation_type` to the avatar structured info in `info_presenter.py`. - Updated `AvatarDetail.vue` to utilize the new `relation_type` for displaying avatar relationships. - Modified `RelationRow.vue` to accept `type` as a prop for enhanced relationship representation. - Updated `core.ts` to include `relation_type` in the `RelationInfo` interface. Closes #
This commit is contained in:
@@ -32,7 +32,7 @@ from src.classes.weapon import Weapon
|
||||
from src.classes.auxiliary import Auxiliary
|
||||
from src.classes.magic_stone import MagicStone
|
||||
from src.classes.hp import HP, HP_MAX_BY_REALM
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.sect import Sect
|
||||
from src.classes.appearance import Appearance, get_random_appearance
|
||||
from src.classes.spirit_animal import SpiritAnimal
|
||||
@@ -102,6 +102,8 @@ class Avatar(
|
||||
materials: dict[Material, int] = field(default_factory=dict)
|
||||
hp: HP = field(default_factory=lambda: HP(0, 0))
|
||||
relations: dict["Avatar", Relation] = field(default_factory=dict)
|
||||
# 缓存的二阶关系 (由 Simulator 定期计算)
|
||||
computed_relations: dict["Avatar", Relation] = field(default_factory=dict)
|
||||
alignment: Alignment | None = None
|
||||
sect: Sect | None = None
|
||||
sect_rank: "SectRank | None" = None
|
||||
@@ -392,17 +394,17 @@ class Avatar(
|
||||
|
||||
def set_relation(self, other: "Avatar", relation: Relation) -> None:
|
||||
"""设置与另一个角色的关系。"""
|
||||
from src.classes.relations import set_relation
|
||||
from src.classes.relation.relations import set_relation
|
||||
set_relation(self, other, relation)
|
||||
|
||||
def get_relation(self, other: "Avatar") -> Optional[Relation]:
|
||||
"""获取与另一个角色的关系。"""
|
||||
from src.classes.relations import get_relation
|
||||
from src.classes.relation.relations import get_relation
|
||||
return get_relation(self, other)
|
||||
|
||||
def clear_relation(self, other: "Avatar") -> None:
|
||||
"""清除与另一个角色的关系。"""
|
||||
from src.classes.relations import clear_relation
|
||||
from src.classes.relation.relations import clear_relation
|
||||
clear_relation(self, other)
|
||||
|
||||
# ========== 信息展示(委托) ==========
|
||||
|
||||
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
|
||||
from src.classes.avatar.core import Avatar
|
||||
|
||||
from src.classes.battle import get_base_strength
|
||||
from src.classes.relation import get_relation_label
|
||||
from src.classes.relation.relation import get_relation_label
|
||||
from src.classes.emotions import EMOTION_EMOJIS, EmotionType
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
@@ -35,7 +35,7 @@ def get_avatar_info(avatar: "Avatar", detailed: bool = False) -> dict:
|
||||
"""
|
||||
from src.i18n import t
|
||||
region = avatar.tile.region if avatar.tile is not None else None
|
||||
from src.classes.relation import get_relations_strs
|
||||
from src.classes.relation.relation import get_relations_strs
|
||||
relation_lines = get_relations_strs(avatar, max_lines=8)
|
||||
relations_info = t("relation_separator").join(relation_lines) if relation_lines else t("None")
|
||||
magic_stone_info = str(avatar.magic_stone)
|
||||
@@ -200,6 +200,7 @@ def get_avatar_structured_info(avatar: "Avatar") -> dict:
|
||||
"target_id": other.id,
|
||||
"name": other.name,
|
||||
"relation": get_relation_label(relation, avatar, other),
|
||||
"relation_type": relation.value,
|
||||
"realm": other.cultivation_progress.get_info(),
|
||||
"sect": other.sect.name if other.sect else t("Rogue Cultivator")
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ from src.classes.technique import (
|
||||
)
|
||||
from src.classes.weapon import Weapon, get_random_weapon_by_realm
|
||||
from src.classes.auxiliary import Auxiliary, get_random_auxiliary_by_realm
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.alignment import Alignment
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from src.i18n import t
|
||||
from .mutual_action import MutualAction
|
||||
from src.classes.relations import (
|
||||
from src.classes.relation.relations import (
|
||||
process_relation_changes,
|
||||
get_relation_change_context,
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ from src.i18n import t
|
||||
from .mutual_action import MutualAction
|
||||
from src.classes.action.cooldown import cooldown_action
|
||||
from src.classes.event import Event
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -9,8 +9,8 @@ from src.classes.action.action import DefineAction, ActualActionMixin, LLMAction
|
||||
from src.classes.event import Event
|
||||
from src.utils.llm import call_llm_with_task_name
|
||||
from src.utils.config import CONFIG
|
||||
from src.classes.relation import relation_display_names, Relation
|
||||
from src.classes.relations import get_possible_new_relations
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.relation.relations import get_possible_new_relations
|
||||
from src.classes.action_runtime import ActionResult, ActionStatus
|
||||
from src.classes.action.event_helper import EventHelper
|
||||
from src.classes.action.targeting_mixin import TargetingMixin
|
||||
|
||||
@@ -18,6 +18,16 @@ class Relation(Enum):
|
||||
LOVERS = "lovers" # 道侣(对称)
|
||||
FRIEND = "friend" # 朋友(对称)
|
||||
ENEMY = "enemy" # 仇人/敌人(对称)
|
||||
|
||||
# —— 二阶衍生关系 (Calculated) ——
|
||||
GRAND_PARENT = "grand_parent" # Parent's Parent
|
||||
GRAND_CHILD = "grand_child" # Child's Child
|
||||
# SIBLING 也可以是 calculated
|
||||
|
||||
# 师门系 (Distance: 2)
|
||||
MARTIAL_GRANDMASTER = "martial_grandmaster" # Master's Master
|
||||
MARTIAL_GRANDCHILD = "martial_grandchild" # Apprentice's Apprentice
|
||||
MARTIAL_SIBLING = "martial_sibling" # Shared Master
|
||||
|
||||
def __str__(self) -> str:
|
||||
from src.i18n import t
|
||||
@@ -31,8 +41,15 @@ class Relation(Enum):
|
||||
if not name_cn:
|
||||
return None
|
||||
s = str(name_cn).strip()
|
||||
for rel, cn in relation_display_names.items():
|
||||
if s == cn:
|
||||
|
||||
# 动态查找:遍历所有关系,翻译后比对
|
||||
from src.i18n import t
|
||||
for rel, msg_id in relation_msg_ids.items():
|
||||
# 这里假设当前环境语言是中文,或者我们需要强制用中文比对
|
||||
# 如果 name_cn 是 LLM 返回的中文,我们需要确保 t() 返回的是中文
|
||||
# 由于 gettext 通常基于全局上下文,这里依赖全局语言设置
|
||||
# 如果需要强制中文,可能需要临时切换 locale,但这里简化处理,假设 LLM 输出语言与当前 locale 一致
|
||||
if s == t(msg_id):
|
||||
return rel
|
||||
return None
|
||||
|
||||
@@ -47,27 +64,25 @@ relation_msg_ids = {
|
||||
Relation.LOVERS: "lovers",
|
||||
Relation.FRIEND: "friend",
|
||||
Relation.ENEMY: "enemy",
|
||||
}
|
||||
|
||||
# 兼容性:保留旧的dict用于from_chinese方法
|
||||
relation_display_names = {
|
||||
# 血缘(先天)
|
||||
Relation.PARENT: "父母",
|
||||
Relation.CHILD: "子女",
|
||||
Relation.SIBLING: "兄弟姐妹",
|
||||
Relation.KIN: "亲属",
|
||||
|
||||
# 后天(社会/情感)
|
||||
Relation.MASTER: "师傅",
|
||||
Relation.APPRENTICE: "徒弟",
|
||||
Relation.LOVERS: "道侣",
|
||||
Relation.FRIEND: "朋友",
|
||||
Relation.ENEMY: "仇人",
|
||||
|
||||
Relation.GRAND_PARENT: "grand_parent",
|
||||
Relation.GRAND_CHILD: "grand_child",
|
||||
Relation.MARTIAL_GRANDMASTER: "martial_grandmaster",
|
||||
Relation.MARTIAL_GRANDCHILD: "martial_grandchild",
|
||||
Relation.MARTIAL_SIBLING: "martial_sibling",
|
||||
}
|
||||
|
||||
# 关系是否属于“先天”(血缘),其余为“后天”
|
||||
INNATE_RELATIONS: set[Relation] = {
|
||||
Relation.PARENT, Relation.CHILD, Relation.SIBLING, Relation.KIN,
|
||||
Relation.GRAND_PARENT, Relation.GRAND_CHILD
|
||||
}
|
||||
|
||||
# 自动计算的关系集合
|
||||
CALCULATED_RELATIONS: set[Relation] = {
|
||||
Relation.GRAND_PARENT, Relation.GRAND_CHILD,
|
||||
Relation.MARTIAL_GRANDMASTER, Relation.MARTIAL_GRANDCHILD, Relation.MARTIAL_SIBLING,
|
||||
Relation.SIBLING
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +128,8 @@ RECIPROCAL_RELATION: dict[Relation, Relation] = {
|
||||
Relation.CHILD: Relation.PARENT, # 子女 -> 父母
|
||||
Relation.SIBLING: Relation.SIBLING, # 兄弟姐妹 -> 兄弟姐妹
|
||||
Relation.KIN: Relation.KIN, # 亲属 -> 亲属
|
||||
Relation.GRAND_PARENT: Relation.GRAND_CHILD,
|
||||
Relation.GRAND_CHILD: Relation.GRAND_PARENT,
|
||||
|
||||
# 后天
|
||||
Relation.MASTER: Relation.APPRENTICE, # 师傅 -> 徒弟
|
||||
@@ -120,6 +137,9 @@ RECIPROCAL_RELATION: dict[Relation, Relation] = {
|
||||
Relation.LOVERS: Relation.LOVERS, # 道侣 -> 道侣
|
||||
Relation.FRIEND: Relation.FRIEND, # 朋友 -> 朋友
|
||||
Relation.ENEMY: Relation.ENEMY, # 仇人 -> 仇人
|
||||
Relation.MARTIAL_GRANDMASTER: Relation.MARTIAL_GRANDCHILD,
|
||||
Relation.MARTIAL_GRANDCHILD: Relation.MARTIAL_GRANDMASTER,
|
||||
Relation.MARTIAL_SIBLING: Relation.MARTIAL_SIBLING,
|
||||
}
|
||||
|
||||
|
||||
@@ -144,15 +164,24 @@ GENDERED_DISPLAY: dict[tuple[Relation, str], str] = {
|
||||
# 我 -> 对方:PARENT(我为父/母,对方为子) → 显示对方为 儿子/女儿
|
||||
(Relation.PARENT, "male"): "relation_son",
|
||||
(Relation.PARENT, "female"): "relation_daughter",
|
||||
# 祖父母
|
||||
(Relation.GRAND_PARENT, "male"): "relation_grandfather",
|
||||
(Relation.GRAND_PARENT, "female"): "relation_grandmother",
|
||||
# 孙辈
|
||||
(Relation.GRAND_CHILD, "male"): "relation_grandson",
|
||||
(Relation.GRAND_CHILD, "female"): "relation_granddaughter",
|
||||
}
|
||||
|
||||
# 显示顺序配置
|
||||
DISPLAY_ORDER = [
|
||||
"master", "apprentice", "lovers",
|
||||
"martial_grandmaster", "master", "martial_sibling", "apprentice", "martial_grandchild",
|
||||
"lovers",
|
||||
"relation_grandfather", "relation_grandmother", "grand_parent", # 祖父母
|
||||
"relation_father", "relation_mother",
|
||||
"relation_son", "relation_daughter",
|
||||
"relation_older_brother", "relation_younger_brother", "relation_older_sister", "relation_younger_sister",
|
||||
"sibling", # 兜底
|
||||
"sibling",
|
||||
"relation_son", "relation_daughter",
|
||||
"relation_grandson", "relation_granddaughter", "grand_child", # 孙辈
|
||||
"friend", "enemy",
|
||||
"kin"
|
||||
]
|
||||
@@ -163,8 +192,8 @@ def get_relation_label(relation: Relation, self_avatar: "Avatar", other_avatar:
|
||||
"""
|
||||
from src.i18n import t
|
||||
|
||||
# 1. 处理兄弟姐妹 (涉及长幼比较)
|
||||
if relation == Relation.SIBLING:
|
||||
# 1. 处理兄弟姐妹/同门 (涉及长幼比较)
|
||||
if relation == Relation.SIBLING or relation == Relation.MARTIAL_SIBLING:
|
||||
is_older = False
|
||||
# 比较出生时间 (MonthStamp 越小越早出生,年龄越大)
|
||||
if hasattr(other_avatar, "birth_month_stamp") and hasattr(self_avatar, "birth_month_stamp"):
|
||||
@@ -176,10 +205,18 @@ def get_relation_label(relation: Relation, self_avatar: "Avatar", other_avatar:
|
||||
|
||||
gender_val = getattr(getattr(other_avatar, "gender", None), "value", "male")
|
||||
|
||||
if gender_val == "male":
|
||||
return t("relation_older_brother") if is_older else t("relation_younger_brother")
|
||||
else:
|
||||
return t("relation_older_sister") if is_older else t("relation_younger_sister")
|
||||
if relation == Relation.SIBLING:
|
||||
if gender_val == "male":
|
||||
return t("relation_older_brother") if is_older else t("relation_younger_brother")
|
||||
else:
|
||||
return t("relation_older_sister") if is_older else t("relation_younger_sister")
|
||||
else: # MARTIAL_SIBLING
|
||||
# 这里简单复用兄弟姐妹的 key,或者需要定义新的 key 如 martial_older_brother
|
||||
# 暂时使用通用的 sibling 称谓,或者如果有专用的 key
|
||||
if gender_val == "male":
|
||||
return t("relation_martial_older_brother") if is_older else t("relation_martial_younger_brother")
|
||||
else:
|
||||
return t("relation_martial_older_sister") if is_older else t("relation_martial_younger_sister")
|
||||
|
||||
# 2. 查表处理通用性别化称谓
|
||||
other_gender = getattr(other_avatar, "gender", None)
|
||||
@@ -200,7 +237,11 @@ def get_relations_strs(avatar: "Avatar", max_lines: int = 12) -> list[str]:
|
||||
以“我”的视角整理关系,输出若干行。
|
||||
"""
|
||||
from src.i18n import t
|
||||
relations = getattr(avatar, "relations", {}) or {}
|
||||
# 融合 relations 和 computed_relations
|
||||
# 优先显示一阶关系(如果同一个key在两个字典都存在,relations 覆盖 computed_relations)
|
||||
# 但一般不会有重叠,除了 SIBLING 可能被提升为一阶
|
||||
relations = getattr(avatar, "computed_relations", {}).copy()
|
||||
relations.update(getattr(avatar, "relations", {}) or {})
|
||||
|
||||
# 1. 收集并根据标签分组所有关系
|
||||
grouped: dict[str, list[str]] = defaultdict(list)
|
||||
@@ -2,12 +2,11 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, List, Tuple, Optional
|
||||
import asyncio
|
||||
|
||||
from src.classes.relation import (
|
||||
from src.classes.relation.relation import (
|
||||
Relation,
|
||||
get_relation_rules_desc,
|
||||
relation_display_names
|
||||
get_relation_rules_desc
|
||||
)
|
||||
from src.classes.relations import (
|
||||
from src.classes.relation.relations import (
|
||||
set_relation,
|
||||
cancel_relation,
|
||||
)
|
||||
@@ -40,7 +39,7 @@ class RelationResolver:
|
||||
current_rel = avatar_a.get_relation(avatar_b)
|
||||
rel_desc = "无"
|
||||
if current_rel:
|
||||
rel_name = relation_display_names.get(current_rel, current_rel.value)
|
||||
rel_name = str(current_rel)
|
||||
rel_desc = f"{rel_name}"
|
||||
|
||||
# 获取当前世界时间
|
||||
@@ -85,7 +84,7 @@ class RelationResolver:
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
display_name = relation_display_names.get(rel, rel_name)
|
||||
display_name = str(rel)
|
||||
event = None
|
||||
|
||||
if c_type == "ADD":
|
||||
@@ -5,7 +5,13 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from src.classes.relation import Relation, INNATE_RELATIONS, get_reciprocal, is_innate, relation_display_names
|
||||
from src.classes.relation.relation import (
|
||||
Relation,
|
||||
INNATE_RELATIONS,
|
||||
get_reciprocal,
|
||||
is_innate,
|
||||
CALCULATED_RELATIONS
|
||||
)
|
||||
from src.classes.event import Event
|
||||
from src.classes.action.event_helper import EventHelper
|
||||
|
||||
@@ -13,6 +19,71 @@ if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
|
||||
def update_second_degree_relations(avatar: "Avatar") -> None:
|
||||
"""
|
||||
计算并更新角色的二阶关系缓存。
|
||||
覆盖 SIBLING, GRAND_PARENT, MARTIAL_SIBLING 等。
|
||||
"""
|
||||
computed = {}
|
||||
|
||||
# 1. 预筛选一阶关键人 (中间节点)
|
||||
relations = getattr(avatar, "relations", {})
|
||||
|
||||
parents = [t for t, r in relations.items() if r == Relation.PARENT]
|
||||
children = [t for t, r in relations.items() if r == Relation.CHILD]
|
||||
masters = [t for t, r in relations.items() if r == Relation.MASTER]
|
||||
apprentices = [t for t, r in relations.items() if r == Relation.APPRENTICE]
|
||||
|
||||
# 2. 血缘推导
|
||||
# Sibling: 父母的子女 (排除自己)
|
||||
for p in parents:
|
||||
# 注意:这里需要访问 parent 的 relations
|
||||
# 如果 parent 是死者或者未完全加载,需要确保其 relations 可用
|
||||
p_relations = getattr(p, "relations", {})
|
||||
for sib, r in p_relations.items():
|
||||
if r == Relation.CHILD and sib.id != avatar.id:
|
||||
computed[sib] = Relation.SIBLING
|
||||
|
||||
# Grandparent: 父母的父母
|
||||
for p in parents:
|
||||
p_relations = getattr(p, "relations", {})
|
||||
for gp, r in p_relations.items():
|
||||
if r == Relation.PARENT:
|
||||
computed[gp] = Relation.GRAND_PARENT
|
||||
|
||||
# Grandchild: 子女的子女
|
||||
for c in children:
|
||||
c_relations = getattr(c, "relations", {})
|
||||
for gc, r in c_relations.items():
|
||||
if r == Relation.CHILD:
|
||||
computed[gc] = Relation.GRAND_CHILD
|
||||
|
||||
# 3. 师门推导
|
||||
# Martial Sibling: 师傅的徒弟 (排除自己)
|
||||
for m in masters:
|
||||
m_relations = getattr(m, "relations", {})
|
||||
for fellow, r in m_relations.items():
|
||||
if r == Relation.APPRENTICE and fellow.id != avatar.id:
|
||||
computed[fellow] = Relation.MARTIAL_SIBLING
|
||||
|
||||
# Martial Grandmaster: 师傅的师傅
|
||||
for m in masters:
|
||||
m_relations = getattr(m, "relations", {})
|
||||
for mgm, r in m_relations.items():
|
||||
if r == Relation.MASTER:
|
||||
computed[mgm] = Relation.MARTIAL_GRANDMASTER
|
||||
|
||||
# Martial Grandchild: 徒弟的徒弟
|
||||
for app in apprentices:
|
||||
app_relations = getattr(app, "relations", {})
|
||||
for mgc, r in app_relations.items():
|
||||
if r == Relation.APPRENTICE:
|
||||
computed[mgc] = Relation.MARTIAL_GRANDCHILD
|
||||
|
||||
# 4. 更新缓存
|
||||
avatar.computed_relations = computed
|
||||
|
||||
|
||||
def get_possible_new_relations(from_avatar: "Avatar", to_avatar: "Avatar") -> List[Relation]:
|
||||
"""
|
||||
评估"to_avatar 相对于 from_avatar"可能新增的后天关系集合(方向性明确)。
|
||||
@@ -138,8 +209,8 @@ def get_relation_change_context(avatar1: "Avatar", avatar2: "Avatar") -> tuple[l
|
||||
new_rels = get_possible_new_relations(avatar1, avatar2)
|
||||
cancel_rels = get_possible_cancel_relations(avatar1, avatar2)
|
||||
|
||||
new_strs = [relation_display_names[r] for r in new_rels]
|
||||
cancel_strs = [relation_display_names[r] for r in cancel_rels]
|
||||
new_strs = [str(r) for r in new_rels]
|
||||
cancel_strs = [str(r) for r in cancel_rels]
|
||||
|
||||
return new_strs, cancel_strs
|
||||
|
||||
@@ -162,7 +233,7 @@ def process_relation_changes(initiator: "Avatar", target: "Avatar", result_dict:
|
||||
set_relation(target, initiator, rel)
|
||||
set_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {initiator.name} 的关系变为:{relation_display_names.get(rel, str(rel))}",
|
||||
f"{target.name} 与 {initiator.name} 的关系变为:{str(rel)}",
|
||||
related_avatars=[initiator.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
@@ -176,7 +247,7 @@ def process_relation_changes(initiator: "Avatar", target: "Avatar", result_dict:
|
||||
if success:
|
||||
cancel_event = Event(
|
||||
month_stamp,
|
||||
f"{target.name} 与 {initiator.name} 取消了关系:{relation_display_names.get(rel, str(rel))}",
|
||||
f"{target.name} 与 {initiator.name} 取消了关系:{str(rel)}",
|
||||
related_avatars=[initiator.id, target.id],
|
||||
is_major=True
|
||||
)
|
||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from src.utils.config import CONFIG
|
||||
from src.utils.llm import call_llm_with_task_name
|
||||
from src.classes.relations import (
|
||||
from src.classes.relation.relations import (
|
||||
process_relation_changes,
|
||||
get_relation_change_context
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from dataclasses import dataclass
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
Binary file not shown.
@@ -3540,3 +3540,42 @@ msgstr "Cold and concise: Mainly short sentences, every word counts, like metal
|
||||
|
||||
msgid "Fine line drawing: No decoration, capturing subtle movements and expressions to convey spirit, real and delicate."
|
||||
msgstr "Fine line drawing: No decoration, capturing subtle movements and expressions to convey spirit, real and delicate."
|
||||
|
||||
msgid "grand_parent"
|
||||
msgstr "Grandparent"
|
||||
|
||||
msgid "grand_child"
|
||||
msgstr "Grandchild"
|
||||
|
||||
msgid "martial_grandmaster"
|
||||
msgstr "Martial Grandmaster"
|
||||
|
||||
msgid "martial_grandchild"
|
||||
msgstr "Martial Grandchild"
|
||||
|
||||
msgid "martial_sibling"
|
||||
msgstr "Martial Sibling"
|
||||
|
||||
msgid "relation_grandfather"
|
||||
msgstr "Grandfather"
|
||||
|
||||
msgid "relation_grandmother"
|
||||
msgstr "Grandmother"
|
||||
|
||||
msgid "relation_grandson"
|
||||
msgstr "Grandson"
|
||||
|
||||
msgid "relation_granddaughter"
|
||||
msgstr "Granddaughter"
|
||||
|
||||
msgid "relation_martial_older_brother"
|
||||
msgstr "Senior Martial Brother"
|
||||
|
||||
msgid "relation_martial_younger_brother"
|
||||
msgstr "Junior Martial Brother"
|
||||
|
||||
msgid "relation_martial_older_sister"
|
||||
msgstr "Senior Martial Sister"
|
||||
|
||||
msgid "relation_martial_younger_sister"
|
||||
msgstr "Junior Martial Sister"
|
||||
|
||||
Binary file not shown.
@@ -1924,6 +1924,45 @@ msgstr "姐姐"
|
||||
msgid "relation_younger_sister"
|
||||
msgstr "妹妹"
|
||||
|
||||
msgid "grand_parent"
|
||||
msgstr "祖父母"
|
||||
|
||||
msgid "grand_child"
|
||||
msgstr "孙辈"
|
||||
|
||||
msgid "martial_grandmaster"
|
||||
msgstr "师祖"
|
||||
|
||||
msgid "martial_grandchild"
|
||||
msgstr "徒孙"
|
||||
|
||||
msgid "martial_sibling"
|
||||
msgstr "同门"
|
||||
|
||||
msgid "relation_grandfather"
|
||||
msgstr "爷爷"
|
||||
|
||||
msgid "relation_grandmother"
|
||||
msgstr "奶奶"
|
||||
|
||||
msgid "relation_grandson"
|
||||
msgstr "孙子"
|
||||
|
||||
msgid "relation_granddaughter"
|
||||
msgstr "孙女"
|
||||
|
||||
msgid "relation_martial_older_brother"
|
||||
msgstr "师兄"
|
||||
|
||||
msgid "relation_martial_younger_brother"
|
||||
msgstr "师弟"
|
||||
|
||||
msgid "relation_martial_older_sister"
|
||||
msgstr "师姐"
|
||||
|
||||
msgid "relation_martial_younger_sister"
|
||||
msgstr "师妹"
|
||||
|
||||
# Root Keys
|
||||
msgid "root_gold"
|
||||
msgstr "金灵根"
|
||||
|
||||
Binary file not shown.
@@ -2403,3 +2403,42 @@ msgstr "冷峻簡練:短句為主,字字珠璣,如金石相擊,不做多
|
||||
|
||||
msgid "Fine line drawing: No decoration, capturing subtle movements and expressions to convey spirit, real and delicate."
|
||||
msgstr "細筆白描:不加藻飾,通過捕捉極細微的動作與神態來傳神,真實細膩。"
|
||||
|
||||
msgid "grand_parent"
|
||||
msgstr "祖父母"
|
||||
|
||||
msgid "grand_child"
|
||||
msgstr "孫輩"
|
||||
|
||||
msgid "martial_grandmaster"
|
||||
msgstr "師祖"
|
||||
|
||||
msgid "martial_grandchild"
|
||||
msgstr "徒孫"
|
||||
|
||||
msgid "martial_sibling"
|
||||
msgstr "同門"
|
||||
|
||||
msgid "relation_grandfather"
|
||||
msgstr "爺爺"
|
||||
|
||||
msgid "relation_grandmother"
|
||||
msgstr "奶奶"
|
||||
|
||||
msgid "relation_grandson"
|
||||
msgstr "孫子"
|
||||
|
||||
msgid "relation_granddaughter"
|
||||
msgstr "孫女"
|
||||
|
||||
msgid "relation_martial_older_brother"
|
||||
msgstr "師兄"
|
||||
|
||||
msgid "relation_martial_younger_brother"
|
||||
msgstr "師弟"
|
||||
|
||||
msgid "relation_martial_older_sister"
|
||||
msgstr "師姐"
|
||||
|
||||
msgid "relation_martial_younger_sister"
|
||||
msgstr "師妹"
|
||||
|
||||
@@ -37,7 +37,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from src.classes.calendar import MonthStamp
|
||||
from src.classes.event import Event
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from src.classes.calendar import MonthStamp
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.event import Event
|
||||
from src.classes.sect import sects_by_id, Sect
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.sim.simulator import Simulator
|
||||
from src.run.load_map import load_cultivation_world_map
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
@@ -14,7 +14,7 @@ from src.classes.age import Age
|
||||
from src.classes.name import get_random_name_for_sect, pick_surname_for_sect, get_random_name_with_surname
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.classes.sect import Sect, sects_by_id, sects_by_name
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.technique import get_technique_by_sect, attribute_to_root, Technique, techniques_by_id, techniques_by_name
|
||||
from src.classes.weapon import Weapon, weapons_by_id, weapons_by_name
|
||||
from src.classes.auxiliary import Auxiliary, auxiliaries_by_id, auxiliaries_by_name
|
||||
|
||||
@@ -366,7 +366,7 @@ class Simulator:
|
||||
"""
|
||||
关系演化阶段:检查并处理满足条件的角色关系变化
|
||||
"""
|
||||
from src.classes.relation_resolver import RelationResolver
|
||||
from src.classes.relation.relation_resolver import RelationResolver
|
||||
|
||||
pairs_to_resolve = []
|
||||
processed_pairs = set() # (id1, id2) id1 < id2
|
||||
@@ -477,9 +477,26 @@ class Simulator:
|
||||
# 14. 处理剩余阶段的交互计数
|
||||
self._phase_handle_interactions(events, processed_event_ids)
|
||||
|
||||
# 15. 归档与时间推进
|
||||
# 15. (每年1月) 更新计算关系 (二阶关系)
|
||||
self._phase_update_calculated_relations()
|
||||
|
||||
# 16. 归档与时间推进
|
||||
return self._finalize_step(events)
|
||||
|
||||
def _phase_update_calculated_relations(self):
|
||||
"""
|
||||
每年 1 月刷新全服角色的二阶关系缓存
|
||||
"""
|
||||
# 仅在 1 月执行
|
||||
if self.world.month_stamp.get_month() != Month.JANUARY:
|
||||
return
|
||||
|
||||
from src.classes.relation.relations import update_second_degree_relations
|
||||
living_avatars = self.world.avatar_manager.get_living_avatars()
|
||||
|
||||
for avatar in living_avatars:
|
||||
update_second_degree_relations(avatar)
|
||||
|
||||
def _finalize_step(self, events: list[Event]) -> list[Event]:
|
||||
"""
|
||||
本轮步进的最终归档:去重、入库、打日志、推进时间。
|
||||
|
||||
@@ -3,7 +3,7 @@ import random
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.world import World
|
||||
from src.classes.calendar import MonthStamp
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.sim.new_avatar import create_avatar_from_request
|
||||
|
||||
# ==========================================
|
||||
|
||||
@@ -131,7 +131,7 @@ def mock_llm_managers():
|
||||
with patch("src.sim.simulator.llm_ai") as mock_ai, \
|
||||
patch("src.sim.simulator.process_avatar_long_term_objective", new_callable=AsyncMock) as mock_lto, \
|
||||
patch("src.classes.nickname.process_avatar_nickname", new_callable=AsyncMock) as mock_nick, \
|
||||
patch("src.classes.relation_resolver.RelationResolver.run_batch", new_callable=AsyncMock) as mock_rr, \
|
||||
patch("src.classes.relation.relation_resolver.RelationResolver.run_batch", new_callable=AsyncMock) as mock_rr, \
|
||||
patch("src.classes.history.HistoryManager.apply_history_influence", new_callable=AsyncMock) as mock_hist, \
|
||||
patch("src.classes.story_teller.StoryTeller.tell_story", new_callable=AsyncMock) as mock_story, \
|
||||
patch("src.classes.story_teller.StoryTeller.tell_gathering_story", new_callable=AsyncMock) as mock_gathering_story, \
|
||||
|
||||
@@ -5,7 +5,7 @@ from src.classes.action_runtime import ActionResult, ActionStatus
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.age import Age
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.classes.calendar import create_month_stamp, Year, Month
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
from src.classes.death_reason import DeathReason, DeathType
|
||||
from src.classes.death import handle_death
|
||||
from src.classes.relation import Relation, get_relations_strs
|
||||
from src.classes.relation.relation import Relation, get_relations_strs
|
||||
from src.classes.event import Event
|
||||
|
||||
def test_death_reason_str():
|
||||
|
||||
@@ -18,7 +18,7 @@ from src.classes.mutual_action.talk import Talk
|
||||
from src.classes.mutual_action.spar import Spar
|
||||
from src.classes.mutual_action.impart import Impart
|
||||
from src.classes.action_runtime import ActionStatus
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
|
||||
|
||||
class TestTalk:
|
||||
|
||||
@@ -3,7 +3,7 @@ from src.classes.world import World
|
||||
from src.classes.calendar import MonthStamp
|
||||
from src.classes.age import Age
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.relation import Relation, get_relation_label
|
||||
from src.classes.relation.relation import Relation, get_relation_label
|
||||
from src.classes.cultivation import CultivationProgress, Realm
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.sim.new_avatar import create_random_mortal, MortalPlanner, AvatarFactory, PopulationPlanner
|
||||
|
||||
31
tests/test_relations_i18n.py
Normal file
31
tests/test_relations_i18n.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.language import language_manager
|
||||
from src.i18n import reload_translations, t
|
||||
|
||||
def test_relation_i18n_zh_tw():
|
||||
# Store original language
|
||||
original_lang = str(language_manager)
|
||||
print(f"Original lang: {original_lang}")
|
||||
|
||||
try:
|
||||
# Switch to Traditional Chinese
|
||||
language_manager.set_language("zh-TW")
|
||||
reload_translations()
|
||||
|
||||
print(f"Current lang: {str(language_manager)}")
|
||||
|
||||
# Debug: try translating directly
|
||||
gp_trans = t("grand_parent")
|
||||
print(f"Translation of 'grand_parent': {gp_trans}")
|
||||
print(f"Expected: 祖父母")
|
||||
|
||||
# Test new relations
|
||||
assert str(Relation.GRAND_PARENT) == "祖父母"
|
||||
assert str(Relation.GRAND_CHILD) == "孫輩"
|
||||
|
||||
finally:
|
||||
# Restore original language
|
||||
language_manager.set_language(original_lang)
|
||||
reload_translations()
|
||||
91
tests/test_relations_logic.py
Normal file
91
tests/test_relations_logic.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import pytest
|
||||
from src.classes.relation.relation import Relation
|
||||
from src.classes.relation.relations import update_second_degree_relations, set_relation
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.age import Age
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.calendar import MonthStamp
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
|
||||
def create_avatar(world, name, gender=Gender.MALE):
|
||||
return Avatar(
|
||||
world=world,
|
||||
name=name,
|
||||
id=get_avatar_id(),
|
||||
birth_month_stamp=MonthStamp(0),
|
||||
age=Age(20, Realm.Qi_Refinement),
|
||||
gender=gender,
|
||||
pos_x=0, pos_y=0
|
||||
)
|
||||
|
||||
def test_family_relations(base_world):
|
||||
grandpa = create_avatar(base_world, "Grandpa")
|
||||
father = create_avatar(base_world, "Father")
|
||||
son = create_avatar(base_world, "Son")
|
||||
daughter = create_avatar(base_world, "Daughter", Gender.FEMALE)
|
||||
|
||||
# Setup relations: Grandpa -> Father -> Son/Daughter
|
||||
# Father's parent is Grandpa
|
||||
set_relation(father, grandpa, Relation.PARENT)
|
||||
# Son's parent is Father
|
||||
set_relation(son, father, Relation.PARENT)
|
||||
# Daughter's parent is Father
|
||||
set_relation(daughter, father, Relation.PARENT)
|
||||
|
||||
# Update logic
|
||||
for p in [grandpa, father, son, daughter]:
|
||||
update_second_degree_relations(p)
|
||||
|
||||
# Assertions
|
||||
|
||||
# 1. Sibling check (Son <-> Daughter)
|
||||
# Son perspective
|
||||
assert son.computed_relations.get(daughter) == Relation.SIBLING
|
||||
# Daughter perspective
|
||||
assert daughter.computed_relations.get(son) == Relation.SIBLING
|
||||
|
||||
# 2. Grandparent check (Son/Daughter -> Grandpa)
|
||||
assert son.computed_relations.get(grandpa) == Relation.GRAND_PARENT
|
||||
assert daughter.computed_relations.get(grandpa) == Relation.GRAND_PARENT
|
||||
|
||||
# 3. Grandchild check (Grandpa -> Son/Daughter)
|
||||
assert grandpa.computed_relations.get(son) == Relation.GRAND_CHILD
|
||||
assert grandpa.computed_relations.get(daughter) == Relation.GRAND_CHILD
|
||||
|
||||
# 4. Father should not have Sibling/Grandparent (in this limited set)
|
||||
assert Relation.SIBLING not in father.computed_relations.values()
|
||||
assert Relation.GRAND_PARENT not in father.computed_relations.values()
|
||||
|
||||
def test_sect_relations(base_world):
|
||||
master = create_avatar(base_world, "Master")
|
||||
disciple_a = create_avatar(base_world, "DiscipleA")
|
||||
disciple_b = create_avatar(base_world, "DiscipleB")
|
||||
grand_master = create_avatar(base_world, "GrandMaster")
|
||||
|
||||
# Setup: GrandMaster -> Master -> A/B
|
||||
# Master is disciple of GrandMaster
|
||||
set_relation(master, grand_master, Relation.MASTER) # master.relations[GM] = MASTER, GM.relations[master] = APPRENTICE
|
||||
|
||||
# A is disciple of Master
|
||||
set_relation(disciple_a, master, Relation.MASTER)
|
||||
|
||||
# B is disciple of Master
|
||||
set_relation(disciple_b, master, Relation.MASTER)
|
||||
|
||||
# Update
|
||||
for p in [grand_master, master, disciple_a, disciple_b]:
|
||||
update_second_degree_relations(p)
|
||||
|
||||
# Assertions
|
||||
|
||||
# 1. Martial Sibling (A <-> B)
|
||||
assert disciple_a.computed_relations.get(disciple_b) == Relation.MARTIAL_SIBLING
|
||||
assert disciple_b.computed_relations.get(disciple_a) == Relation.MARTIAL_SIBLING
|
||||
|
||||
# 2. Martial Grandmaster (A/B -> GrandMaster)
|
||||
assert disciple_a.computed_relations.get(grand_master) == Relation.MARTIAL_GRANDMASTER
|
||||
assert disciple_b.computed_relations.get(grand_master) == Relation.MARTIAL_GRANDMASTER
|
||||
|
||||
# 3. Martial Grandchild (GrandMaster -> A/B)
|
||||
assert grand_master.computed_relations.get(disciple_a) == Relation.MARTIAL_GRANDCHILD
|
||||
assert grand_master.computed_relations.get(disciple_b) == Relation.MARTIAL_GRANDCHILD
|
||||
@@ -140,7 +140,7 @@ def test_save_load_with_relations(temp_save_dir):
|
||||
world.avatar_manager.avatars[av2.id] = av2
|
||||
|
||||
# Add relationship
|
||||
from src.classes.relation import Relation
|
||||
from src.classes.relation.relation import Relation
|
||||
|
||||
# Manually adding relation for test (usually done via helper methods)
|
||||
# relation value is integer
|
||||
|
||||
@@ -193,6 +193,7 @@ async function handleClearObjective() {
|
||||
:name="rel.name"
|
||||
:meta="t('game.info_panel.avatar.relation_meta', { owner: data.name, relation: rel.relation })"
|
||||
:sub="`${rel.sect} · ${rel.realm}`"
|
||||
:type="rel.relation_type"
|
||||
@click="jumpToAvatar(rel.target_id)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ defineProps<{
|
||||
name: string;
|
||||
meta?: string;
|
||||
sub?: string;
|
||||
type?: string;
|
||||
}>();
|
||||
|
||||
defineEmits(['click']);
|
||||
@@ -54,4 +55,3 @@ defineEmits(['click']);
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ export interface RelationInfo {
|
||||
target_id: string;
|
||||
name: string;
|
||||
relation: string;
|
||||
relation_type: string;
|
||||
realm: string;
|
||||
sect: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user