This commit is contained in:
bridge
2025-10-02 18:20:42 +08:00
parent c9d51642fb
commit 6cdaa888f4
6 changed files with 87 additions and 43 deletions

View File

@@ -16,7 +16,7 @@ from src.classes.action import (
)
from src.classes.mutual_action import (
DriveAway,
AttackInteract,
Attack,
MoveAwayFromAvatar,
MoveAwayFromRegion,
)
@@ -35,7 +35,7 @@ ALL_ACTION_CLASSES = [
Sold,
# 互动相关动作(实际执行的反馈动作也纳入)
DriveAway,
AttackInteract,
Attack,
MoveAwayFromAvatar,
MoveAwayFromRegion,
]
@@ -49,7 +49,8 @@ ALL_ACTUAL_ACTION_CLASSES = [
Hunt,
Harvest,
Sold,
# 互动类/反馈类作为即时落地,不进入可选择动作空间
DriveAway,
Attack,
]
ALL_ACTION_NAMES = [action.__name__ for action in ALL_ACTION_CLASSES]

View File

@@ -58,7 +58,8 @@ def get_escape_success_rate(attacker: "Avatar", defender: "Avatar") -> float:
def get_damage(winner: "Avatar", loser: "Avatar") -> int:
"""
根据胜负双方境界差距估算伤害:基础30差一大境界+10上限80。
根据胜负双方境界差距估算伤害:基础100差一大境界+100,上限500。
"""
gap = max(0, _realm_order(winner.cultivation_progress.realm) - _realm_order(loser.cultivation_progress.realm))
return min(80, 30 + 10 * gap)
# return min(500, 100 + 100 * gap)
return 500

View File

