feat(avatar): implement region ownership management in AvatarManager and Avatar classes

- Added `owned_regions` attribute to the Avatar class to track regions owned by avatars.
- Introduced `occupy_region` and `release_region` methods for managing region ownership and ensuring proper relationship handling.
- Updated AvatarManager to clear relationships when an avatar is released, ensuring no lingering references.
- Refactored region ownership logic in the Occupy action and Simulator to utilize the new methods for better clarity and maintainability.
- Enhanced game loading process to establish ownership relationships correctly during game state restoration.
This commit is contained in:
bridge
2026-02-02 21:34:02 +08:00
parent 7143b27a0a
commit 4f377551e8
8 changed files with 355 additions and 122 deletions

View File

@@ -11,6 +11,7 @@ from typing import Optional, List, TYPE_CHECKING
if TYPE_CHECKING:
from src.classes.sect_ranks import SectRank
from src.classes.region import CultivateRegion
from src.classes.calendar import MonthStamp
from src.classes.world import World
@@ -133,6 +134,39 @@ class Avatar(
# 关系交互计数器: key=target_id, value={"count": 0, "checked_times": 0}
relation_interaction_states: dict[str, dict[str, int]] = field(default_factory=lambda: defaultdict(lambda: {"count": 0, "checked_times": 0}))
# 拥有的洞府列表(不参与序列化,通过 load_game 重建)
owned_regions: List["CultivateRegion"] = field(default_factory=list, init=False)
def occupy_region(self, region: "CultivateRegion") -> None:
"""
占据一个洞府,处理双向绑定和旧主清理。
"""
# 如果已经是我的,无需操作
if region.host_avatar == self:
if region not in self.owned_regions:
self.owned_regions.append(region)
return
# 如果有旧主,先让旧主释放
if region.host_avatar is not None:
region.host_avatar.release_region(region)
# 建立新关系
region.host_avatar = self
if region not in self.owned_regions:
self.owned_regions.append(region)
def release_region(self, region: "CultivateRegion") -> None:
"""
放弃一个洞府的所有权。
"""
if region in self.owned_regions:
self.owned_regions.remove(region)
# 只有当 region 的主人确实是自己时才置空(防止误伤新主人)
if region.host_avatar == self:
region.host_avatar = None
def add_breakthrough_rate(self, rate: float, duration: int = 1) -> None:
"""
增加突破成功率(临时效果)
@@ -259,6 +293,11 @@ class Avatar(
self.thinking = ""
self.short_term_objective = ""
# 释放所有拥有的洞府
# 复制列表进行遍历,因为 release_region 会修改列表
for region in list(self.owned_regions):
self.release_region(region)
if self.sect:
self.sect.remove_member(self)

View File

@@ -114,10 +114,12 @@ class AvatarManager:
avatar.clear_relation(other)
# 2. 清理占据的洞府
if getattr(avatar, "world", None) and hasattr(avatar.world, "map"):
for region in avatar.world.map.regions.values():
if getattr(region, "host_avatar", None) == avatar:
if hasattr(avatar, "owned_regions") and avatar.owned_regions:
for region in list(avatar.owned_regions):
# 仅解除关系,不触发其他逻辑
if region.host_avatar == avatar:
region.host_avatar = None
avatar.owned_regions.clear()
# 3. 扫一遍所有角色(含死者),确保清除反向引用
for other in self._iter_all_avatars():

View File

@@ -106,7 +106,7 @@ class Occupy(MutualAction):
if feedback_name == "Yield":
# 对方让步:直接转移所有权
if region:
region.host_avatar = self.avatar
self.avatar.occupy_region(region)
# 共用一个事件
event_text = t("{initiator} forced {target} to yield {region}",
@@ -131,7 +131,7 @@ class Occupy(MutualAction):
# 进攻方胜利则洞府易主
attacker_won = winner == self.avatar
if attacker_won and region:
region.host_avatar = self.avatar
self.avatar.occupy_region(region)
self._last_result = (winner, loser, loser_dmg, winner_dmg, region_name, attacker_won)

View File

@@ -261,7 +261,9 @@ def load_game(save_path: Optional[Path] = None) -> Tuple["World", "Simulator", L
if rid in game_map.regions:
region = game_map.regions[rid]
if isinstance(region, CultivateRegion) and avatar_id in all_avatars:
region.host_avatar = all_avatars[avatar_id]
avatar = all_avatars[avatar_id]
# 使用 occupy_region 建立双向绑定
avatar.occupy_region(region)
# 重建宗门成员关系与功法列表
from src.classes.technique import techniques_by_name

View File

@@ -82,7 +82,7 @@ class Simulator:
if region.host_avatar is None:
if avatar.id not in avatars_with_home:
# 占据
region.host_avatar = avatar
avatar.occupy_region(region)
avatars_with_home.add(avatar.id)
# 记录事件
event = Event(