refactor mutual action
This commit is contained in:
@@ -650,20 +650,42 @@ class Sold(DefineAction, ActualActionMixin):
|
||||
return []
|
||||
|
||||
|
||||
class Battle(DefineAction, ChunkActionMixin):
|
||||
class Battle(DefineAction, ActualActionMixin):
|
||||
COMMENT = "与目标进行对战,判定胜负"
|
||||
DOABLES_REQUIREMENTS = "任何时候都可以执行"
|
||||
PARAMS = {"avatar_name": "AvatarName"}
|
||||
def _execute(self, avatar_name: str) -> None:
|
||||
target = None
|
||||
|
||||
def _get_target(self, avatar_name: str):
|
||||
for v in self.world.avatar_manager.avatars.values():
|
||||
if v.name == avatar_name:
|
||||
target = v
|
||||
break
|
||||
return v
|
||||
return None
|
||||
|
||||
def _execute(self, avatar_name: str) -> None:
|
||||
target = self._get_target(avatar_name)
|
||||
if target is None:
|
||||
return
|
||||
winner, loser, _ = decide_battle(self.avatar, target)
|
||||
# 简化:失败者HP小额扣减
|
||||
if hasattr(loser, "hp"):
|
||||
loser.hp.reduce(10)
|
||||
# Battle 作为 ChunkAction,不直接由调度器执行
|
||||
winner, loser, damage = decide_battle(self.avatar, target)
|
||||
loser.hp.reduce(damage)
|
||||
self._last_result = (winner.name, loser.name)
|
||||
|
||||
def can_start(self, avatar_name: str | None = None) -> bool:
|
||||
if avatar_name is None:
|
||||
return False
|
||||
return self._get_target(avatar_name) is not None
|
||||
|
||||
def start(self, avatar_name: str) -> Event:
|
||||
target = self._get_target(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]:
|
||||
res = getattr(self, "_last_result", None)
|
||||
if isinstance(res, tuple) and len(res) == 2:
|
||||
winner, loser = res
|
||||
return [Event(self.world.month_stamp, f"{winner} 战胜了 {loser}")]
|
||||
return []
|
||||
|
||||
@@ -34,14 +34,31 @@ def calc_win_rate(attacker: "Avatar", defender: "Avatar") -> float:
|
||||
return max(0.1, min(0.9, base))
|
||||
|
||||
|
||||
def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", float]:
|
||||
def decide_battle(attacker: "Avatar", defender: "Avatar") -> Tuple["Avatar", "Avatar", int]:
|
||||
"""
|
||||
结算一场战斗,返回(胜者, 败者, 进攻方胜率)。
|
||||
仅做结果判定,不做数值伤害结算。
|
||||
结算一场战斗,返回(胜者, 败者, 伤害值)。
|
||||
伤害值根据胜负双方境界差距给出,范围约 [30, 80]。
|
||||
"""
|
||||
p = calc_win_rate(attacker, defender)
|
||||
if random.random() < p:
|
||||
return attacker, defender, p
|
||||
winner, loser = attacker, defender
|
||||
else:
|
||||
return defender, attacker, p
|
||||
winner, loser = defender, attacker
|
||||
|
||||
damage = get_damage(winner, loser)
|
||||
return winner, loser, damage
|
||||
|
||||
def get_escape_success_rate(attacker: "Avatar", defender: "Avatar") -> float:
|
||||
"""
|
||||
逃跑成功率:临时返回常量值,后续可基于双方能力细化。
|
||||
attacker: 追击方(通常为进攻者)
|
||||
defender: 逃跑方(通常为被攻击者)
|
||||
"""
|
||||
return 0.6
|
||||
|
||||
def get_damage(winner: "Avatar", loser: "Avatar") -> int:
|
||||
"""
|
||||
根据胜负双方境界差距估算伤害:基础30,差一大境界+10,上限80。
|
||||
"""
|
||||
gap = max(0, _realm_order(winner.cultivation_progress.realm) - _realm_order(loser.cultivation_progress.realm))
|
||||
return min(80, 30 + 10 * gap)
|
||||
@@ -35,6 +35,37 @@ class HP:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
||||
# 比较运算符,使用cur进行比较
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur == other.cur
|
||||
return self.cur == other
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur != other.cur
|
||||
return self.cur != other
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur < other.cur
|
||||
return self.cur < other
|
||||
|
||||
def __le__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur <= other.cur
|
||||
return self.cur <= other
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur > other.cur
|
||||
return self.cur > other
|
||||
|
||||
def __ge__(self, other) -> bool:
|
||||
if isinstance(other, HP):
|
||||
return self.cur >= other.cur
|
||||
return self.cur >= other
|
||||
|
||||
HP_MAX_BY_REALM = {
|
||||
Realm.Qi_Refinement: 100,
|
||||
@@ -75,6 +106,37 @@ class MP:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
|
||||
# 比较运算符,使用cur进行比较
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur == other.cur
|
||||
return self.cur == other
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur != other.cur
|
||||
return self.cur != other
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur < other.cur
|
||||
return self.cur < other
|
||||
|
||||
def __le__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur <= other.cur
|
||||
return self.cur <= other
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur > other.cur
|
||||
return self.cur > other
|
||||
|
||||
def __ge__(self, other) -> bool:
|
||||
if isinstance(other, MP):
|
||||
return self.cur >= other.cur
|
||||
return self.cur >= other
|
||||
|
||||
def add_max(self, value_2_add:int) -> bool:
|
||||
self.max += value_2_add
|
||||
|
||||
@@ -4,6 +4,8 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from src.classes.action import DefineAction, ActualActionMixin, LLMAction
|
||||
from src.classes.battle import get_escape_success_rate
|
||||
import random
|
||||
from src.classes.event import Event
|
||||
from src.utils.llm import get_prompt_and_call_llm
|
||||
from src.utils.config import CONFIG
|
||||
@@ -107,8 +109,14 @@ class MutualAction(DefineAction, LLMAction):
|
||||
target_avatar.clear_plans()
|
||||
# 2) 再结算反馈映射为对应动作
|
||||
self._settle_feedback(target_avatar, feedback)
|
||||
# 3) 反馈事件(进入侧边栏与双方历史)
|
||||
feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{feedback}")
|
||||
# 3) 反馈事件(进入侧边栏与双方历史,中文化文案)
|
||||
fb_map = {
|
||||
"MoveAwayFromAvatar": "试图远离",
|
||||
"MoveAwayFromRegion": "试图离开区域",
|
||||
"Battle": "战斗",
|
||||
}
|
||||
fb_label = fb_map.get(str(feedback).strip(), str(feedback))
|
||||
feedback_event = Event(self.world.month_stamp, f"{target_avatar.name} 对 {self.avatar.name} 的反馈:{fb_label}")
|
||||
self.avatar.add_event(feedback_event)
|
||||
target_avatar.add_event(feedback_event)
|
||||
# 4) 记录历史(文本记录)
|
||||
@@ -129,6 +137,7 @@ class DriveAway(MutualAction, ActualActionMixin):
|
||||
def _settle_feedback(self, target_avatar: "Avatar", feedback_name: str) -> None:
|
||||
fb = str(feedback_name).strip()
|
||||
if fb == "MoveAwayFromRegion":
|
||||
# 驱赶选择离开:必定成功,不涉及概率
|
||||
params = {"region": self.avatar.tile.region.name}
|
||||
self._set_target_immediate_action(target_avatar, fb, params)
|
||||
elif fb == "Battle":
|
||||
@@ -166,14 +175,28 @@ class MoveAwayFromAvatar(DefineAction, ActualActionMixin):
|
||||
break
|
||||
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)
|
||||
# 被攻击时逃跑的成功率:从 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.avatar.clear_plans()
|
||||
# 入队并提交
|
||||
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.avatar.add_event(start_event)
|
||||
# 也同步给对方
|
||||
if target is not None:
|
||||
target.add_event(start_event)
|
||||
|
||||
|
||||
class MoveAwayFromRegion(DefineAction, ActualActionMixin):
|
||||
@@ -181,7 +204,7 @@ class MoveAwayFromRegion(DefineAction, ActualActionMixin):
|
||||
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))
|
||||
|
||||
@@ -56,8 +56,7 @@ def make_avatars(world: World, count: int = 12, current_month_stamp: MonthStamp
|
||||
name = get_random_name(gender)
|
||||
|
||||
# 随机生成level,范围从0到120(对应四个大境界)
|
||||
# level = random.randint(0, 120)
|
||||
level = 29
|
||||
level = random.randint(0, 120)
|
||||
cultivation_progress = CultivationProgress(level)
|
||||
|
||||
# 创建Age实例,传入年龄与当前境界
|
||||
|
||||
@@ -9,6 +9,7 @@ from src.classes.event import Event, is_null_event
|
||||
from src.classes.ai import llm_ai, rule_ai
|
||||
from src.utils.names import get_random_name
|
||||
from src.utils.config import CONFIG
|
||||
from src.run.log import get_logger
|
||||
|
||||
class Simulator:
|
||||
def __init__(self, world: World):
|
||||
@@ -55,8 +56,12 @@ class Simulator:
|
||||
if new_events:
|
||||
events.extend(new_events)
|
||||
|
||||
# 结算寿命逻辑
|
||||
for avatar_id, avatar in self.world.avatar_manager.avatars.items():
|
||||
# 结算战斗等导致的死亡(HP<=0)与寿命逻辑
|
||||
for avatar_id, avatar in list(self.world.avatar_manager.avatars.items()):
|
||||
if avatar.hp <= 0:
|
||||
death_avatar_ids.append(avatar_id)
|
||||
event = Event(self.world.month_stamp, f"{avatar.name} 因重伤身亡")
|
||||
events.append(event)
|
||||
if avatar.death_by_old_age():
|
||||
death_avatar_ids.append(avatar_id)
|
||||
event = Event(self.world.month_stamp, f"{avatar.name} 老死了,时年{avatar.age.get_age()}岁")
|
||||
@@ -77,6 +82,11 @@ class Simulator:
|
||||
event = Event(self.world.month_stamp, f"{new_avatar.name}晋升为修士了。")
|
||||
events.append(event)
|
||||
|
||||
# 将事件写入日志
|
||||
logger = get_logger().logger
|
||||
for event in events:
|
||||
logger.info("EVENT: %s", str(event))
|
||||
|
||||
# 最后结算年月
|
||||
self.world.month_stamp = self.world.month_stamp + 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user