update death
This commit is contained in:
@@ -9,6 +9,7 @@ from src.classes.battle import decide_battle, get_assassination_success_rate
|
||||
from src.classes.story_teller import StoryTeller
|
||||
from src.classes.normalize import normalize_avatar_name
|
||||
from src.classes.death import handle_death
|
||||
from src.classes.death_reason import DeathReason
|
||||
from src.classes.kill_and_grab import kill_and_grab
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -116,7 +117,7 @@ class Assassinate(InstantAction):
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True)
|
||||
|
||||
# 死亡清理
|
||||
handle_death(self.world, target)
|
||||
handle_death(self.world, target, DeathReason.BATTLE)
|
||||
|
||||
return [result_event, story_event]
|
||||
|
||||
@@ -153,7 +154,7 @@ class Assassinate(InstantAction):
|
||||
story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True)
|
||||
|
||||
if is_fatal:
|
||||
handle_death(self.world, loser)
|
||||
handle_death(self.world, loser, DeathReason.BATTLE)
|
||||
|
||||
return [result_event, story_event]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from src.classes.battle import decide_battle, get_effective_strength_pair
|
||||
from src.classes.story_teller import StoryTeller
|
||||
from src.classes.normalize import normalize_avatar_name
|
||||
from src.classes.death import handle_death
|
||||
from src.classes.death_reason import DeathReason
|
||||
from src.classes.kill_and_grab import kill_and_grab
|
||||
|
||||
class Attack(InstantAction):
|
||||
@@ -109,6 +110,6 @@ class Attack(InstantAction):
|
||||
|
||||
# 如果死亡,执行死亡清理(在故事生成后,保证关系数据可用)
|
||||
if is_fatal:
|
||||
handle_death(self.world, loser)
|
||||
handle_death(self.world, loser, DeathReason.BATTLE)
|
||||
|
||||
return [result_event, story_event]
|
||||
|
||||
@@ -84,7 +84,8 @@ class Age:
|
||||
years_over_lifespan = self.age - expected
|
||||
|
||||
# 基础概率:每超过1年增加0.01的概率
|
||||
death_probability = min(years_over_lifespan * 0.01, 0.1)
|
||||
prob_add = 0.01
|
||||
death_probability = min(years_over_lifespan * prob_add, 0.1)
|
||||
|
||||
return death_probability
|
||||
|
||||
|
||||
@@ -114,6 +114,12 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
nickname: Optional[Nickname] = None
|
||||
# 自定义头像ID:如果设置,优先使用此ID显示头像
|
||||
custom_pic_id: Optional[int] = None
|
||||
|
||||
# 死亡状态
|
||||
is_dead: bool = False
|
||||
# 死亡信息:{ "time": MonthStamp, "reason": str, "location": (x, y) }
|
||||
death_info: Optional[dict] = None
|
||||
|
||||
# 当月/当步新设动作标记:在 commit_next_plan 设为 True,首次 tick_action 后清为 False
|
||||
_new_action_set_this_step: bool = False
|
||||
# 动作冷却:记录动作类名 -> 上次完成月戳
|
||||
@@ -122,6 +128,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
|
||||
def join_sect(self, sect: Sect, rank: "SectRank") -> None:
|
||||
"""加入宗门"""
|
||||
if self.is_dead:
|
||||
return
|
||||
if self.sect:
|
||||
self.leave_sect()
|
||||
self.sect = sect
|
||||
@@ -137,6 +145,38 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
self.sect = None
|
||||
self.sect_rank = None
|
||||
|
||||
def set_dead(self, reason: str, time: MonthStamp) -> None:
|
||||
"""
|
||||
设置角色死亡状态。
|
||||
|
||||
Args:
|
||||
reason: 死亡原因
|
||||
time: 死亡时间
|
||||
"""
|
||||
if self.is_dead:
|
||||
return
|
||||
|
||||
self.is_dead = True
|
||||
self.death_info = {
|
||||
"time": int(time),
|
||||
"reason": reason,
|
||||
"location": (self.pos_x, self.pos_y)
|
||||
}
|
||||
|
||||
# 清空所有计划和当前动作
|
||||
self.planned_actions.clear()
|
||||
self.current_action = None
|
||||
self._pending_events.clear()
|
||||
self.thinking = ""
|
||||
self.short_term_objective = ""
|
||||
|
||||
# 退出宗门(保留职位记录还是清除?通常死人不再担任职位)
|
||||
# 但为了历史记录,也许可以保留 sect 字段,但从宗门成员列表中移除
|
||||
if self.sect:
|
||||
self.sect.remove_member(self)
|
||||
# 不清除 self.sect 和 self.sect_rank,作为生平记录保留
|
||||
|
||||
|
||||
def __post_init__(self):
|
||||
"""
|
||||
在Avatar创建后自动初始化tile和HP
|
||||
@@ -319,6 +359,8 @@ class Avatar(AvatarSaveMixin, AvatarLoadMixin):
|
||||
"long_term_objective": self.long_term_objective.content if self.long_term_objective else "",
|
||||
"nickname": self.nickname.value if self.nickname else None,
|
||||
"nickname_reason": self.nickname.reason if self.nickname else None,
|
||||
"is_dead": self.is_dead,
|
||||
"death_info": self.death_info,
|
||||
}
|
||||
|
||||
# 复杂对象结构化
|
||||
|
||||
@@ -26,6 +26,12 @@ class AvatarManager:
|
||||
same_region.append(other)
|
||||
return same_region
|
||||
|
||||
def get_living_avatars(self) -> List["Avatar"]:
|
||||
"""
|
||||
返回所有存活的角色列表。
|
||||
"""
|
||||
return [avatar for avatar in self.avatars.values() if not avatar.is_dead]
|
||||
|
||||
def get_observable_avatars(self, avatar: "Avatar") -> List["Avatar"]:
|
||||
"""
|
||||
返回处于 avatar 交互范围内的其他角色列表(不含自己)。
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from src.classes.death_reason import DeathReason
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.world import World
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
def handle_death(world: World, avatar: Avatar) -> None:
|
||||
def handle_death(world: World, avatar: Avatar, reason: Union[str, DeathReason] = DeathReason.UNKNOWN) -> None:
|
||||
"""
|
||||
处理角色死亡的统一入口。
|
||||
负责将角色从世界管理器中移除,并处理相关的清理工作(如关系解除已在 remove_avatar 中实现)。
|
||||
注意:本函数不负责生成死亡事件文本,调用者应在调用前生成相应的 Event。
|
||||
负责将角色标记为死亡,清理行动队列,但保留角色数据。
|
||||
|
||||
Args:
|
||||
world: 世界对象
|
||||
avatar: 死亡的角色
|
||||
reason: 死亡原因(DeathReason枚举或字符串)
|
||||
"""
|
||||
# 从管理器中移除角色(remove_avatar 内部会自动清理双向关系)
|
||||
world.avatar_manager.remove_avatar(avatar.id)
|
||||
# 如果传入的是枚举,转为字符串值
|
||||
reason_str = reason.value if isinstance(reason, DeathReason) else str(reason)
|
||||
|
||||
# 标记为死亡(软删除)
|
||||
avatar.set_dead(reason_str, world.month_stamp)
|
||||
|
||||
# 可以在这里触发其他逻辑,比如检查是否有继承人等
|
||||
|
||||
|
||||
|
||||
11
src/classes/death_reason.py
Normal file
11
src/classes/death_reason.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from enum import Enum
|
||||
|
||||
class DeathReason(Enum):
|
||||
OLD_AGE = "老死"
|
||||
BATTLE = "战死"
|
||||
SERIOUS_INJURY = "重伤"
|
||||
UNKNOWN = "未知"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
@@ -255,16 +255,19 @@ async def game_loop():
|
||||
# 执行一步
|
||||
events = await sim.step()
|
||||
|
||||
# 找出新诞生的角色 ID
|
||||
# 找出新诞生的角色 ID 和 刚死亡的角色 ID
|
||||
newly_born_ids = set()
|
||||
newly_dead_ids = set()
|
||||
for e in events:
|
||||
if "晋升为修士" in e.content and e.related_avatars:
|
||||
newly_born_ids.update(e.related_avatars)
|
||||
if ("身亡" in e.content or "老死" in e.content) and e.related_avatars:
|
||||
newly_dead_ids.update(e.related_avatars)
|
||||
|
||||
avatar_updates = []
|
||||
|
||||
# 为了避免重复发送大量数据,我们区分处理:
|
||||
# - 新角色:发送完整数据
|
||||
# - 新角色/刚死角色:发送完整数据(或关键状态更新)
|
||||
# - 旧角色:只发送位置 (x, y)(限制数量)
|
||||
|
||||
# 1. 发送新角色的完整信息
|
||||
@@ -276,15 +279,29 @@ async def game_loop():
|
||||
"name": a.name,
|
||||
"x": int(getattr(a, "pos_x", 0)),
|
||||
"y": int(getattr(a, "pos_y", 0)),
|
||||
"gender": a.gender.value, # 使用 value (male/female) 而不是 str (中文)
|
||||
"gender": a.gender.value,
|
||||
"pic_id": resolve_avatar_pic_id(a),
|
||||
"action": getattr(a, "current_action", {}).get("name", "发呆") if hasattr(a, "current_action") and a.current_action else "发呆"
|
||||
"action": getattr(a, "current_action", {}).get("name", "发呆") if hasattr(a, "current_action") and a.current_action else "发呆",
|
||||
"is_dead": False
|
||||
})
|
||||
|
||||
# 2. 常规位置更新(暂时只发前 50 个旧角色,减少数据量)
|
||||
# 2. 发送刚死角色的状态更新
|
||||
for aid in newly_dead_ids:
|
||||
# 注意:死人可能不在 get_living_avatars() 里,但还在 avatars 里
|
||||
a = world.avatar_manager.avatars.get(aid)
|
||||
if a:
|
||||
avatar_updates.append({
|
||||
"id": str(a.id),
|
||||
"name": a.name, # 名字也带上,防止前端没数据
|
||||
"is_dead": True,
|
||||
"action": "已故"
|
||||
})
|
||||
|
||||
# 3. 常规位置更新(暂时只发前 50 个旧角色,减少数据量)
|
||||
limit = 50
|
||||
count = 0
|
||||
for a in world.avatar_manager.avatars.values():
|
||||
# 只遍历活人更新位置
|
||||
for a in world.avatar_manager.get_living_avatars():
|
||||
# 如果是新角色,已经在上面处理过了,跳过
|
||||
if a.id in newly_born_ids:
|
||||
continue
|
||||
|
||||
@@ -148,6 +148,10 @@ class AvatarLoadMixin:
|
||||
# 恢复绰号
|
||||
from src.classes.nickname_data import Nickname
|
||||
avatar.nickname = Nickname.from_dict(data.get("nickname"))
|
||||
|
||||
# 恢复死亡状态
|
||||
avatar.is_dead = data.get("is_dead", False)
|
||||
avatar.death_info = data.get("death_info")
|
||||
|
||||
# 设置行动与AI
|
||||
avatar.thinking = data.get("thinking", "")
|
||||
|
||||
@@ -251,7 +251,10 @@ class MortalPlanner:
|
||||
plan.pos_y = random.randint(0, world.map.height - 1)
|
||||
|
||||
if existing_avatars is None:
|
||||
existing_avatars = list(world.avatar_manager.avatars.values())
|
||||
existing_avatars = world.avatar_manager.get_living_avatars()
|
||||
else:
|
||||
existing_avatars = [av for av in existing_avatars if not av.is_dead]
|
||||
|
||||
if existed_sects is None:
|
||||
try:
|
||||
from src.classes.sect import sects_by_id as _sects_by_id
|
||||
|
||||
@@ -90,6 +90,8 @@ class AvatarSaveMixin:
|
||||
"persona_ids": [p.id for p in self.personas] if self.personas else [],
|
||||
"appearance": self.appearance.level,
|
||||
"nickname": self.nickname.to_dict() if self.nickname else None,
|
||||
"is_dead": self.is_dead,
|
||||
"death_info": self.death_info,
|
||||
|
||||
# 行动与AI
|
||||
"current_action": current_action_dict,
|
||||
|
||||
@@ -16,6 +16,7 @@ from src.classes.fortune import try_trigger_fortune
|
||||
from src.classes.celestial_phenomenon import get_random_celestial_phenomenon
|
||||
from src.classes.long_term_objective import process_avatar_long_term_objective
|
||||
from src.classes.death import handle_death
|
||||
from src.classes.death_reason import DeathReason
|
||||
|
||||
class Simulator:
|
||||
def __init__(self, world: World):
|
||||
@@ -28,7 +29,7 @@ class Simulator:
|
||||
将 AI 的决策结果加载为角色的计划链。
|
||||
"""
|
||||
avatars_to_decide = []
|
||||
for avatar in list(self.world.avatar_manager.avatars.values()):
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
if avatar.current_action is None and not avatar.has_plans():
|
||||
avatars_to_decide.append(avatar)
|
||||
if not avatars_to_decide:
|
||||
@@ -45,7 +46,7 @@ class Simulator:
|
||||
提交阶段:为空闲角色提交计划中的下一个可执行动作,返回开始事件集合。
|
||||
"""
|
||||
events = []
|
||||
for avatar in list(self.world.avatar_manager.avatars.values()):
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
if avatar.current_action is None:
|
||||
start_event = avatar.commit_next_plan()
|
||||
if start_event is not None and not is_null_event(start_event):
|
||||
@@ -60,7 +61,7 @@ class Simulator:
|
||||
MAX_LOCAL_ROUNDS = 3
|
||||
for _ in range(MAX_LOCAL_ROUNDS):
|
||||
new_action_happened = False
|
||||
for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()):
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
# 本轮执行前若标记为新设,则清理,执行后由 Avatar 再统一清除
|
||||
if getattr(avatar, "_new_action_set_this_step", False):
|
||||
new_action_happened = True
|
||||
@@ -78,28 +79,30 @@ class Simulator:
|
||||
def _phase_resolve_death(self):
|
||||
"""
|
||||
结算死亡:
|
||||
- 战斗死亡已在 Action 中结算,此处不再重复(因为已从 avatars 中移除)
|
||||
- 战斗死亡已在 Action 中结算
|
||||
- 此时剩下的 avatars 都是存活的,只需检查非战斗因素(如老死、被动掉血)
|
||||
"""
|
||||
events = []
|
||||
# 遍历时可能修改字典,使用 list() 复制
|
||||
for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()):
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
is_dead = False
|
||||
reason = ""
|
||||
reason_str = ""
|
||||
death_reason = DeathReason.UNKNOWN
|
||||
|
||||
# 优先判定重伤(可能是被动效果导致)
|
||||
if avatar.hp <= 0:
|
||||
if avatar.hp.cur <= 0: # 注意:这里应该是 avatar.hp.cur 或者 avatar.hp <= 0 取决于 HP 类的实现,原代码是 avatar.hp <= 0
|
||||
is_dead = True
|
||||
reason = f"{avatar.name} 因重伤不治身亡"
|
||||
reason_str = f"{avatar.name} 因重伤不治身亡"
|
||||
death_reason = DeathReason.SERIOUS_INJURY
|
||||
# 其次判定寿元
|
||||
elif avatar.death_by_old_age():
|
||||
is_dead = True
|
||||
reason = f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁"
|
||||
reason_str = f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁"
|
||||
death_reason = DeathReason.OLD_AGE
|
||||
|
||||
if is_dead:
|
||||
event = Event(self.world.month_stamp, reason, related_avatars=[avatar.id])
|
||||
event = Event(self.world.month_stamp, reason_str, related_avatars=[avatar.id])
|
||||
events.append(event)
|
||||
handle_death(self.world, avatar)
|
||||
handle_death(self.world, avatar, death_reason)
|
||||
|
||||
return events
|
||||
|
||||
@@ -108,12 +111,13 @@ class Simulator:
|
||||
更新存活角色年龄,并以一定概率生成新修士,返回期间产生的事件集合。
|
||||
"""
|
||||
events = []
|
||||
for avatar_id, avatar in self.world.avatar_manager.avatars.items():
|
||||
for avatar in self.world.avatar_manager.get_living_avatars():
|
||||
avatar.update_age(self.world.month_stamp)
|
||||
if random.random() < self.birth_rate:
|
||||
age = random.randint(16, 60)
|
||||
gender = random.choice(list(Gender))
|
||||
name = get_random_name(gender)
|
||||
# create_random_mortal 内部会获取 existing_avatars,需要确保它处理活人
|
||||
new_avatar = create_random_mortal(self.world, self.world.month_stamp, name, Age(age, Realm.Qi_Refinement))
|
||||
self.world.avatar_manager.avatars[new_avatar.id] = new_avatar
|
||||
event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。", related_avatars=[new_avatar.id])
|
||||
@@ -127,11 +131,12 @@ class Simulator:
|
||||
- 触发奇遇(非动作)
|
||||
"""
|
||||
events = []
|
||||
for avatar in self.world.avatar_manager.avatars.values():
|
||||
living_avatars = self.world.avatar_manager.get_living_avatars()
|
||||
for avatar in living_avatars:
|
||||
avatar.update_time_effect()
|
||||
|
||||
# 使用 gather 并行触发奇遇
|
||||
tasks = [try_trigger_fortune(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
tasks = [try_trigger_fortune(avatar) for avatar in living_avatars]
|
||||
results = await asyncio.gather(*tasks)
|
||||
for res in results:
|
||||
if res:
|
||||
@@ -146,7 +151,8 @@ class Simulator:
|
||||
from src.classes.nickname import process_avatar_nickname
|
||||
|
||||
# 并发执行
|
||||
tasks = [process_avatar_nickname(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
living_avatars = self.world.avatar_manager.get_living_avatars()
|
||||
tasks = [process_avatar_nickname(avatar) for avatar in living_avatars]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
events = [e for e in results if e]
|
||||
@@ -158,7 +164,8 @@ class Simulator:
|
||||
检查角色是否需要生成/更新长期目标
|
||||
"""
|
||||
# 并发执行
|
||||
tasks = [process_avatar_long_term_objective(avatar) for avatar in self.world.avatar_manager.avatars.values()]
|
||||
living_avatars = self.world.avatar_manager.get_living_avatars()
|
||||
tasks = [process_avatar_long_term_objective(avatar) for avatar in living_avatars]
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
events = [e for e in results if e]
|
||||
|
||||
Reference in New Issue
Block a user