From 937e71db850bed9d593e351368be36c0907d7ab0 Mon Sep 17 00:00:00 2001 From: bridge Date: Wed, 26 Nov 2025 21:09:54 +0800 Subject: [PATCH] add kill and grab --- src/classes/action/battle.py | 83 ++++++++++++++++++++++++++++++++++++ src/classes/alignment.py | 2 +- src/classes/single_choice.py | 22 +++++++--- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/classes/action/battle.py b/src/classes/action/battle.py index dd64372..479c75a 100644 --- a/src/classes/action/battle.py +++ b/src/classes/action/battle.py @@ -1,4 +1,5 @@ from __future__ import annotations +from typing import TYPE_CHECKING from src.classes.action import InstantAction from src.classes.event import Event @@ -6,6 +7,83 @@ 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.equipment_grade import EquipmentGrade +from src.classes.single_choice import make_decision +import random + +if TYPE_CHECKING: + from src.classes.avatar import Avatar + from src.classes.weapon import Weapon + from src.classes.auxiliary import Auxiliary + + +async def handle_loot(winner: Avatar, loser: Avatar) -> str: + """ + 处理杀人夺宝逻辑 + + Args: + winner: 胜利者 + loser: 失败者(已死亡) + + Returns: + str: 夺宝结果描述文本(如"并夺取了..."),如果没有夺取则为空字符串 + """ + loot_candidates = [] + + # 检查兵器 + if loser.weapon and loser.weapon.grade != EquipmentGrade.COMMON: + loot_candidates.append(("weapon", loser.weapon)) + + # 检查辅助装备 + if loser.auxiliary and loser.auxiliary.grade != EquipmentGrade.COMMON: + loot_candidates.append(("auxiliary", loser.auxiliary)) + + if not loot_candidates: + return "" + + # 优先选法宝,其次宝物;如果有多个同级,随机选一个 + loot_candidates.sort(key=lambda x: 1 if x[1].grade == EquipmentGrade.ARTIFACT else 0, reverse=True) + # 筛选出最高优先级的那些 + best_grade = loot_candidates[0][1].grade + best_candidates = [c for c in loot_candidates if c[1].grade == best_grade] + loot_type, loot_item = random.choice(best_candidates) + + should_loot = False + + # 判定是否夺取 + # 1. 如果winner当前部位为空或为凡品,直接夺取 + winner_current = getattr(winner, loot_type) + if winner_current is None or winner_current.grade == EquipmentGrade.COMMON: + should_loot = True + else: + # 2. 否则让 AI 决策 + context = f"战斗胜利,{loser.name} 身死道消,留下了一件{loot_item.grade.value}{'兵器' if loot_type == 'weapon' else '辅助装备'}『{loot_item.name}』({loot_item.desc})。" + options = [ + { + "key": "A", + "desc": f"夺取{loot_item.grade.value}『{loot_item.name}』({loot_item.desc}),替换掉身上的『{winner_current.name}』({winner_current.grade.value},{winner_current.desc})。" + }, + { + "key": "B", + "desc": f"放弃『{loot_item.name}』,保留身上的『{winner_current.name}』。" + } + ] + choice = await make_decision(winner, context, options) + if choice == "A": + should_loot = True + + if should_loot: + if loot_type == "weapon": + winner.change_weapon(loot_item) + from src.classes.weapon import get_common_weapon + loser.change_weapon(get_common_weapon(loot_item.weapon_type)) # 给死者塞个凡品防止空指针 + else: + winner.change_auxiliary(loot_item) + loser.change_auxiliary(None) + + return f"并夺取了对方的{loot_item.grade.value}『{loot_item.name}』!" + + return "" class Battle(InstantAction): @@ -83,6 +161,11 @@ class Battle(InstantAction): is_fatal = loser.hp <= 0 if is_fatal: result_text = f"{winner.name} 战胜了 {loser.name},造成{loser_damage}点伤害。{loser.name} 遭受重创,当场陨落。" + + # 杀人夺宝 + loot_text = await handle_loot(winner, loser) + result_text += loot_text + else: result_text = f"{winner.name} 战胜了 {loser.name},{loser.name} 受伤{loser_damage}点,{winner.name} 也受伤{winner_damage}点" diff --git a/src/classes/alignment.py b/src/classes/alignment.py index adc142d..4b6b3c7 100644 --- a/src/classes/alignment.py +++ b/src/classes/alignment.py @@ -61,5 +61,5 @@ alignment_strs = { alignment_infos = { Alignment.RIGHTEOUS: "正义阵营的理念是:扶助弱小,维护秩序,除魔卫道。", Alignment.NEUTRAL: "中立阵营的理念是:顺势而为,趋利避害,重视自度与平衡,不轻易站队。", - Alignment.EVIL: "邪恶阵营的理念是:弱肉强食,以自身利益为先,蔑视规则,推崇权力与恐惧。", + Alignment.EVIL: "邪恶阵营的理念是:弱肉强食,以自身利益为先,蔑视规则,推崇权力与恐惧。行事狠辣,常有杀人夺宝之举。", } diff --git a/src/classes/single_choice.py b/src/classes/single_choice.py index c3ec0ba..6004dca 100644 --- a/src/classes/single_choice.py +++ b/src/classes/single_choice.py @@ -1,11 +1,13 @@ -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, TYPE_CHECKING from src.utils.llm import call_llm_with_template -from src.classes.avatar import Avatar from src.utils.config import CONFIG import json +if TYPE_CHECKING: + from src.classes.avatar import Avatar + async def make_decision( - avatar: Avatar, + avatar: "Avatar", context_desc: str, options: List[Dict[str, Any]] ) -> str: @@ -57,11 +59,21 @@ async def make_decision( except (json.JSONDecodeError, ValueError): # 如果 JSON 解析失败,直接看字符串内容是否就是选项 key choice = clean_result + # 有时候 llm 会输出 "choice: A",这里做个兼容 else: choice = clean_result # 验证 choice 是否在 options key 中 valid_keys = {opt["key"] for opt in options} - assert choice in valid_keys, f"choice {choice} not in valid_keys {valid_keys}" + # 简单的容错:如果返回的是 "A." 或者 "A " + if choice not in valid_keys: + for k in valid_keys: + if k in choice: + choice = k + break + + if choice not in valid_keys: + # 兜底:默认选第一个 + choice = options[0]["key"] + return choice -