refactor mutual action

This commit is contained in:
bridge
2025-10-02 17:31:25 +08:00
parent 281c21b3a4
commit 2d97edca8f
6 changed files with 163 additions and 30 deletions

View File

@@ -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 []

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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实例传入年龄与当前境界

View File

@@ -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