Feat/play (#109)

* refactor: play
This commit is contained in:
4thfever
2026-01-29 22:52:35 +08:00
committed by GitHub
parent 6cb9e5c4e9
commit 202de66654
20 changed files with 640 additions and 1387 deletions

77
append_i18n.py Normal file
View File

@@ -0,0 +1,77 @@
import os
zh_content = """
# Action: Play Extended
msgid "action_reading"
msgstr "读书"
msgid "action_tea_tasting"
msgstr "品茶"
msgid "action_traveling"
msgstr "游历"
msgid "action_zither_playing"
msgstr "抚琴"
msgid "action_tea_party"
msgstr "茶会"
msgid "action_chess"
msgstr "下棋"
msgid "{avatar} starts {action}"
msgstr "{avatar} 开始{action}"
msgid "{avatar} finished {action}."
msgstr "{avatar} 完成了{action}"
msgid "gained {val} cultivation"
msgstr "若有所悟,修为增加 {val}"
msgid "breakthrough probability increased by {val:.1%}"
msgstr "心境提升,突破概率增加 {val:.1%}"
"""
en_content = """
# Action: Play Extended
msgid "action_reading"
msgstr "Reading"
msgid "action_tea_tasting"
msgstr "Tea Tasting"
msgid "action_traveling"
msgstr "Traveling"
msgid "action_zither_playing"
msgstr "Playing Zither"
msgid "action_tea_party"
msgstr "Tea Party"
msgid "action_chess"
msgstr "Chess"
msgid "{avatar} starts {action}"
msgstr "{avatar} starts {action}"
msgid "{avatar} finished {action}."
msgstr "{avatar} finished {action}."
msgid "gained {val} cultivation"
msgstr "gained {val} cultivation"
msgid "breakthrough probability increased by {val:.1%}"
msgstr "breakthrough probability increased by {val:.1%}"
"""
with open('src/i18n/locales/zh_CN/LC_MESSAGES/messages.po', 'a', encoding='utf-8') as f:
f.write(zh_content)
with open('src/i18n/locales/en_US/LC_MESSAGES/messages.po', 'a', encoding='utf-8') as f:
f.write(en_content)
print("Appended translations successfully.")

View File

@@ -22,7 +22,7 @@ from .move_away_from_region import MoveAwayFromRegion
from .escape import Escape
from .cultivate import Cultivate
from .breakthrough import Breakthrough
from .play import Play
from .play import Reading, TeaTasting, Traveling, ZitherPlaying
from .hunt import Hunt
from .harvest import Harvest
from .sell import Sell
@@ -58,7 +58,10 @@ register_action(actual=True)(MoveAwayFromRegion)
register_action(actual=False)(Escape)
register_action(actual=True)(Cultivate)
register_action(actual=True)(Breakthrough)
register_action(actual=True)(Play)
register_action(actual=True)(Reading)
register_action(actual=True)(TeaTasting)
register_action(actual=True)(Traveling)
register_action(actual=True)(ZitherPlaying)
register_action(actual=True)(Hunt)
register_action(actual=True)(Harvest)
register_action(actual=True)(Sell)
@@ -97,7 +100,10 @@ __all__ = [
"Escape",
"Cultivate",
"Breakthrough",
"Play",
"Reading",
"TeaTasting",
"Traveling",
"ZitherPlaying",
"Hunt",
"Harvest",
"Sell",
@@ -116,5 +122,3 @@ __all__ = [
"Mine",
"Retreat",
]

View File

@@ -1,44 +1,68 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from src.i18n import t
from src.classes.action import TimedAction
from src.classes.action.action import TimedAction
from src.classes.action_runtime import ActionStatus
from src.classes.event import Event
from src.utils.config import CONFIG
if TYPE_CHECKING:
from src.classes.avatar import Avatar
from src.classes.world import World
class Play(TimedAction):
"""
消遣动作,持续半年时间
"""
# 多语言 ID
ACTION_NAME_ID = "play_action_name"
DESC_ID = "play_description"
class BasePlayAction(TimedAction):
"""消遣动作基类"""
duration_months = 1
REQUIREMENTS_ID = "play_requirements"
# 不需要翻译的常量
EMOJI = "🪁"
PARAMS = {}
duration_months = 6
def __init__(self, avatar: Avatar, world: World):
super().__init__(avatar, world)
def _try_trigger_benefit(self) -> str:
"""尝试触发额外收益 (突破概率)"""
prob = CONFIG.play.base_benefit_probability if hasattr(CONFIG, 'play') else 0.05
if random.random() < prob:
rate = 0.2
self.avatar.add_breakthrough_rate(rate)
return t("breakthrough probability increased by {val:.1%}", val=rate)
return ""
def _execute(self) -> None:
"""
进行消遣活动
"""
# 消遣的具体逻辑可以在这里实现
# 比如增加心情值、减少压力等
pass
def can_start(self) -> tuple[bool, str]:
return True, ""
pass # 具体逻辑由子类实现或只需记录事件
def start(self) -> Event:
content = t("{avatar} begins leisure activities", avatar=self.avatar.name)
return Event(self.world.month_stamp, content, related_avatars=[self.avatar.id])
# TimedAction 已统一 step 逻辑
# 通用开始事件
return Event(self.world.month_stamp, t("{avatar} starts {action}", avatar=self.avatar.name, action=self.get_action_name()), related_avatars=[self.avatar.id])
async def finish(self) -> list[Event]:
return []
# 结算时尝试触发收益
benefit_msg = self._try_trigger_benefit()
content = t("{avatar} finished {action}.", avatar=self.avatar.name, action=self.get_action_name())
if benefit_msg:
content += f" {benefit_msg}"
return [Event(self.world.month_stamp, content, related_avatars=[self.avatar.id])]
# 具体动作实现
class Reading(BasePlayAction):
ACTION_NAME_ID = "action_reading"
DESC_ID = "action_reading_desc"
EMOJI = "📖"
class TeaTasting(BasePlayAction):
ACTION_NAME_ID = "action_tea_tasting"
DESC_ID = "action_tea_tasting_desc"
EMOJI = "🍵"
class Traveling(BasePlayAction):
ACTION_NAME_ID = "action_traveling"
DESC_ID = "action_traveling_desc"
EMOJI = "🧳"
class ZitherPlaying(BasePlayAction):
ACTION_NAME_ID = "action_zither_playing"
DESC_ID = "action_zither_playing_desc"
EMOJI = "🎵"

View File

@@ -103,9 +103,9 @@ class LLMAI(AI):
# 更新情绪
from src.classes.emotions import EmotionType
raw_emotion = r.get("current_emotion", "平静")
raw_emotion = r.get("current_emotion", "emotion_calm")
try:
# 尝试通过 value (中文) 获取枚举
# 尝试通过 value 获取枚举
avatar.emotion = EmotionType(raw_emotion)
except ValueError:
avatar.emotion = EmotionType.CALM

View File

@@ -127,6 +127,18 @@ class Avatar(
# 关系交互计数器: key=target_id, value={"count": 0, "checked_times": 0}
relation_interaction_states: dict[str, dict[str, int]] = field(default_factory=lambda: defaultdict(lambda: {"count": 0, "checked_times": 0}))
def add_breakthrough_rate(self, rate: float, duration: int = 1) -> None:
"""
增加突破成功率(临时效果)
"""
self.temporary_effects.append({
"source": "play_benefit",
"effects": {"extra_breakthrough_success_rate": rate},
"start_month": int(self.world.month_stamp),
"duration": duration
})
self.recalc_effects()
# ========== 宗门相关 ==========
def consume_elixir(self, elixir: Elixir) -> bool:
@@ -333,7 +345,8 @@ class Avatar(
action = self.current_action.action
# 使用 get_action_name() 获取翻译后的动作名称
return action.get_action_name()
return "思考"
from src.i18n import t
return t("action_thinking")
def __post_init__(self):
"""在Avatar创建后自动初始化tile和HP"""

View File

@@ -1,16 +1,16 @@
from enum import Enum
class EmotionType(Enum):
CALM = "平静"
HAPPY = "开心"
ANGRY = "愤怒"
SAD = "悲伤"
FEARFUL = "恐惧"
SURPRISED = "惊讶"
ANTICIPATING = "期待"
DISGUSTED = "厌恶"
CONFUSED = "疑惑"
TIRED = "疲惫"
CALM = "emotion_calm"
HAPPY = "emotion_happy"
ANGRY = "emotion_angry"
SAD = "emotion_sad"
FEARFUL = "emotion_fearful"
SURPRISED = "emotion_surprised"
ANTICIPATING = "emotion_anticipating"
DISGUSTED = "emotion_disgusted"
CONFUSED = "emotion_confused"
TIRED = "emotion_tired"
# 情绪对应的 Emoji 配置
EMOTION_EMOJIS = {

View File

@@ -10,6 +10,7 @@ from .impart import Impart
from .gift import Gift
from .spar import Spar
from .occupy import Occupy
from .play import TeaParty, Chess
from src.classes.action.registry import register_action
__all__ = [
@@ -23,6 +24,8 @@ __all__ = [
"Gift",
"Spar",
"Occupy",
"TeaParty",
"Chess",
]
# 注册 mutual actions均为实际动作
@@ -35,5 +38,5 @@ register_action(actual=True)(Impart)
register_action(actual=True)(Gift)
register_action(actual=True)(Spar)
register_action(actual=True)(Occupy)
register_action(actual=True)(TeaParty)
register_action(actual=True)(Chess)

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import random
from src.classes.mutual_action.mutual_action import MutualAction
from src.i18n import t
from src.utils.config import CONFIG
if TYPE_CHECKING:
from src.classes.avatar import Avatar
from src.classes.world import World
def try_trigger_play_benefit(avatar: Avatar) -> str:
"""
尝试触发消遣收益 (复用单人消遣的逻辑)
"""
prob = CONFIG.play.base_benefit_probability if hasattr(CONFIG, 'play') else 0.05
if random.random() < prob:
rate = 0.2
avatar.add_breakthrough_rate(rate)
return t("breakthrough probability increased by {val:.1%}", val=rate)
return ""
class TeaParty(MutualAction):
"""茶会:双人互动"""
ACTION_NAME_ID = "action_tea_party"
DESC_ID = "action_tea_party_desc"
STORY_PROMPT_ID = "action_tea_party_story_prompt"
REQUIREMENTS_ID = "play_requirements"
EMOJI = "🍵"
FEEDBACK_ACTIONS = ["Accept", "Reject"]
def _settle_feedback(self, target_avatar: Avatar, feedback_name: str) -> None:
if feedback_name == "Accept":
# 尝试给双方触发收益
try_trigger_play_benefit(self.avatar)
try_trigger_play_benefit(target_avatar)
class Chess(MutualAction):
"""下棋:双人互动"""
ACTION_NAME_ID = "action_chess"
DESC_ID = "action_chess_desc"
STORY_PROMPT_ID = "action_chess_story_prompt"
REQUIREMENTS_ID = "play_requirements"
EMOJI = "♟️"
FEEDBACK_ACTIONS = ["Accept", "Reject"]
def _settle_feedback(self, target_avatar: Avatar, feedback_name: str) -> None:
if feedback_name == "Accept":
# 尝试给双方触发收益
try_trigger_play_benefit(self.avatar)
try_trigger_play_benefit(target_avatar)

File diff suppressed because it is too large Load Diff

View File

@@ -726,7 +726,7 @@ msgid "play_description"
msgstr "消遣,放松身心"
msgid "play_requirements"
msgstr "无限制"
msgstr "目标在交互范围内"
msgid "{avatar} begins leisure activities"
msgstr "{avatar} 开始消遣"
@@ -1853,34 +1853,34 @@ msgstr "{names}{price}灵石)"
msgid "Sell: "
msgstr "出售:"
msgid "平静"
msgid "emotion_calm"
msgstr "平静"
msgid "开心"
msgid "emotion_happy"
msgstr "开心"
msgid "愤怒"
msgid "emotion_angry"
msgstr "愤怒"
msgid "悲伤"
msgid "emotion_sad"
msgstr "悲伤"
msgid "恐惧"
msgid "emotion_fearful"
msgstr "恐惧"
msgid "惊讶"
msgid "emotion_surprised"
msgstr "惊讶"
msgid "期待"
msgid "emotion_anticipating"
msgstr "期待"
msgid "厌恶"
msgid "emotion_disgusted"
msgstr "厌恶"
msgid "疑惑"
msgid "emotion_confused"
msgstr "疑惑"
msgid "疲惫"
msgid "emotion_tired"
msgstr "疲惫"
# ============================================================================
@@ -2041,91 +2041,13 @@ msgstr "{root_name}{elements}"
msgid "{sect} {rank}"
msgstr "{sect}{rank}"
# 灵根
msgid "金灵根"
msgstr "金灵根"
msgid "木灵根"
msgstr "木灵根"
msgid "水灵根"
msgstr "水灵根"
msgid "火灵根"
msgstr "火灵根"
msgid "土灵根"
msgstr "土灵根"
msgid "雷灵根"
msgstr "雷灵根"
msgid "冰灵根"
msgstr "冰灵根"
msgid "风灵根"
msgstr "风灵根"
msgid "暗灵根"
msgstr "暗灵根"
msgid "天灵根"
msgstr "天灵根"
# 宗门职位
msgid "掌门"
msgstr "掌门"
msgid "长老"
msgstr "长老"
msgid "内门弟子"
msgstr "内门弟子"
msgid "外门弟子"
msgstr "外门弟子"
# 外貌
msgid "奇丑"
msgstr "奇丑"
msgid "丑陋"
msgstr "丑陋"
msgid "粗陋"
msgstr "粗陋"
msgid "寒素"
msgstr "寒素"
msgid "清秀"
msgstr "清秀"
msgid "秀致"
msgstr "秀致"
msgid "俊美"
msgstr "俊美"
msgid "倾城"
msgstr "倾城"
msgid "绝色"
msgstr "绝色"
msgid "惊艳"
msgstr "惊艳"
msgid "你长得很俊,回头率很高。"
msgstr "你长得很俊,回头率很高。"
msgid "你长得很美,很抢眼。"
msgstr "你长得很美,很抢眼。"
# ============================================================================
# Additional (New)
# ============================================================================
msgid "action_thinking"
msgstr "思考"
msgid "{alignment}: {description}"
msgstr "{alignment}{description}"
@@ -2245,3 +2167,58 @@ msgstr "当阵营为{align}时"
msgid "When using {weapon_type}"
msgstr "当使用{weapon_type}时"
# Action: Play Extended
msgid "action_reading"
msgstr "读书"
msgid "action_tea_tasting"
msgstr "品茶"
msgid "action_traveling"
msgstr "游历"
msgid "action_zither_playing"
msgstr "抚琴"
msgid "action_tea_party"
msgstr "茶会"
msgid "action_chess"
msgstr "下棋"
msgid "{avatar} starts {action}"
msgstr "{avatar} 开始{action}"
msgid "{avatar} finished {action}."
msgstr "{avatar} 完成了{action}。"
msgid "gained {val} cultivation"
msgstr "若有所悟,修为增加 {val}"
msgid "breakthrough probability increased by {val:.1%}"
msgstr "心境提升,突破概率增加 {val:.1%}"
msgid "action_reading_desc"
msgstr "研读古籍,增长见闻。"
msgid "action_tea_tasting_desc"
msgstr "品味清茶,静心养性。"
msgid "action_traveling_desc"
msgstr "游历四方,感悟天地。"
msgid "action_zither_playing_desc"
msgstr "抚琴奏乐,陶冶情操。"
msgid "action_tea_party_desc"
msgstr "与友共品清茶。"
msgid "action_chess_desc"
msgstr "对弈博弈,磨砺心智。"
msgid "action_tea_party_story_prompt"
msgstr "两人正在举行一场雅致的茶会。"
msgid "action_chess_story_prompt"
msgstr "两人正在对弈下棋。"

View File

@@ -158,7 +158,7 @@ class AvatarLoadMixin:
# 恢复情绪
from src.classes.emotions import EmotionType
emotion_str = data.get("emotion", "平静")
emotion_str = data.get("emotion", "emotion_calm")
try:
avatar.emotion = EmotionType(emotion_str)
except ValueError:

View File

@@ -59,3 +59,6 @@ frontend:
cloud_freq: low
system:
language: zh-CN
play:
base_benefit_probability: 0.05

View File

@@ -71,3 +71,9 @@ id,key,name,exclusion_keys,desc,rarity,condition,effects
69,DEVOTED,Devoted,LUSTFUL;ROMANTIC;HEARTLESS,"You are devoted in love, once committed, you will not change.",N,,
70,ROMANTIC,Romantic,DESIRELESS;HEARTLESS;DEVOTED,"You are easily moved by emotions, full of longing for beautiful feelings.",N,,
71,HEARTLESS,Heartless,DEVOTED;ROMANTIC;FRIENDLY,"You are indifferent to emotions, not easily moved.",N,,
1001,PLAYFUL,Playful,RATIONAL;CULTIVATION_OBSESSED,"Indulging in fun, not thinking about progress.",N,,
1002,BOOKWORM,Bookworm,RASH,"Addicted to books.",R,,
1003,TEA_LOVER,Tea Lover,,"Can't go a day without tea.",R,,
1004,MUSICIAN,Musician,,"Good at playing the zither.",R,,
1005,TRAVELER,Traveler,HOMEBODY,"Reading ten thousand books is not as good as traveling ten thousand miles.",R,,
1006,CHESS_PLAYER,Chess Player,,"Good chess players live long.",R,,
1 id key name exclusion_keys desc rarity condition effects
71 69 DEVOTED Devoted LUSTFUL;ROMANTIC;HEARTLESS You are devoted in love, once committed, you will not change. N
72 70 ROMANTIC Romantic DESIRELESS;HEARTLESS;DEVOTED You are easily moved by emotions, full of longing for beautiful feelings. N
73 71 HEARTLESS Heartless DEVOTED;ROMANTIC;FRIENDLY You are indifferent to emotions, not easily moved. N
74 1001 PLAYFUL Playful RATIONAL;CULTIVATION_OBSESSED Indulging in fun, not thinking about progress. N
75 1002 BOOKWORM Bookworm RASH Addicted to books. R
76 1003 TEA_LOVER Tea Lover Can't go a day without tea. R
77 1004 MUSICIAN Musician Good at playing the zither. R
78 1005 TRAVELER Traveler HOMEBODY Reading ten thousand books is not as good as traveling ten thousand miles. R
79 1006 CHESS_PLAYER Chess Player Good chess players live long. R

View File

@@ -21,5 +21,5 @@ Requirements:
- The goal should fit the character's status and the Xianxia worldview.
- Do not fabricate information that has not appeared.
- It can be grand or specific.
- Primarily refer to character traits and personality, while considering sect, alignment, interpersonal relationships, historical events, etc.
- Primarily refer to character traits and personality, while considering sect, alignment, interpersonal relationships, historical events, etc. It can be related to cultivation, interpersonal relationships, personality, pastimes, self-cultivation, sects, or production activities.
- "thinking" should provide a detailed analysis; "long_term_objective" should only return the goal content itself, but don't mention how long it will take to complete.

View File

@@ -71,3 +71,9 @@ id,key,name,exclusion_keys,desc,rarity,condition,effects
69,DEVOTED,专情,LUSTFUL;ROMANTIC;HEARTLESS,你对爱情专一,一旦认定便不会改变。,N,,
70,ROMANTIC,多情,DESIRELESS;HEARTLESS;DEVOTED,你容易动情,对美好的感情充满向往。,N,,
71,HEARTLESS,薄情,DEVOTED;ROMANTIC;FRIENDLY,你对感情淡漠,不会轻易动心。,N,,
1001,PLAYFUL,贪玩,RATIONAL;CULTIVATION_OBSESSED,沉迷玩乐,不思进取。,N,,
1002,BOOKWORM,书痴,RASH,嗜书如命。,R,,
1003,TEA_LOVER,茶痴,,宁可三日无食,不可一日无茶。,R,,
1004,MUSICIAN,琴师,,善抚琴。,R,,
1005,TRAVELER,游历者,HOMEBODY,读万卷书不如行万里路。,R,,
1006,CHESS_PLAYER,棋痴,,善弈者长生。,R,,
1 id key name exclusion_keys desc rarity condition effects
71 69 DEVOTED 专情 LUSTFUL;ROMANTIC;HEARTLESS 你对爱情专一,一旦认定便不会改变。 N
72 70 ROMANTIC 多情 DESIRELESS;HEARTLESS;DEVOTED 你容易动情,对美好的感情充满向往。 N
73 71 HEARTLESS 薄情 DEVOTED;ROMANTIC;FRIENDLY 你对感情淡漠,不会轻易动心。 N
74 1001 PLAYFUL 贪玩 RATIONAL;CULTIVATION_OBSESSED 沉迷玩乐,不思进取。 N
75 1002 BOOKWORM 书痴 RASH 嗜书如命。 R
76 1003 TEA_LOVER 茶痴 宁可三日无食,不可一日无茶。 R
77 1004 MUSICIAN 琴师 善抚琴。 R
78 1005 TRAVELER 游历者 HOMEBODY 读万卷书不如行万里路。 R
79 1006 CHESS_PLAYER 棋痴 善弈者长生。 R

View File

@@ -21,5 +21,5 @@
- 目标要符合角色身份和修仙世界观
- 不要虚构未出现的信息
- 可以是宏大的也可以是具体的
- 主要参考角色特质和性格,兼顾宗门、阵营、人际关系、历史事件等
- 主要参考角色特质和性格,兼顾宗门、阵营、人际关系、历史事件等。可以和修炼相关、也可以和人际关系、性格、消遣、修养、宗门、生产活动相关。
- thinking要详细分析long_term_objective只返回目标内容本身但别提多久完成

93
tests/test_action_play.py Normal file
View File

@@ -0,0 +1,93 @@
import pytest
from unittest.mock import MagicMock, patch
import asyncio
from src.classes.action.play import Reading, TeaTasting, Traveling, ZitherPlaying
from src.classes.mutual_action.play import TeaParty, Chess
from src.classes.event import Event
from src.utils.config import CONFIG
class TestActionPlay:
@pytest.fixture
def play_avatar(self, dummy_avatar):
"""配置一个适合消遣的角色"""
# 确保没有初始临时效果
dummy_avatar.temporary_effects = []
return dummy_avatar
def test_single_play_action_instantiation(self, play_avatar):
"""测试单人消遣动作实例化"""
world = play_avatar.world
actions = [Reading, TeaTasting, Traveling, ZitherPlaying]
for action_cls in actions:
action = action_cls(play_avatar, world)
assert action.duration_months == 1
assert action.can_start()[0] is True
@patch('src.classes.action.play.random.random')
def test_single_play_benefit_trigger(self, mock_random, play_avatar):
"""测试单人消遣触发收益"""
# mock random < 0.05 to trigger benefit
# CONFIG.play.base_benefit_probability is 0.05
mock_random.return_value = 0.01
action = Reading(play_avatar, play_avatar.world)
# Execute finish (async)
events = asyncio.run(action.finish())
# Check event content
assert len(events) == 1
# 检查是否包含突破概率提升的描述 (英文或中文,取决于环境,这里假设 conftest 强制了中文)
# conftest.py force_chinese_language fixture sets language to zh-CN
# "breakthrough probability increased by 20.0%" -> zh-CN: "心境提升,突破概率增加 20.0%"
# Let's check for "20.0%" to be safe across languages if translation fails,
# or check the translation key if possible. But here we get the formatted string.
assert "20.0%" in events[0].content
# Check effect applied
# Need to check if temporary_effects has the entry
assert len(play_avatar.temporary_effects) == 1
effect = play_avatar.temporary_effects[0]
assert effect["source"] == "play_benefit"
assert effect["effects"]["extra_breakthrough_success_rate"] == 0.2
assert effect["duration"] == 1
@patch('src.classes.action.play.random.random')
def test_single_play_no_benefit(self, mock_random, play_avatar):
"""测试单人消遣未触发收益"""
# mock random >= 0.05
mock_random.return_value = 0.1
action = Reading(play_avatar, play_avatar.world)
events = asyncio.run(action.finish())
assert len(events) == 1
assert "20.0%" not in events[0].content
assert len(play_avatar.temporary_effects) == 0
@patch('src.classes.mutual_action.play.random.random')
def test_mutual_play_benefit(self, mock_random, play_avatar):
"""测试双人消遣触发收益"""
# Setup target avatar
target_avatar = MagicMock()
target_avatar.name = "Friend"
# MagicMock doesn't have temporary_effects list by default, but add_breakthrough_rate is called on it
# mock random < 0.05
mock_random.return_value = 0.01
action = TeaParty(play_avatar, play_avatar.world)
# Simulate feedback "Accept"
action._settle_feedback(target_avatar, "Accept")
# Check initiator benefit
assert len(play_avatar.temporary_effects) == 1
assert play_avatar.temporary_effects[0]["effects"]["extra_breakthrough_success_rate"] == 0.2
# Check target benefit
target_avatar.add_breakthrough_rate.assert_called_with(0.2)

View File

@@ -21,7 +21,7 @@ Testing Strategy:
"AvatarName": {
"action_name_params_pairs": [["cultivate", {"duration": 10}]],
"avatar_thinking": "...",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
results = await ai._decide(world, [avatar])
@@ -67,7 +67,7 @@ class TestLLMAIDecide:
],
"avatar_thinking": "I should cultivate to get stronger.",
"short_term_objective": "Reach Foundation Establishment",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -96,7 +96,7 @@ class TestLLMAIDecide:
],
"avatar_thinking": "Time to rest after cultivation.",
"short_term_objective": "Recover energy",
"current_emotion": "疲惫"
"current_emotion": "emotion_tired"
}
}
@@ -124,7 +124,7 @@ class TestLLMAIDecide:
],
"avatar_thinking": "Just cultivating.",
"short_term_objective": "Get stronger",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -155,7 +155,7 @@ class TestLLMAIDecide:
],
"avatar_thinking": "Mixed formats.",
"short_term_objective": "Test edge cases",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -201,7 +201,7 @@ class TestLLMAIDecide:
"action_name_params_pairs": [["cultivate", {}]],
"avatar_thinking": "Should not be used.",
"short_term_objective": "Not for test avatar",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -224,7 +224,7 @@ class TestLLMAIDecide:
],
"avatar_thinking": "All invalid.",
"short_term_objective": "Test",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -258,15 +258,15 @@ class TestLLMAIEmotionUpdate:
async def test_decide_updates_emotion_with_valid_value(self, mock_world, test_avatar):
"""Test that valid emotion string updates avatar.emotion correctly."""
emotions_to_test = [
("开心", EmotionType.HAPPY),
("愤怒", EmotionType.ANGRY),
("悲伤", EmotionType.SAD),
("恐惧", EmotionType.FEARFUL),
("惊讶", EmotionType.SURPRISED),
("期待", EmotionType.ANTICIPATING),
("厌恶", EmotionType.DISGUSTED),
("疑惑", EmotionType.CONFUSED),
("疲惫", EmotionType.TIRED),
("emotion_happy", EmotionType.HAPPY),
("emotion_angry", EmotionType.ANGRY),
("emotion_sad", EmotionType.SAD),
("emotion_fearful", EmotionType.FEARFUL),
("emotion_surprised", EmotionType.SURPRISED),
("emotion_anticipating", EmotionType.ANTICIPATING),
("emotion_disgusted", EmotionType.DISGUSTED),
("emotion_confused", EmotionType.CONFUSED),
("emotion_tired", EmotionType.TIRED),
]
ai = LLMAI()
@@ -329,7 +329,7 @@ class TestLLMAIEmotionUpdate:
mock_llm.return_value = mock_response
await ai._decide(mock_world, [test_avatar])
# Default is "平静" which maps to CALM.
# Default is "emotion_calm" which maps to CALM.
assert test_avatar.emotion == EmotionType.CALM
@@ -419,7 +419,7 @@ class TestLLMAIBatchProcessing:
"action_name_params_pairs": [["cultivate", {"duration": 10}]],
"avatar_thinking": "A is cultivating.",
"short_term_objective": "A's goal",
"current_emotion": "开心"
"current_emotion": "emotion_happy"
}
}
else:
@@ -428,7 +428,7 @@ class TestLLMAIBatchProcessing:
"action_name_params_pairs": [["move", {"target_x": 2, "target_y": 2}]],
"avatar_thinking": "B is moving.",
"short_term_objective": "B's goal",
"current_emotion": "愤怒"
"current_emotion": "emotion_angry"
}
}
@@ -492,7 +492,7 @@ class TestAIDecideWrapper:
"action_name_params_pairs": [["cultivate", {}]],
"avatar_thinking": "Testing.",
"short_term_objective": "Test",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -539,7 +539,7 @@ class TestLLMAIThinkingFieldVariants:
"action_name_params_pairs": [["cultivate", {}]],
"thinking": "Using thinking field.", # Not avatar_thinking
"short_term_objective": "Test",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -561,7 +561,7 @@ class TestLLMAIThinkingFieldVariants:
"avatar_thinking": "Preferred field.",
"thinking": "Fallback field.",
"short_term_objective": "Test",
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}
@@ -581,7 +581,7 @@ class TestLLMAIThinkingFieldVariants:
test_avatar.name: {
"action_name_params_pairs": [["cultivate", {}]],
# No avatar_thinking, thinking, or short_term_objective
"current_emotion": "平静"
"current_emotion": "emotion_calm"
}
}