This commit is contained in:
bridge
2025-10-04 14:57:11 +08:00
parent f933eea8ac
commit 25b018e10a
6 changed files with 112 additions and 62 deletions

View File

@@ -16,45 +16,44 @@ class Simulator:
self.world = world
self.birth_rate = CONFIG.game.npc_birth_rate_per_month # 从配置文件读取NPC每月出生率
async def step(self):
async def _phase_decide_actions(self):
"""
前进一步(每步模拟是一个月时间)
结算这个时间内的所有情况
角色行为、世界变化、重大事件、etc。
先结算多个角色间互相交互的事件。
再去结算单个角色的事件。
决策阶段:仅对需要新计划的角色调用 AI当前无动作且无计划
将 AI 的决策结果加载为角色的计划链
"""
events = [] # list of Event
death_avatar_ids = [] # list of str
# 决策阶段:仅对需要新计划的角色调用 AI当前无动作且无计划
avatars_to_decide = []
for avatar in list(self.world.avatar_manager.avatars.values()):
if avatar.current_action is None and not avatar.has_plans():
avatars_to_decide.append(avatar)
if not avatars_to_decide:
return
if CONFIG.ai.mode == "llm":
ai = llm_ai
else:
ai = rule_ai
if avatars_to_decide:
decide_results = await ai.decide(self.world, avatars_to_decide)
for avatar, result in decide_results.items():
action_name_params_pairs, avatar_thinking, objective, _event = result
# 仅入队计划,不在此处添加开始事件,避免与提交阶段重复
avatar.load_decide_result_chain(action_name_params_pairs, avatar_thinking, objective)
# 提交阶段:为空闲角色提交计划中的下一个可执行动作
decide_results = await ai.decide(self.world, avatars_to_decide)
for avatar, result in decide_results.items():
action_name_params_pairs, avatar_thinking, objective, _event = result
# 仅入队计划,不在此处添加开始事件,避免与提交阶段重复
avatar.load_decide_result_chain(action_name_params_pairs, avatar_thinking, objective)
def _phase_commit_next_plans(self):
"""
提交阶段:为空闲角色提交计划中的下一个可执行动作,返回开始事件集合。
"""
events = []
for avatar in list(self.world.avatar_manager.avatars.values()):
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):
events.append(start_event)
return events
# 执行阶段:推进当前动作,支持同月链式抢占即时结算
# 采用最多3轮的小循环
# - 每轮遍历现有角色执行一次 tick_action
# - 若本轮有角色在遍历过程中被抢占并新设了动作(标记 _new_action_set_this_step=True下一轮继续执行
# - 最多 3 轮以防极端互相抢占导致长链
async def _phase_execute_actions(self):
"""
执行阶段:推进当前动作,支持同月链式抢占即时结算,返回期间产生的事件。
"""
events = []
MAX_LOCAL_ROUNDS = 3
for _ in range(MAX_LOCAL_ROUNDS):
new_action_happened = False
@@ -71,8 +70,14 @@ class Simulator:
# 若本轮未检测到新动作产生,则结束本地循环
if not new_action_happened:
break
return events
# 结算战斗等导致的死亡逻辑
def _phase_resolve_death(self):
"""
结算战斗等导致的死亡以及寿终正寝,移除死亡角色,返回死亡事件集合。
"""
events = []
death_avatar_ids = []
for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()):
if avatar.hp <= 0:
death_avatar_ids.append(avatar_id)
@@ -82,17 +87,17 @@ class Simulator:
death_avatar_ids.append(avatar_id)
event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}")
events.append(event)
# 删除死亡的角色(由 AvatarManager 清理关系并移除)
if death_avatar_ids:
self.world.avatar_manager.remove_avatars(death_avatar_ids)
# 寿命逻辑
return events
def _phase_update_age_and_birth(self):
"""
更新存活角色年龄,并以一定概率生成新修士,返回期间产生的事件集合。
"""
events = []
for avatar_id, avatar in self.world.avatar_manager.avatars.items():
avatar.update_age(self.world.month_stamp)
# 新角色
if random.random() < self.birth_rate:
age = random.randint(16, 60)
gender = random.choice(list(Gender))
@@ -101,13 +106,46 @@ class Simulator:
self.world.avatar_manager.avatars[new_avatar.id] = new_avatar
event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。")
events.append(event)
return events
# 将事件写入日志
def _phase_log_events(self, events):
"""
将事件写入日志。
"""
logger = get_logger().logger
for event in events:
logger.info("EVENT: %s", str(event))
# 最后结算年月
async def step(self):
"""
前进一步(每步模拟是一个月时间)
结算这个时间内的所有情况。
角色行为、世界变化、重大事件、etc。
先结算多个角色间互相交互的事件。
再去结算单个角色的事件。
"""
events = [] # list of Event
# 1. 决策阶段
await self._phase_decide_actions()
# 2. 提交阶段
events.extend(self._phase_commit_next_plans())
# 3. 执行阶段
events.extend(await self._phase_execute_actions())
# 4. 结算死亡
events.extend(self._phase_resolve_death())
# 5. 年龄与新生
events.extend(self._phase_update_age_and_birth())
# 6. 日志
self._phase_log_events(events)
# 7. 时间推进
self.world.month_stamp = self.world.month_stamp + 1
return events