@@ -3,17 +3,20 @@ from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from src.classes.action import DefineAction, ActualActionMixin, LLMAction
from src.classes.action import DefineAction, ActualActionMixin, LLMAction, StepStatus
from src.classes.battle import get_escape_success_rate
from src.classes.tile import get_avatar_distance
import random
from src.classes.event import Event
from src.utils.llm import get_prompt_and_call_llm
from src.utils.config import CONFIG
from src.classes.action import long_action
if TYPE_CHECKING:
from src.classes.avatar import Avatar
# 默认所有的互动都是1个月时间
@long_action(step_month=1)
class MutualAction(DefineAction, LLMAction):
"""
互动动作A 对 B 发起动作B 可以给出反馈(由 LLM 决策)。
@@ -80,17 +83,27 @@ class MutualAction(DefineAction, LLMAction):
# 默认不额外记录,由事件系统承担
return
def _execute(self, target_avatar: "Avatar|str") -> None:
# 允许传入名字字符串
def _get_target_avatar(self, target_avatar: "Avatar|str") -> "Avatar|None":
"""
获取目标角色,支持传入角色对象或名字字符串
Returns:
目标角色,如果找不到返回 None
"""
if isinstance(target_avatar, str):
name = target_avatar
target_avatar = None
for v in self.world.avatar_manager.avatars.values():
if v.name == name:
target_avatar = v
break
if target_avatar is None:
return
return v
return None
return target_avatar
def _execute(self, target_avatar: "Avatar|str") -> None:
# 允许传入名字字符串
target_avatar = self._get_target_avatar(target_avatar)
if target_avatar is None:
return
infos = self._build_prompt_infos(target_avatar)
res = self._call_llm_feedback(infos)
# LLM 只返回 {avatar_name_2: {thinking, feedback}}
@@ -100,10 +113,6 @@ class MutualAction(DefineAction, LLMAction):
# 挂到目标的thinking上面向UI/日志),并执行反馈落地
target_avatar.thinking = thinking
# 发起事件(进入侧边栏与双方历史)
start_event = Event(self.world.month_stamp, f"{self.avatar.name}{target_avatar.name} 发起 {getattr(self, 'ACTION_NAME', self.name)}")
self.avatar.add_event(start_event)
target_avatar.add_event(start_event)
# 1) 先清空目标后续计划(仅清空队列,不动当前动作)
if hasattr(target_avatar, "clear_plans"):
target_avatar.clear_plans()
@@ -122,9 +131,45 @@ class MutualAction(DefineAction, LLMAction):
# 4) 记录历史(文本记录)
self._apply_feedback(target_avatar, feedback)
# 互动行为一般是一次性的即时动作
# 互动类行为仍保持一次性效果,由自身执行时发事件
# 不接入新的调度器接口
# 实现 ActualActionMixin 接口
def can_start(self, target_avatar: "Avatar|str|None" = None) -> bool:
"""
检查互动动作能否启动两个角色距离必须小于等于2
"""
if target_avatar is None:
return False
target = self._get_target_avatar(target_avatar)
if target is None:
return False
distance = get_avatar_distance(self.avatar, target)
return distance <= 2
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)
action_name = getattr(self, 'ACTION_NAME', self.name)
event = Event(self.world.month_stamp, f"{self.avatar.name}{target_name} 发起 {action_name}")
# 将事件添加到双方历史
self.avatar.add_event(event)
if target is not None:
target.add_event(event)
return event
def step(self, target_avatar: "Avatar|str") -> tuple[StepStatus, list[Event]]:
"""
执行互动动作,互动动作是即时完成的
"""
self.execute(target_avatar=target_avatar)
return StepStatus.COMPLETED, []
def finish(self, target_avatar: "Avatar|str") -> list[Event]:
"""
完成互动动作,事件已在 step 中处理,无需额外事件
"""
return []
class DriveAway(MutualAction, ActualActionMixin):
@@ -144,8 +189,8 @@ class DriveAway(MutualAction, ActualActionMixin):
params = {"avatar_name": self.avatar.name}
self._set_target_immediate_action(target_avatar, fb, params)
class AttackInteract(MutualAction, ActualActionMixin):
"""攻击互动:被攻击者的反馈。"""
class Attack(MutualAction, ActualActionMixin):
"""攻击另一个NPC"""
ACTION_NAME = "攻击"
COMMENT = "对目标进行攻击。"
DOABLES_REQUIREMENTS = "与目标处于同一区域"

View File

@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from src.classes.region import Region
from src.classes.avatar import Avatar
class TileType(Enum):
PLAIN = "plain" # 平原
@@ -32,3 +33,17 @@ class Tile():
y: int
region: 'Region' = None # 可以是一个region的一部分也可以不属于任何region
def get_avatar_distance(avatar1: 'Avatar', avatar2: 'Avatar') -> int:
"""
计算两个 Avatar 之间的汉明距离(曼哈顿距离)
Args:
avatar1: 第一个角色
avatar2: 第二个角色
Returns:
两个角色之间的距离
"""
return abs(avatar1.pos_x - avatar2.pos_x) + abs(avatar1.pos_y - avatar2.pos_y)

View File

@@ -20,4 +20,4 @@ df:
ids_separator: ";"
avatar:
persona_num: 3
persona_num: 1

View File

@@ -1,21 +1,3 @@
id,name,exclusion_ids,prompt
,,和本persona互斥的persona的id,输入给LLM的prompt
1,理性,2;5,你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动
2,无常,1;9,你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
3,怠惰,4,你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
4,冒险,3;10,你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。
5,随性,1,你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。
6,贪财,,你是一个贪财的人,你对灵石和财富有着强烈的渴望。
7,采药,,你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。
8,猎者,,你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。
9,沉思,2,你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理。
10,惜命,4,你是一个惜命的人,你总是会珍惜自己的生命,不会轻易冒险。
11,友爱,13;14;15;12,你是一个友爱的人,你重视同伴与和谐,乐于助人,倾向通过协作与沟通化解矛盾。
12,复仇,11;14,你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。
13,孤僻,11,你是一个孤僻的人,你喜欢独处,避免与人深交,更信赖自己的判断与行动。
14,淡漠,11;12;15,你是一个淡漠的人,你情感克制,对外界冷静疏离,不轻易被他人或事件影响。
15,好斗,11;14;10;17,你是一个好斗的人,你直面冲突,偏好以力量与对抗解决问题,越挫越勇。
16,鲁莽,1;9;10,你是一个鲁莽的人,你行事冲动、少考虑后果,常凭直觉立刻行动。
17,胆小,4;15;12,你是一个胆小的人,你谨小慎微,容易畏惧风险,倾向回避正面冲突。
18,霸道,11;17,你是一个霸道的人,你行事强势,不讲道理,习惯以自己的利益为先,倾向多吃多占、压人一步,对他人的反对不以为意。
19,修行痴迷,2;3;5,你是一个对修行极度痴迷的人,你将绝大多数时间用于修炼,厌恶与修行无关的社交与享乐。
12,复仇,11;14,你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。你要立刻复仇
1 id name exclusion_ids prompt
2 和本persona互斥的persona的id 输入给LLM的prompt
3 1 12 理性 复仇 2;5 11;14 你是一个理性的人,你总是会用逻辑来思考问题,做事会谋定而后动。 你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。你要立刻复仇。
2 无常 1;9 你是一个无常的人,目标飘忽不定,不会长期坚持一个目标。
3 怠惰 4 你是一个怠惰的人,你总是会拖延,不想努力,更热衷于享受人生。
4 冒险 3;10 你是一个冒险的人,你总是会冒险,喜欢刺激,总想放手一搏。
5 随性 1 你是一个随性的人,你总是会随机应变,性子到哪里了就是哪里,没有一定之规。
6 贪财 你是一个贪财的人,你对灵石和财富有着强烈的渴望。
7 采药 你是一个热爱采集的人,喜欢在山林中寻找各种奇花异草和灵药,对植物有着敏锐的直觉和深厚的兴趣。你认为大自然的恩赐需要用心去发现和珍惜。
8 猎者 你是一个热爱狩猎的人,享受在野外追踪猎物的刺激感,对各种动物的习性了如指掌。你相信通过狩猎能够磨练自己的意志和技能,获得更强大的力量。
9 沉思 2 你是一个沉思的人,你总是会深思熟虑,思考问题比较有哲理。
10 惜命 4 你是一个惜命的人,你总是会珍惜自己的生命,不会轻易冒险。
11 友爱 13;14;15;12 你是一个友爱的人,你重视同伴与和谐,乐于助人,倾向通过协作与沟通化解矛盾。
12 复仇 11;14 你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。
13 孤僻 11 你是一个孤僻的人,你喜欢独处,避免与人深交,更信赖自己的判断与行动。
14 淡漠 11;12;15 你是一个淡漠的人,你情感克制,对外界冷静疏离,不轻易被他人或事件影响。
15 好斗 11;14;10;17 你是一个好斗的人,你直面冲突,偏好以力量与对抗解决问题,越挫越勇。
16 鲁莽 1;9;10 你是一个鲁莽的人,你行事冲动、少考虑后果,常凭直觉立刻行动。
17 胆小 4;15;12 你是一个胆小的人,你谨小慎微,容易畏惧风险,倾向回避正面冲突。
18 霸道 11;17 你是一个霸道的人,你行事强势,不讲道理,习惯以自己的利益为先,倾向多吃多占、压人一步,对他人的反对不以为意。
19 修行痴迷 2;3;5 你是一个对修行极度痴迷的人,你将绝大多数时间用于修炼,厌恶与修行无关的社交与享乐。