from __future__ import annotations from src.classes.action import InstantAction from src.classes.event import Event 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 class Battle(InstantAction): COMMENT = "与目标进行对战,判定胜负" DOABLES_REQUIREMENTS = "任何时候都可以执行" PARAMS = {"avatar_name": "AvatarName"} # 提供用于故事生成的提示词:不出现血量/伤害等数值描述 STORY_PROMPT: str | None = ( "不要出现具体血量、伤害点数或任何数值表达。战斗要体现出双方的功法、境界、装备等。" ) # 战斗是大事(长期记忆) IS_MAJOR: bool = True def _get_target(self, avatar_name: str): """ 根据名字查找目标角色;找不到返回 None。 会自动规范化名字(去除括号等附加信息)以提高容错性。 """ normalized_name = normalize_avatar_name(avatar_name) for v in self.world.avatar_manager.avatars.values(): if v.name == normalized_name: return v return None def _execute(self, avatar_name: str) -> None: target = self._get_target(avatar_name) if target is None: return winner, loser, loser_damage, winner_damage = decide_battle(self.avatar, target) # 应用双方伤害 loser.hp.reduce(loser_damage) winner.hp.reduce(winner_damage) # 增加双方兵器熟练度(战斗经验) import random proficiency_gain = random.uniform(1.0, 3.0) self.avatar.increase_weapon_proficiency(proficiency_gain) if target is not None: target.increase_weapon_proficiency(proficiency_gain) self._last_result = (winner.name, loser.name, loser_damage, winner_damage) def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]: if avatar_name is None: return False, "缺少参数 avatar_name" ok = self._get_target(avatar_name) is not None return (ok, "" if ok else "目标不存在") def start(self, avatar_name: str) -> Event: target = self._get_target(avatar_name) target_name = target.name if target is not None else avatar_name # 展示双方折算战斗力(基于对手、含克制) s_att, s_def = get_effective_strength_pair(self.avatar, target) rel_ids = [self.avatar.id] if target is not None: try: rel_ids.append(target.id) except Exception: pass event = Event(self.world.month_stamp, f"{self.avatar.name} 对 {target_name} 发起战斗(战斗力:{self.avatar.name} {int(s_att)} vs {target_name} {int(s_def)})", related_avatars=rel_ids, is_major=True) # 记录开始事件内容,供故事生成使用 self._start_event_content = event.content return event # InstantAction 已实现 step 完成 async def finish(self, avatar_name: str) -> list[Event]: res = self._last_result if not (isinstance(res, tuple) and len(res) == 4): return [] winner, loser = res[0], res[1] loser_damage, winner_damage = res[2], res[3] result_text = f"{winner} 战胜了 {loser},{loser} 受伤{loser_damage}点,{winner} 也受伤{winner_damage}点" rel_ids = [self.avatar.id] try: t = self._get_target(avatar_name) if t is not None: rel_ids.append(t.id) except Exception: pass result_event = Event(self.world.month_stamp, result_text, related_avatars=rel_ids, is_major=True) # 生成战斗小故事 target = self._get_target(avatar_name) start_text = self._start_event_content if hasattr(self, '_start_event_content') else result_event.content # 战斗强制双人模式,允许改变关系 story = await StoryTeller.tell_story(start_text, result_event.content, self.avatar, target, prompt=self.STORY_PROMPT, allow_relation_changes=True) story_event = Event(self.world.month_stamp, story, related_avatars=rel_ids, is_story=True) return [result_event, story_event]