refactor gift
This commit is contained in:
@@ -7,7 +7,7 @@ from .conversation import Conversation
|
||||
from .dual_cultivation import DualCultivation
|
||||
from .talk import Talk
|
||||
from .impart import Impart
|
||||
from .gift_spirit_stone import GiftSpiritStone
|
||||
from .gift import Gift
|
||||
from .spar import Spar
|
||||
from .occupy import Occupy
|
||||
from src.classes.action.registry import register_action
|
||||
@@ -20,7 +20,7 @@ __all__ = [
|
||||
"DualCultivation",
|
||||
"Talk",
|
||||
"Impart",
|
||||
"GiftSpiritStone",
|
||||
"Gift",
|
||||
"Spar",
|
||||
"Occupy",
|
||||
]
|
||||
@@ -32,7 +32,7 @@ register_action(actual=True)(Conversation)
|
||||
register_action(actual=True)(DualCultivation)
|
||||
register_action(actual=True)(Talk)
|
||||
register_action(actual=True)(Impart)
|
||||
register_action(actual=True)(GiftSpiritStone)
|
||||
register_action(actual=True)(Gift)
|
||||
register_action(actual=True)(Spar)
|
||||
register_action(actual=True)(Occupy)
|
||||
|
||||
|
||||
262
src/classes/mutual_action/gift.py
Normal file
262
src/classes/mutual_action/gift.py
Normal file
@@ -0,0 +1,262 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .mutual_action import MutualAction
|
||||
from src.classes.event import Event
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
from src.classes.action_runtime import ActionResult
|
||||
from src.classes.world import World
|
||||
|
||||
|
||||
class Gift(MutualAction):
|
||||
"""赠送:向目标赠送灵石或物品。
|
||||
|
||||
- 支持赠送灵石、素材、装备。
|
||||
- 目标在交互范围内。
|
||||
- 目标可以感知具体赠送的物品并选择 接受 或 拒绝。
|
||||
- 若接受:物品从发起者转移给目标(装备会自动穿戴并顶替旧装备)。
|
||||
- 非灵石物品一次只能赠送1个。
|
||||
"""
|
||||
|
||||
ACTION_NAME = "赠送"
|
||||
EMOJI = "🎁"
|
||||
DESC = "向对方赠送灵石或物品"
|
||||
DOABLES_REQUIREMENTS = "发起者持有该物品;目标在交互范围内"
|
||||
|
||||
PARAMS = {
|
||||
"target_avatar": "Avatar",
|
||||
"item_name": "str",
|
||||
"amount": "int"
|
||||
}
|
||||
|
||||
FEEDBACK_ACTIONS = ["Accept", "Reject"]
|
||||
|
||||
def __init__(self, avatar: "Avatar", world: "World"):
|
||||
super().__init__(avatar, world)
|
||||
# 暂存当前赠送上下文,用于 step 跨帧和 build_prompt_infos
|
||||
self._current_gift_context: dict[str, Any] = {}
|
||||
self._gift_success = False
|
||||
|
||||
def _get_template_path(self) -> Path:
|
||||
return CONFIG.paths.templates / "mutual_action.txt"
|
||||
|
||||
def _resolve_gift(self, item_name: str, amount: int) -> tuple[Any, str, int]:
|
||||
"""
|
||||
解析赠送意图,返回 (物品对象/None, 显示名称, 实际数量)。
|
||||
物品对象为 None 代表是灵石。
|
||||
"""
|
||||
# 1. 灵石
|
||||
if item_name == "灵石" or not item_name:
|
||||
return None, "灵石", max(1, amount)
|
||||
|
||||
# 非灵石强制数量为 1
|
||||
forced_amount = 1
|
||||
|
||||
# 2. 检查装备 (Weapon/Auxiliary)
|
||||
if self.avatar.weapon and self.avatar.weapon.name == item_name:
|
||||
return self.avatar.weapon, self.avatar.weapon.name, forced_amount
|
||||
if self.avatar.auxiliary and self.avatar.auxiliary.name == item_name:
|
||||
return self.avatar.auxiliary, self.avatar.auxiliary.name, forced_amount
|
||||
|
||||
# 3. 检查背包素材 (Materials)
|
||||
for mat, qty in self.avatar.materials.items():
|
||||
if mat.name == item_name:
|
||||
return mat, mat.name, forced_amount
|
||||
|
||||
# 未找到
|
||||
return None, "", 0
|
||||
|
||||
def _get_gift_description(self) -> str:
|
||||
name = self._current_gift_context.get("name", "未知物品")
|
||||
amount = self._current_gift_context.get("amount", 0)
|
||||
obj = self._current_gift_context.get("obj")
|
||||
|
||||
from src.classes.weapon import Weapon
|
||||
from src.classes.auxiliary import Auxiliary
|
||||
|
||||
if obj is None: # 灵石
|
||||
return f"{amount} 灵石"
|
||||
elif isinstance(obj, (Weapon, Auxiliary)):
|
||||
return f"[{name}]"
|
||||
else:
|
||||
return f"{amount} {name}"
|
||||
|
||||
def step(self, target_avatar: "Avatar|str", item_name: str = "灵石", amount: int = 100) -> ActionResult:
|
||||
"""
|
||||
重写 step 以接收额外参数。
|
||||
将参数存入 self,然后调用父类 step 执行通用逻辑(LLM交互)。
|
||||
"""
|
||||
# 每一帧都会传入参数,更新上下文
|
||||
obj, name, real_amount = self._resolve_gift(item_name, amount)
|
||||
|
||||
self._current_gift_context = {
|
||||
"obj": obj,
|
||||
"name": name,
|
||||
"amount": real_amount,
|
||||
"original_item_name": item_name
|
||||
}
|
||||
|
||||
# 调用父类 step,父类会调用 _build_prompt_infos -> _can_start 等
|
||||
return super().step(target_avatar)
|
||||
|
||||
def _can_start(self, target: "Avatar") -> tuple[bool, str]:
|
||||
"""检查赠送条件:物品是否存在且足够"""
|
||||
obj = self._current_gift_context.get("obj")
|
||||
name = self._current_gift_context.get("name")
|
||||
amount = self._current_gift_context.get("amount", 0)
|
||||
original_name = self._current_gift_context.get("original_item_name")
|
||||
|
||||
# 如果 name 为空,说明 resolve 失败
|
||||
if not name:
|
||||
if original_name and original_name != "灵石":
|
||||
return False, f"未找到物品:{original_name}"
|
||||
# 如果是灵石但没解析出来(不应该发生,除非amount有问题,但max(1)了),或者是默认情况
|
||||
|
||||
# 1. 灵石
|
||||
if obj is None and name == "灵石":
|
||||
if self.avatar.magic_stone < amount:
|
||||
return False, f"灵石不足(当前:{self.avatar.magic_stone},需要:{amount})"
|
||||
return True, ""
|
||||
|
||||
# 2. 物品 (装备/素材)
|
||||
from src.classes.weapon import Weapon
|
||||
from src.classes.auxiliary import Auxiliary
|
||||
|
||||
if isinstance(obj, (Weapon, Auxiliary)):
|
||||
if self.avatar.weapon is not obj and self.avatar.auxiliary is not obj:
|
||||
return False, f"未装备该物品:{name}"
|
||||
elif obj is not None:
|
||||
# Material
|
||||
qty = self.avatar.materials.get(obj, 0)
|
||||
if qty < amount:
|
||||
return False, f"物品不足:{name}"
|
||||
else:
|
||||
return False, f"未找到物品:{original_name}"
|
||||
|
||||
# 检查交互范围 (父类 MutualAction.can_start 已经检查了,但这里是 _can_start 额外检查)
|
||||
from src.classes.observe import is_within_observation
|
||||
if not is_within_observation(self.avatar, target):
|
||||
return False, "目标不在交互范围内"
|
||||
|
||||
return True, ""
|
||||
|
||||
def _build_prompt_infos(self, target_avatar: "Avatar") -> dict:
|
||||
"""
|
||||
重写:构建传给 LLM 的 prompt 信息。
|
||||
"""
|
||||
infos = super()._build_prompt_infos(target_avatar)
|
||||
|
||||
gift_desc = self._get_gift_description()
|
||||
infos["action_info"] = f"向你赠送 {gift_desc}"
|
||||
|
||||
return infos
|
||||
|
||||
def start(self, target_avatar: "Avatar|str", item_name: str = "灵石", amount: int = 100) -> Event:
|
||||
# start 也会接收参数,同样需要设置上下文
|
||||
obj, name, real_amount = self._resolve_gift(item_name, amount)
|
||||
self._current_gift_context = {
|
||||
"obj": obj,
|
||||
"name": name,
|
||||
"amount": real_amount,
|
||||
"original_item_name": item_name
|
||||
}
|
||||
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
target_name = target.name if target is not None else str(target_avatar)
|
||||
|
||||
gift_desc = self._get_gift_description()
|
||||
|
||||
rel_ids = [self.avatar.id]
|
||||
if target is not None:
|
||||
rel_ids.append(target.id)
|
||||
|
||||
event = Event(
|
||||
self.world.month_stamp,
|
||||
f"{self.avatar.name} 试图向 {target_name} 赠送 {gift_desc}",
|
||||
related_avatars=rel_ids
|
||||
)
|
||||
|
||||
# 写入历史
|
||||
self.avatar.add_event(event, to_sidebar=False)
|
||||
if target is not None:
|
||||
target.add_event(event, to_sidebar=False)
|
||||
|
||||
self._gift_success = False
|
||||
return event
|
||||
|
||||
def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None:
|
||||
fb = str(feedback_name).strip()
|
||||
if fb == "Accept":
|
||||
self._apply_gift(target_avatar)
|
||||
self._gift_success = True
|
||||
else:
|
||||
self._gift_success = False
|
||||
|
||||
def _apply_gift(self, target: "Avatar") -> None:
|
||||
"""执行物品转移"""
|
||||
obj = self._current_gift_context.get("obj")
|
||||
amount = self._current_gift_context.get("amount", 0)
|
||||
|
||||
if obj is None:
|
||||
# 灵石
|
||||
if self.avatar.magic_stone >= amount:
|
||||
self.avatar.magic_stone -= amount
|
||||
target.magic_stone += amount
|
||||
else:
|
||||
from src.classes.weapon import Weapon
|
||||
from src.classes.auxiliary import Auxiliary
|
||||
|
||||
if isinstance(obj, (Weapon, Auxiliary)):
|
||||
# 装备:发起者卸下 -> 目标装备(旧装备自动处理)
|
||||
if self.avatar.weapon is obj:
|
||||
self.avatar.weapon = None
|
||||
elif self.avatar.auxiliary is obj:
|
||||
self.avatar.auxiliary = None
|
||||
else:
|
||||
return # 已经不在身上了
|
||||
|
||||
# 目标装备
|
||||
new_equip = obj
|
||||
|
||||
old_item = None
|
||||
if isinstance(new_equip, Weapon):
|
||||
old_item = target.weapon
|
||||
target.weapon = new_equip
|
||||
else: # Auxiliary
|
||||
old_item = target.auxiliary
|
||||
target.auxiliary = new_equip
|
||||
|
||||
# 旧装备简单处理:折价变成灵石加给目标
|
||||
if old_item:
|
||||
refund = int(getattr(old_item, "price", 0) * 0.5)
|
||||
if refund > 0:
|
||||
target.magic_stone += refund
|
||||
|
||||
else:
|
||||
# 素材:发起者移除 -> 目标添加
|
||||
if self.avatar.remove_material(obj, amount):
|
||||
target.add_material(obj, amount)
|
||||
|
||||
async def finish(self, target_avatar: "Avatar|str") -> list[Event]:
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
events: list[Event] = []
|
||||
if target is None:
|
||||
return events
|
||||
|
||||
if self._gift_success:
|
||||
gift_desc = self._get_gift_description()
|
||||
result_text = f"{self.avatar.name} 成功赠送了 {gift_desc} 给 {target.name}"
|
||||
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
related_avatars=[self.avatar.id, target.id]
|
||||
)
|
||||
events.append(result_event)
|
||||
|
||||
return events
|
||||
@@ -1,102 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .mutual_action import MutualAction
|
||||
from src.classes.event import Event
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.classes.avatar import Avatar
|
||||
|
||||
|
||||
class GiftSpiritStone(MutualAction):
|
||||
"""赠送灵石:向目标赠送灵石。
|
||||
|
||||
- 发起方灵石必须足够(至少100灵石)
|
||||
- 目标在交互范围内
|
||||
- 目标可以选择 接受 或 拒绝
|
||||
- 若接受:发起方扣除100灵石,目标获得100灵石
|
||||
"""
|
||||
|
||||
ACTION_NAME = "赠送灵石"
|
||||
EMOJI = "🎁"
|
||||
DESC = "向对方赠送灵石,一次赠送100灵石"
|
||||
DOABLES_REQUIREMENTS = "发起者至少有100灵石;目标在交互范围内"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
FEEDBACK_ACTIONS = ["Accept", "Reject"]
|
||||
|
||||
# 默认赠送数量
|
||||
GIFT_AMOUNT = 100
|
||||
|
||||
def _get_template_path(self) -> Path:
|
||||
return CONFIG.paths.templates / "mutual_action.txt"
|
||||
|
||||
def _can_start(self, target: "Avatar") -> tuple[bool, str]:
|
||||
"""检查赠送灵石的启动条件"""
|
||||
from src.classes.observe import is_within_observation
|
||||
if not is_within_observation(self.avatar, target):
|
||||
return False, "目标不在交互范围内"
|
||||
|
||||
# 检查发起者的灵石是否足够
|
||||
if self.avatar.magic_stone < self.GIFT_AMOUNT:
|
||||
return False, f"灵石不足(当前:{self.avatar.magic_stone},需要:{self.GIFT_AMOUNT})"
|
||||
|
||||
return True, ""
|
||||
|
||||
def start(self, target_avatar: "Avatar|str") -> Event:
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
target_name = target.name if target is not None else str(target_avatar)
|
||||
rel_ids = [self.avatar.id]
|
||||
if target is not None:
|
||||
rel_ids.append(target.id)
|
||||
event = Event(
|
||||
self.world.month_stamp,
|
||||
f"{self.avatar.name} 向 {target_name} 赠送 {self.GIFT_AMOUNT} 灵石",
|
||||
related_avatars=rel_ids
|
||||
)
|
||||
# 仅写入历史
|
||||
self.avatar.add_event(event, to_sidebar=False)
|
||||
if target is not None:
|
||||
target.add_event(event, to_sidebar=False)
|
||||
# 初始化内部标记
|
||||
self._gift_success = False
|
||||
return event
|
||||
|
||||
def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None:
|
||||
fb = str(feedback_name).strip()
|
||||
if fb == "Accept":
|
||||
# 接受则当场结算灵石转移
|
||||
self._apply_gift(target_avatar)
|
||||
self._gift_success = True
|
||||
else:
|
||||
# 拒绝
|
||||
self._gift_success = False
|
||||
|
||||
def _apply_gift(self, target: "Avatar") -> None:
|
||||
"""执行灵石转移"""
|
||||
# 从发起者扣除灵石
|
||||
self.avatar.magic_stone -= self.GIFT_AMOUNT
|
||||
# 目标获得灵石
|
||||
target.magic_stone += self.GIFT_AMOUNT
|
||||
|
||||
async def finish(self, target_avatar: "Avatar|str") -> list[Event]:
|
||||
target = self._get_target_avatar(target_avatar)
|
||||
events: list[Event] = []
|
||||
success = self._gift_success
|
||||
if target is None:
|
||||
return events
|
||||
|
||||
if success:
|
||||
result_text = f"""{self.avatar.name} 赠送了 {self.GIFT_AMOUNT} 灵石给 {target.name}
|
||||
({self.avatar.name} 灵石:{self.avatar.magic_stone + self.GIFT_AMOUNT} → {self.avatar.magic_stone},
|
||||
{target.name} 灵石:{target.magic_stone - self.GIFT_AMOUNT} → {target.magic_stone})"""
|
||||
result_event = Event(
|
||||
self.world.month_stamp,
|
||||
result_text,
|
||||
related_avatars=[self.avatar.id, target.id]
|
||||
)
|
||||
events.append(result_event)
|
||||
return events
|
||||
|
||||
198
tests/test_action_gift.py
Normal file
198
tests/test_action_gift.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import patch, MagicMock
|
||||
from src.classes.mutual_action.gift import Gift
|
||||
from src.classes.action_runtime import ActionResult, ActionStatus
|
||||
from src.classes.avatar import Avatar, Gender
|
||||
from src.classes.age import Age
|
||||
from src.classes.cultivation import Realm
|
||||
from src.classes.relation import Relation
|
||||
from src.utils.id_generator import get_avatar_id
|
||||
from src.classes.calendar import create_month_stamp, Year, Month
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def target_avatar(base_world):
|
||||
"""创建第二个角色作为赠送目标"""
|
||||
return Avatar(
|
||||
world=base_world,
|
||||
name="TargetNPC",
|
||||
id=get_avatar_id(),
|
||||
birth_month_stamp=create_month_stamp(Year(2000), Month.JANUARY),
|
||||
age=Age(20, Realm.Qi_Refinement),
|
||||
gender=Gender.FEMALE,
|
||||
pos_x=0,
|
||||
pos_y=0
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def gift_action(dummy_avatar, base_world):
|
||||
"""初始化 Gift 动作"""
|
||||
# 模拟 _call_llm_feedback,避免 step 中调用 asyncio.get_running_loop()
|
||||
with patch.object(Gift, '_call_llm_feedback') as mock_llm:
|
||||
# 返回一个 mock task,确保 task.done() 初始为 False,
|
||||
# 但在这里我们主要是为了让 step 不报错
|
||||
mock_llm.return_value = {}
|
||||
|
||||
action = Gift(dummy_avatar, base_world)
|
||||
|
||||
# 我们直接 Mock 掉 step 里的异步创建任务逻辑,
|
||||
# 因为我们主要测试的是:
|
||||
# 1. 参数是否正确解析 (step -> _resolve_gift)
|
||||
# 2. _can_start 逻辑
|
||||
# 3. 反馈结算逻辑 _settle_feedback / _apply_gift
|
||||
|
||||
# 但 step 本身有逻辑:
|
||||
# 1. 解析参数
|
||||
# 2. 检查 target
|
||||
# 3. 创建 task (这里会报错)
|
||||
|
||||
# 我们采用 patch asyncio.get_running_loop 的方式更简单
|
||||
yield action
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tests
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
class TestGiftAction:
|
||||
|
||||
# --- 1. 赠送灵石 ---
|
||||
|
||||
def test_gift_spirit_stone_success(self, gift_action, dummy_avatar, target_avatar):
|
||||
"""测试赠送灵石成功"""
|
||||
dummy_avatar.magic_stone = 1000
|
||||
target_avatar.magic_stone = 0
|
||||
|
||||
# Mock asyncio loop just to pass the step() check
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=100)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True, f"Should be able to start: {reason}"
|
||||
|
||||
# 模拟接受
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.magic_stone == 900
|
||||
assert target_avatar.magic_stone == 100
|
||||
assert gift_action._gift_success is True
|
||||
|
||||
def test_gift_spirit_stone_insufficient(self, gift_action, dummy_avatar, target_avatar):
|
||||
"""测试灵石不足"""
|
||||
dummy_avatar.magic_stone = 50
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=100)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
|
||||
assert can_start is False
|
||||
assert "灵石不足" in reason
|
||||
|
||||
# --- 2. 赠送素材 ---
|
||||
|
||||
def test_gift_material_success(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送素材成功"""
|
||||
test_material = mock_item_data["obj_material"]
|
||||
dummy_avatar.add_material(test_material, quantity=5)
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
# 非灵石强制数量 1
|
||||
gift_action.step(target_avatar, item_name=test_material.name, amount=999)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.get_material_quantity(test_material) == 4
|
||||
assert target_avatar.get_material_quantity(test_material) == 1
|
||||
assert gift_action._current_gift_context["amount"] == 1
|
||||
|
||||
def test_gift_material_not_owned(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送未持有的素材"""
|
||||
test_material = mock_item_data["obj_material"]
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_material.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is False
|
||||
|
||||
# --- 3. 赠送装备 ---
|
||||
|
||||
def test_gift_weapon_success_and_auto_equip(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送装备成功,且目标自动装备"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = test_weapon
|
||||
assert target_avatar.weapon is None
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is True
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert dummy_avatar.weapon is None
|
||||
assert target_avatar.weapon == test_weapon
|
||||
|
||||
def test_gift_weapon_fail_not_equipped(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试赠送未装备的装备"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name, amount=1)
|
||||
|
||||
can_start, reason = gift_action._can_start(target_avatar)
|
||||
assert can_start is False
|
||||
|
||||
def test_gift_weapon_target_trade_in(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""测试目标已有装备时,收到新装备会自动折价卖出旧的"""
|
||||
from tests.conftest import create_test_weapon
|
||||
from src.classes.cultivation import Realm
|
||||
|
||||
new_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = new_weapon
|
||||
|
||||
old_weapon = create_test_weapon("旧铁剑", Realm.Qi_Refinement, weapon_id=999)
|
||||
old_weapon.price = 100
|
||||
target_avatar.weapon = old_weapon
|
||||
target_avatar.magic_stone = 0
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=new_weapon.name, amount=1)
|
||||
|
||||
gift_action._settle_feedback(target_avatar, "Accept")
|
||||
|
||||
assert target_avatar.weapon == new_weapon
|
||||
# 50% refund
|
||||
assert target_avatar.magic_stone == 50
|
||||
|
||||
# --- 4. 上下文与描述 ---
|
||||
|
||||
def test_prompt_info_description(self, gift_action, dummy_avatar, target_avatar, mock_item_data):
|
||||
"""验证传给 LLM 的 prompt 中包含具体物品描述"""
|
||||
test_weapon = mock_item_data["obj_weapon"]
|
||||
dummy_avatar.weapon = test_weapon
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name=test_weapon.name)
|
||||
|
||||
infos = gift_action._build_prompt_infos(target_avatar)
|
||||
|
||||
assert f"[{test_weapon.name}]" in infos["action_info"]
|
||||
assert "赠送" in infos["action_info"]
|
||||
|
||||
def test_prompt_info_description_stones(self, gift_action, dummy_avatar, target_avatar):
|
||||
dummy_avatar.magic_stone = 1000
|
||||
|
||||
with patch("asyncio.get_running_loop", return_value=MagicMock()):
|
||||
gift_action.step(target_avatar, item_name="灵石", amount=500)
|
||||
|
||||
infos = gift_action._build_prompt_infos(target_avatar)
|
||||
assert "500 灵石" in infos["action_info"]
|
||||
Reference in New Issue
Block a user