add choice helper

This commit is contained in:
bridge
2026-01-03 22:26:55 +08:00
parent 5b5cd79cb5
commit 3a9a9fd6f0
10 changed files with 58 additions and 61 deletions

View File

@@ -25,13 +25,11 @@ class Auxiliary:
# 特殊属性(用于存储实例特定数据)
special_data: dict = field(default_factory=dict)
def get_info(self) -> str:
"""获取简略信息"""
suffix = ""
# 万魂幡特殊显示
if self.name == "万魂幡" and self.special_data.get("devoured_souls", 0) > 0:
suffix = f"(吞噬魂魄:{self.special_data['devoured_souls']}"
return f"{self.name}{suffix}"
def get_info(self, detailed: bool = False) -> str:
"""获取信息"""
if detailed:
return self.get_detailed_info()
return f"{self.name}"
def get_detailed_info(self) -> str:
"""获取详细信息"""

View File

@@ -150,7 +150,7 @@ def _find_potential_master(avatar: Avatar) -> Optional[Avatar]:
def _can_get_weapon(avatar: Avatar) -> bool:
"""检查是否可以获得兵器奇遇:当前兵器是练气级(凡品)时可触发"""
"""检查是否可以获得兵器奇遇:当前兵器是练气级(练气)时可触发"""
if avatar.weapon is None:
return True
return avatar.weapon.realm == Realm.Qi_Refinement
@@ -395,7 +395,7 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
# 导入单选决策模块
from src.classes.single_choice import make_decision
from src.classes.single_choice import make_decision, format_swap_choice_desc
async def _resolve_choice(
new_obj: Any,
@@ -414,16 +414,17 @@ async def try_trigger_fortune(avatar: Avatar) -> list[Event]:
return True, f"{avatar.name} 获得{new_grade_val}{type_label}{new_name}"
old_name = old_obj.name
old_grade_val = getattr(old_obj, "grade", getattr(old_obj, "realm", None)).value
swap_desc = format_swap_choice_desc(new_obj, old_obj, type_label)
options = [
{
"key": "A",
"desc": f"保留原{type_label}{old_name}({old_grade_val}),放弃新{type_label}{new_name}({new_grade_val})"
"desc": f"保留原{type_label}{old_name}』,放弃新{type_label}{new_name}』。"
},
{
"key": "B",
"desc": f"卖掉原{type_label}{old_name}』换取灵石,接受新{type_label}{new_name}({new_grade_val})。"
"desc": f"卖掉原{type_label}{old_name}』换取灵石,接受新{type_label}{new_name}\n{swap_desc}"
}
]

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import random
from src.classes.single_choice import make_decision
from src.classes.single_choice import make_decision, format_swap_choice_desc
if TYPE_CHECKING:
from src.classes.avatar import Avatar
@@ -49,14 +49,15 @@ async def kill_and_grab(winner: Avatar, loser: Avatar) -> str:
else:
# 其他情况下都让 AI 决策
# 构建详细描述,包含效果
item_desc = loot_item.get_detailed_info()
current_desc = winner_current.get_detailed_info()
context = f"战斗胜利,{loser.name} 身死道消,留下了一件{loot_item.realm.value}{'兵器' if loot_type == 'weapon' else '辅助装备'}{item_desc}』。"
item_label = '兵器' if loot_type == 'weapon' else '辅助装备'
context = f"战斗胜利,{loser.name} 身死道消,留下了一件{loot_item.realm.value}{item_label}{loot_item.name}』。"
swap_desc = format_swap_choice_desc(loot_item, winner_current, item_label)
options = [
{
"key": "A",
"desc": f"夺取『{loot_item.name}』,卖掉身上的『{winner_current.name}』换取灵石。\n - 新装备:{item_desc}\n - 原装备:{current_desc}"
"desc": f"夺取『{loot_item.name}』,卖掉身上的『{winner_current.name}』换取灵石。\n{swap_desc}"
},
{
"key": "B",

View File

@@ -80,3 +80,25 @@ async def make_decision(
choice = options[0]["key"]
return choice
def format_swap_choice_desc(new_item: Any, old_item: Any | None, item_type_name: str) -> str:
"""
生成替换装备/功法时的决策描述文本。
Args:
new_item: 新获得的物品对象(必须实现 get_info(detailed=True)
old_item: 当前持有的物品对象(可能为 None
item_type_name: 物品类型名称(如"兵器""功法"
"""
new_info = item.get_info(detailed=True)
if old_item:
old_info = item.get_info(detailed=True)
return (
f"现有{item_type_name}{old_info}\n"
f"{item_type_name}{new_info}\n"
f"(选择替换将卖出旧{item_type_name}"
)
else:
return f"{item_type_name}{new_info}"

View File

@@ -73,7 +73,9 @@ class Technique:
return True
return bool(eval(self.condition, {"__builtins__": {}}, {"avatar": avatar, "Alignment": Alignment}))
def get_info(self) -> str:
def get_info(self, detailed: bool = False) -> str:
if detailed:
return self.get_detailed_info()
return f"{self.name}{self.attribute}{self.grade.value}"
def get_detailed_info(self) -> str:

View File

@@ -30,8 +30,10 @@ class Weapon:
# 特殊属性(如万魂幡的吞噬魂魄计数)
special_data: dict = field(default_factory=dict)
def get_info(self) -> str:
"""获取简略信息"""
def get_info(self, detailed: bool = False) -> str:
"""获取信息"""
if detailed:
return self.get_detailed_info()
return f"{self.name}"
def get_detailed_info(self) -> str:

View File

@@ -155,7 +155,7 @@ protagonist_configs = [
"level": 65,
"sect": 14, # 噬魔宗
"technique": 28, # 燃血大法 (自残修仙)
"weapon": 1001, # 凡品剑 (红中)
"weapon": 1001, # 练气剑 (红中)
"auxiliary": 2008, # 菩提子 (清心压制)
"personas": ["无常", "好斗", "忠诚"],
"appearance": 20,

View File

@@ -1,14 +1,14 @@
id,name,weapon_type,grade,desc,effects
,名称,兵器类型,等级(练气/筑基/金丹/元婴),描述/提示词,JSON形式支持宽松格式见effects.py说明
1001,凡品,,练气,市井铁匠铺打造的铁剑,勉强能用。,{extra_battle_strength_points: 1}
1002,凡品,,练气,厚背砍刀,寻常武夫的最爱。,{extra_battle_strength_points: 1}
1003,凡品,,练气,白蜡杆制成,枪头挂着红缨。,{extra_battle_strength_points: 1}
1004,凡品,,练气,普通的硬木棍,长与眉齐。,{extra_battle_strength_points: 1}
1005,凡品,,练气,文人雅士常用的折扇,附庸风雅。,{extra_battle_strength_points: 1}
1006,凡品,,练气,普通的皮鞭,多用于赶车或刑罚。,{extra_battle_strength_points: 1}
1007,凡品,,练气,桐木制成的古琴,音色尚可。,{extra_battle_strength_points: 1}
1008,凡品,,练气,紫竹削制,声音清脆。,{extra_battle_strength_points: 1}
1009,凡品暗器,暗器,练气,随处可见的铁制暗器。,{extra_battle_strength_points: 1}
1001,练气,,练气,市井铁匠铺打造的铁剑,勉强能用。,{extra_battle_strength_points: 1}
1002,练气,,练气,厚背砍刀,寻常武夫的最爱。,{extra_battle_strength_points: 1}
1003,练气,,练气,白蜡杆制成,枪头挂着红缨。,{extra_battle_strength_points: 1}
1004,练气,,练气,普通的硬木棍,长与眉齐。,{extra_battle_strength_points: 1}
1005,练气,,练气,文人雅士常用的折扇,附庸风雅。,{extra_battle_strength_points: 1}
1006,练气,,练气,普通的皮鞭,多用于赶车或刑罚。,{extra_battle_strength_points: 1}
1007,练气,,练气,桐木制成的古琴,音色尚可。,{extra_battle_strength_points: 1}
1008,练气,,练气,紫竹削制,声音清脆。,{extra_battle_strength_points: 1}
1009,练气暗器,暗器,练气,随处可见的铁制暗器。,{extra_battle_strength_points: 1}
2001,青霜剑,,筑基,剑气森寒。,{extra_battle_strength_points: 2}
2002,井中月,,筑基,刀光如井中映月,虚实难测。,{extra_battle_strength_points: 2}
2003,火尖枪,,筑基,枪头隐有火光,能引动凡火。,"{extra_battle_strength_points: 2, extra_max_hp: 50}"
1 id name weapon_type grade desc effects
2 名称 兵器类型 等级(练气/筑基/金丹/元婴) 描述/提示词 JSON形式(支持宽松格式,见effects.py说明)
3 1001 凡品剑 练气剑 练气 市井铁匠铺打造的铁剑,勉强能用。 {extra_battle_strength_points: 1}
4 1002 凡品刀 练气刀 练气 厚背砍刀,寻常武夫的最爱。 {extra_battle_strength_points: 1}
5 1003 凡品枪 练气枪 练气 白蜡杆制成,枪头挂着红缨。 {extra_battle_strength_points: 1}
6 1004 凡品棍 练气棍 练气 普通的硬木棍,长与眉齐。 {extra_battle_strength_points: 1}
7 1005 凡品扇 练气扇 练气 文人雅士常用的折扇,附庸风雅。 {extra_battle_strength_points: 1}
8 1006 凡品鞭 练气鞭 练气 普通的皮鞭,多用于赶车或刑罚。 {extra_battle_strength_points: 1}
9 1007 凡品琴 练气琴 练气 桐木制成的古琴,音色尚可。 {extra_battle_strength_points: 1}
10 1008 凡品笛 练气笛 练气 紫竹削制,声音清脆。 {extra_battle_strength_points: 1}
11 1009 凡品暗器 练气暗器 暗器 练气 随处可见的铁制暗器。 {extra_battle_strength_points: 1}
12 2001 青霜剑 筑基 剑气森寒。 {extra_battle_strength_points: 2}
13 2002 井中月 筑基 刀光如井中映月,虚实难测。 {extra_battle_strength_points: 2}
14 2003 火尖枪 筑基 枪头隐有火光,能引动凡火。 {extra_battle_strength_points: 2, extra_max_hp: 50}

View File

@@ -49,7 +49,7 @@ def dummy_avatar(base_world):
# 赋予一个 Mock 武器,防止 get_avatar_info 报错
av.weapon = MagicMock()
av.weapon.get_detailed_info.return_value = "测试木剑(凡品"
av.weapon.get_detailed_info.return_value = "测试木剑(练气"
av.weapon_proficiency = 0.0
return av

View File

@@ -82,35 +82,6 @@ class TestEquipment:
assert aux.name in detailed
assert aux.realm.value in detailed
def test_soul_banner(self):
"""测试万魂幡特殊逻辑"""
# 查找万魂幡
soul_banner = None
for a in auxiliaries_by_id.values():
if a.name == "万魂幡":
soul_banner = a
break
if not soul_banner:
pytest.skip("万魂幡 not found in config")
import copy
banner = copy.deepcopy(soul_banner)
# 初始状态
assert "吞噬魂魄" not in banner.get_info()
# 增加魂魄
banner.special_data["devoured_souls"] = 100
# 检查显示更新
assert "吞噬魂魄100" in banner.get_info()
assert "吞噬魂魄100" in banner.get_detailed_info()
# 检查结构化信息
struct = banner.get_structured_info()
assert "已吞噬魂魄100" in struct["desc"]
def test_grade_renaming_compatibility(self):
"""测试 realm 改名后的兼容性(如果有必要)"""
# 确保 weapon 和 auxiliary 确实有 realm 属性