fix bug
This commit is contained in:
@@ -11,7 +11,7 @@ from src.classes.event import Event, NULL_EVENT
|
||||
from src.classes.item import Item, items_by_name
|
||||
from src.classes.prices import prices
|
||||
from src.classes.hp_and_mp import HP_MAX_BY_REALM, MP_MAX_BY_REALM
|
||||
from src.classes.battle import decide_battle
|
||||
from src.classes.battle import decide_battle, get_escape_success_rate
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -287,6 +287,153 @@ class MoveToAvatar(DefineAction, ActualActionMixin):
|
||||
def finish(self, avatar_name: str) -> list[Event]:
|
||||
return []
|
||||
|
||||
|
||||
@long_action(step_month=6)
|
||||
class MoveAwayFromAvatar(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
持续远离指定角色,持续6个月。
|
||||
- 规则:每月尝试使与目标的曼哈顿距离增大一步
|
||||
- 任何时候都可以启动
|
||||
"""
|
||||
COMMENT = "持续远离指定角色"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
|
||||
def _find_avatar_by_name(self, name: str) -> "Avatar|None":
|
||||
for v in self.world.avatar_manager.avatars.values():
|
||||
if v.name == name:
|
||||
return v
|
||||
return None
|
||||
|
||||
def _execute(self, avatar_name: str) -> None:
|
||||
target = self._find_avatar_by_name(avatar_name)
|
||||
if target is None:
|
||||
return
|
||||
# 计算远离方向:使曼哈顿距离尽量增大
|
||||
dx = 1 if self.avatar.pos_x >= target.pos_x else -1
|
||||
dy = 1 if self.avatar.pos_y >= target.pos_y else -1
|
||||
nx = self.avatar.pos_x + dx
|
||||
ny = self.avatar.pos_y + dy
|
||||
if self.world.map.is_in_bounds(nx, ny):
|
||||
self.avatar.pos_x = nx
|
||||
self.avatar.pos_y = ny
|
||||
self.avatar.tile = self.world.map.get_tile(nx, ny)
|
||||
|
||||
def can_start(self, avatar_name: str | None = None) -> bool:
|
||||
return True
|
||||
|
||||
def start(self, avatar_name: str) -> Event:
|
||||
target_name = avatar_name
|
||||
try:
|
||||
t = self._find_avatar_by_name(avatar_name)
|
||||
if t is not None:
|
||||
target_name = t.name
|
||||
except Exception:
|
||||
pass
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 开始远离 {target_name}")
|
||||
|
||||
def step(self, avatar_name: str) -> tuple[StepStatus, list[Event]]:
|
||||
self.execute(avatar_name=avatar_name)
|
||||
done = getattr(self, "is_finished")()
|
||||
return (StepStatus.COMPLETED if done else StepStatus.RUNNING), []
|
||||
|
||||
def finish(self, avatar_name: str) -> list[Event]:
|
||||
return []
|
||||
|
||||
|
||||
class MoveAwayFromRegion(DefineAction, ActualActionMixin):
|
||||
COMMENT = "离开指定区域"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"region": "RegionName"}
|
||||
|
||||
def _execute(self, region: str) -> None:
|
||||
# 简化:向地图边缘移动一步
|
||||
dx = 1 if self.avatar.pos_x < self.world.map.width - 1 else -1
|
||||
dy = 1 if self.avatar.pos_y < self.world.map.height - 1 else -1
|
||||
nx = max(0, min(self.world.map.width - 1, self.avatar.pos_x + dx))
|
||||
ny = max(0, min(self.world.map.height - 1, self.avatar.pos_y + dy))
|
||||
if self.world.map.is_in_bounds(nx, ny):
|
||||
self.avatar.pos_x = nx
|
||||
self.avatar.pos_y = ny
|
||||
self.avatar.tile = self.world.map.get_tile(nx, ny)
|
||||
|
||||
def can_start(self, region: str | None = None) -> bool:
|
||||
return True
|
||||
|
||||
def start(self, region: str) -> Event:
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 开始离开 {region}")
|
||||
|
||||
def step(self, region: str) -> tuple[StepStatus, list[Event]]:
|
||||
self.execute(region=region)
|
||||
return StepStatus.COMPLETED, []
|
||||
|
||||
def finish(self, region: str) -> list[Event]:
|
||||
return []
|
||||
|
||||
|
||||
class Escape(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
逃离:尝试从对方身边脱离(有成功率)。
|
||||
成功:抢占并进入 MoveAwayFromAvatar(6个月)。
|
||||
失败:抢占并进入 Battle。
|
||||
"""
|
||||
COMMENT = "逃离对方(基于成功率判定)"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
|
||||
def _find_avatar_by_name(self, name: str) -> "Avatar|None":
|
||||
for v in self.world.avatar_manager.avatars.values():
|
||||
if v.name == name:
|
||||
return v
|
||||
return None
|
||||
|
||||
def _preempt_avatar(self, avatar: "Avatar") -> None:
|
||||
avatar.clear_plans()
|
||||
avatar.current_action = None
|
||||
|
||||
def _add_event_pair(self, event: Event, initiator: "Avatar", target: "Avatar|None") -> None:
|
||||
initiator.add_event(event)
|
||||
if target is not None:
|
||||
target.add_event(event, to_sidebar=False)
|
||||
|
||||
def _execute(self, avatar_name: str) -> None:
|
||||
target = self._find_avatar_by_name(avatar_name)
|
||||
if target is None:
|
||||
return
|
||||
escape_rate = float(get_escape_success_rate(target, self.avatar))
|
||||
import random as _r
|
||||
success = _r.random() < escape_rate
|
||||
result_text = "成功" if success else "失败"
|
||||
result_event = Event(self.world.month_stamp, f"{self.avatar.name} 试图从 {target.name} 逃离:{result_text}")
|
||||
self._add_event_pair(result_event, initiator=self.avatar, target=target)
|
||||
if success:
|
||||
self._preempt_avatar(self.avatar)
|
||||
self.avatar.load_decide_result_chain([("MoveAwayFromAvatar", {"avatar_name": avatar_name})], self.avatar.thinking, "")
|
||||
start_event = self.avatar.commit_next_plan()
|
||||
if start_event is not None:
|
||||
self._add_event_pair(start_event, initiator=self.avatar, target=target)
|
||||
else:
|
||||
self._preempt_avatar(self.avatar)
|
||||
self.avatar.load_decide_result_chain([("Battle", {"avatar_name": avatar_name})], self.avatar.thinking, "")
|
||||
start_event = self.avatar.commit_next_plan()
|
||||
if start_event is not None:
|
||||
self._add_event_pair(start_event, initiator=self.avatar, target=target)
|
||||
|
||||
def can_start(self, avatar_name: str | None = None) -> bool:
|
||||
return True
|
||||
|
||||
def start(self, avatar_name: str) -> Event:
|
||||
target = self._find_avatar_by_name(avatar_name)
|
||||
target_name = target.name if target is not None else avatar_name
|
||||
return Event(self.world.month_stamp, f"{self.avatar.name} 尝试从 {target_name} 逃离")
|
||||
|
||||
def step(self, avatar_name: str) -> tuple[StepStatus, list[Event]]:
|
||||
self.execute(avatar_name=avatar_name)
|
||||
return StepStatus.COMPLETED, []
|
||||
|
||||
def finish(self, avatar_name: str) -> list[Event]:
|
||||
return []
|
||||
|
||||
@long_action(step_month=10)
|
||||
class Cultivate(DefineAction, ActualActionMixin):
|
||||
"""
|
||||
@@ -686,11 +833,11 @@ class Battle(DefineAction, ActualActionMixin):
|
||||
return StepStatus.COMPLETED, []
|
||||
|
||||
def finish(self, avatar_name: str) -> list[Event]:
|
||||
res = getattr(self, "_last_result", None)
|
||||
res = self._last_result
|
||||
if isinstance(res, tuple) and len(res) == 2:
|
||||
winner, loser = res
|
||||
return [Event(self.world.month_stamp, f"{winner} 战胜了 {loser}")]
|
||||
return []
|
||||
raise ValueError(f"Battle finish error: {res}")
|
||||
|
||||
|
||||
@long_action(step_month=3)
|
||||
|
||||
@@ -20,10 +20,9 @@ from src.classes.action import (
|
||||
from src.classes.mutual_action import (
|
||||
DriveAway,
|
||||
Attack,
|
||||
MoveAwayFromAvatar,
|
||||
MoveAwayFromRegion,
|
||||
Conversation,
|
||||
)
|
||||
from src.classes.action import MoveAwayFromAvatar, MoveAwayFromRegion, Escape
|
||||
|
||||
|
||||
ALL_ACTION_CLASSES = [
|
||||
@@ -46,6 +45,9 @@ ALL_ACTION_CLASSES = [
|
||||
MoveAwayFromAvatar,
|
||||
MoveAwayFromRegion,
|
||||
Conversation,
|
||||
Escape,
|
||||
Conversation,
|
||||
Escape,
|
||||
]
|
||||
|
||||
ALL_ACTUAL_ACTION_CLASSES = [
|
||||
@@ -63,6 +65,8 @@ ALL_ACTUAL_ACTION_CLASSES = [
|
||||
DriveAway,
|
||||
Attack,
|
||||
Conversation,
|
||||
MoveAwayFromAvatar,
|
||||
MoveAwayFromRegion,
|
||||
]
|
||||
|
||||
ALL_ACTION_NAMES = [action.__name__ for action in ALL_ACTION_CLASSES]
|
||||
|
||||
@@ -179,8 +179,10 @@ class Avatar:
|
||||
"""
|
||||
if self.current_action is None:
|
||||
return []
|
||||
action = self.current_action.action
|
||||
params = self.current_action.params
|
||||
# 记录当前动作实例引用,用于检测执行过程中是否发生了“抢占/切换”
|
||||
action_instance_before = self.current_action
|
||||
action = action_instance_before.action
|
||||
params = action_instance_before.params
|
||||
try:
|
||||
status, mid_events = action.step(**params)
|
||||
except TypeError:
|
||||
@@ -190,7 +192,10 @@ class Avatar:
|
||||
finish_events = action.finish(**params)
|
||||
except TypeError:
|
||||
finish_events = action.finish()
|
||||
self.current_action = None
|
||||
# 仅当当前动作仍然是刚才执行的那个实例时才清空
|
||||
# 若在 step() 内部通过“抢占”机制切换了动作(如 Escape 失败立即切到 Battle),不要清空新动作
|
||||
if self.current_action is action_instance_before:
|
||||
self.current_action = None
|
||||
if finish_events:
|
||||
# 允许 finish 直接返回事件(极少用),统一并入 pending
|
||||
for e in finish_events:
|
||||
|
||||
@@ -54,7 +54,7 @@ def get_escape_success_rate(attacker: "Avatar", defender: "Avatar") -> float:
|
||||
attacker: 追击方(通常为进攻者)
|
||||
defender: 逃跑方(通常为被攻击者)
|
||||
"""
|
||||
return 0.6
|
||||
return 0.1
|
||||
|
||||
def get_damage(winner: "Avatar", loser: "Avatar") -> int:
|
||||
"""
|
||||
|
||||
@@ -131,6 +131,7 @@ class MutualAction(DefineAction, LLMAction):
|
||||
fb_map = {
|
||||
"MoveAwayFromAvatar": "试图远离",
|
||||
"MoveAwayFromRegion": "试图离开区域",
|
||||
"Escape": "逃离",
|
||||
"Battle": "战斗",
|
||||
}
|
||||
fb_label = fb_map.get(str(feedback).strip(), str(feedback))
|
||||
@@ -204,11 +205,11 @@ class Attack(MutualAction, ActualActionMixin):
|
||||
COMMENT = "对目标进行攻击。"
|
||||
DOABLES_REQUIREMENTS = "与目标处于同一区域"
|
||||
PARAMS = {"target_avatar": "AvatarName"}
|
||||
FEEDBACK_ACTIONS = ["MoveAwayFromAvatar", "Battle"]
|
||||
FEEDBACK_ACTIONS = ["Escape", "Battle"]
|
||||
|
||||
def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None:
|
||||
fb = str(feedback_name).strip()
|
||||
if fb == "MoveAwayFromAvatar":
|
||||
if fb == "Escape":
|
||||
params = {"avatar_name": self.avatar.name}
|
||||
self._set_target_immediate_action(target_avatar, fb, params)
|
||||
elif fb == "Battle":
|
||||
@@ -216,50 +217,7 @@ class Attack(MutualAction, ActualActionMixin):
|
||||
self._set_target_immediate_action(target_avatar, fb, params)
|
||||
|
||||
|
||||
# 轻量实现三个动作类,供互动动作反馈直接使用
|
||||
class MoveAwayFromAvatar(DefineAction, ActualActionMixin):
|
||||
COMMENT = "远离指定角色"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
def _execute(self, avatar_name: str) -> None:
|
||||
target = self._find_avatar_by_name(avatar_name)
|
||||
if target is None:
|
||||
return
|
||||
# 被攻击时逃跑的成功率:从 battle 模块获取(暂时固定值)
|
||||
escape_rate = float(get_escape_success_rate(target, self.avatar))
|
||||
if random.random() < escape_rate:
|
||||
dx = 1 if self.avatar.pos_x >= target.pos_x else -1
|
||||
dy = 1 if self.avatar.pos_y >= target.pos_y else -1
|
||||
nx = self.avatar.pos_x + dx
|
||||
ny = self.avatar.pos_y + dy
|
||||
if self.world.map.is_in_bounds(nx, ny):
|
||||
self.avatar.pos_x = nx
|
||||
self.avatar.pos_y = ny
|
||||
self.avatar.tile = self.world.map.get_tile(nx, ny)
|
||||
else:
|
||||
# 抢占:中断自身动作并清空队列后入队并提交
|
||||
self._preempt_avatar(self.avatar)
|
||||
self.avatar.load_decide_result_chain([("Battle", {"avatar_name": avatar_name})], self.avatar.thinking, "")
|
||||
start_event = self.avatar.commit_next_plan()
|
||||
if start_event is not None:
|
||||
# 仅在本方推送到侧边栏;对方仅写历史
|
||||
self._add_event_pair(start_event, initiator=self.avatar, target=target)
|
||||
|
||||
|
||||
class MoveAwayFromRegion(DefineAction, ActualActionMixin):
|
||||
COMMENT = "离开指定区域"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"region": "RegionName"}
|
||||
def _execute(self, region: str) -> None:
|
||||
# 驱赶离开:若选择离开,必定成功。简化为向地图边缘移动一步
|
||||
dx = 1 if self.avatar.pos_x < self.world.map.width - 1 else -1
|
||||
dy = 1 if self.avatar.pos_y < self.world.map.height - 1 else -1
|
||||
nx = max(0, min(self.world.map.width - 1, self.avatar.pos_x + dx))
|
||||
ny = max(0, min(self.world.map.height - 1, self.avatar.pos_y + dy))
|
||||
if self.world.map.is_in_bounds(nx, ny):
|
||||
self.avatar.pos_x = nx
|
||||
self.avatar.pos_y = ny
|
||||
self.avatar.tile = self.world.map.get_tile(nx, ny)
|
||||
|
||||
|
||||
|
||||
class Conversation(MutualAction, ActualActionMixin):
|
||||
|
||||
@@ -90,17 +90,17 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp
|
||||
avatar.alignment = random.choice(list(Alignment))
|
||||
avatars[avatar.id] = avatar
|
||||
# # —— 为演示添加少量示例关系 ——
|
||||
# avatar_list = list(avatars.values())
|
||||
# if len(avatar_list) >= 2:
|
||||
# avatar_list[0].set_relation(avatar_list[1], Relation.ENEMY)
|
||||
# if len(avatar_list) >= 4:
|
||||
# avatar_list[2].set_relation(avatar_list[3], Relation.FRIEND)
|
||||
# if len(avatar_list) >= 6:
|
||||
# # 师徒(有向):第5位是师傅,第6位是徒弟
|
||||
# avatar_list[4].set_relation(avatar_list[5], Relation.MASTER)
|
||||
# if len(avatar_list) >= 8:
|
||||
# # 情侣
|
||||
# avatar_list[6].set_relation(avatar_list[7], Relation.LOVERS)
|
||||
avatar_list = list(avatars.values())
|
||||
if len(avatar_list) >= 2:
|
||||
avatar_list[0].set_relation(avatar_list[1], Relation.ENEMY)
|
||||
if len(avatar_list) >= 4:
|
||||
avatar_list[2].set_relation(avatar_list[3], Relation.FRIEND)
|
||||
if len(avatar_list) >= 6:
|
||||
# 师徒(有向):第5位是师傅,第6位是徒弟
|
||||
avatar_list[4].set_relation(avatar_list[5], Relation.MASTER)
|
||||
if len(avatar_list) >= 8:
|
||||
# 情侣
|
||||
avatar_list[6].set_relation(avatar_list[7], Relation.LOVERS)
|
||||
return avatars
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ ai:
|
||||
max_decide_num: 3
|
||||
|
||||
game:
|
||||
init_npc_num: 6
|
||||
init_npc_num: 2
|
||||
npc_birth_rate_per_month: 0.001
|
||||
|
||||
df:
|
||||
@@ -23,4 +23,4 @@ avatar:
|
||||
persona_num: 1
|
||||
|
||||
social:
|
||||
talk_into_relation_probability: 0.9
|
||||
talk_into_relation_probability: 0.1
|
||||
@@ -1,3 +1,3 @@
|
||||
id,name,exclusion_ids,prompt
|
||||
,,和本persona互斥的persona的id,输入给LLM的prompt
|
||||
22,外向,13;14;21,你是一个外向的人,你乐于与人交流,主动结识伙伴,倾向接受对话和合作。
|
||||
12,复仇,11;14,你是一个复仇心强的人,你绝不轻易放下仇怨,为了复仇愿意付出代价与时间。
|
||||
|
Reference in New Issue
Block a user