refactor can_start logic of all actions

This commit is contained in:
bridge
2026-01-06 21:43:24 +08:00
parent b60481c99c
commit c266655af9
13 changed files with 14 additions and 40 deletions

View File

@@ -68,7 +68,7 @@ class Assassinate(InstantAction, TargetingMixin):
self._last_result = (winner, loser, loser_damage, winner_damage)
def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]:
def can_start(self, avatar_name: str) -> tuple[bool, str]:
# 注意cooldown_action 装饰器会覆盖这个方法并在调用此方法前检查 CD
_, ok, reason = self.validate_target_avatar(avatar_name)
return ok, reason

View File

@@ -41,7 +41,7 @@ class Attack(InstantAction, TargetingMixin):
self._last_result = (winner, loser, loser_damage, winner_damage)
def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]:
def can_start(self, avatar_name: str) -> tuple[bool, str]:
_, ok, reason = self.validate_target_avatar(avatar_name)
return ok, reason

View File

@@ -31,17 +31,11 @@ class Buy(InstantAction):
DOABLES_REQUIREMENTS = "在城镇且金钱足够"
PARAMS = {"target_name": "str"}
def can_start(self, target_name: str | None = None) -> tuple[bool, str]:
def can_start(self, target_name: str) -> tuple[bool, str]:
region = self.avatar.tile.region
if not isinstance(region, CityRegion):
return False, "仅能在城市区域执行"
if target_name is None:
# 用于动作空间检查
# 理论上只要有钱就可以买东西,这里简单判定金钱>0
ok = self.avatar.magic_stone > 0
return (ok, "" if ok else "身无分文")
obj, obj_type, display_name = resolve_goods_by_name(target_name)
if obj_type == "unknown":
return False, f"未知物品: {target_name}"

View File

@@ -50,7 +50,7 @@ class Cast(TimedAction):
count += qty
return count
def can_start(self, target_realm: str = "") -> tuple[bool, str]:
def can_start(self, target_realm: str) -> tuple[bool, str]:
if not target_realm:
return False, "未指定目标境界"
@@ -67,7 +67,7 @@ class Cast(TimedAction):
return True, ""
def start(self, target_realm: str = "") -> Event:
def start(self, target_realm: str) -> Event:
self.target_realm = Realm(target_realm)
cost = self._get_cost()

View File

@@ -64,7 +64,7 @@ class Escape(InstantAction):
if start_event is not None:
EventHelper.push_pair(start_event, initiator=self.avatar, target=target, to_sidebar_once=True)
def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]:
def can_start(self, avatar_name: str) -> tuple[bool, str]:
return True, ""
def start(self, avatar_name: str) -> Event:

View File

@@ -47,7 +47,7 @@ class MoveAwayFromAvatar(TimedAction):
dx, dy = clamp_manhattan_with_diagonal_priority(raw_dx, raw_dy, step)
Move(self.avatar, self.world).execute(dx, dy)
def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]:
def can_start(self, avatar_name: str) -> tuple[bool, str]:
return True, ""
def start(self, avatar_name: str) -> Event:

View File

@@ -34,9 +34,7 @@ class MoveAwayFromRegion(InstantAction):
dx, dy = clamp_manhattan_with_diagonal_priority(away_dx, away_dy, step)
Move(self.avatar, self.world).execute(dx, dy)
def can_start(self, region: str | None = None) -> tuple[bool, str]:
if region is None:
return True, ""
def can_start(self, region: str) -> tuple[bool, str]:
try:
resolve_region(self.world, region)
return True, ""

View File

@@ -42,7 +42,7 @@ class MoveToAvatar(DefineAction, ActualActionMixin):
dx, dy = clamp_manhattan_with_diagonal_priority(raw_dx, raw_dy, step)
Move(self.avatar, self.world).execute(dx, dy)
def can_start(self, avatar_name: str | None = None) -> tuple[bool, str]:
def can_start(self, avatar_name: str) -> tuple[bool, str]:
return True, ""
def start(self, avatar_name: str) -> Event:

View File

@@ -73,9 +73,7 @@ class MoveToDirection(DefineAction, ActualActionMixin):
self.start_monthstamp = None
self.direction = None
def can_start(self, direction: str | None = None) -> tuple[bool, str]:
if not direction:
return False, "缺少方向参数"
def can_start(self, direction: str) -> tuple[bool, str]:
if not Direction.is_valid(direction):
return False, f"无效的方向: {direction}"
return True, ""

View File

@@ -57,9 +57,7 @@ class MoveToRegion(DefineAction, ActualActionMixin):
dx, dy = clamp_manhattan_with_diagonal_priority(raw_dx, raw_dy, step)
Move(self.avatar, self.world).execute(dx, dy)
def can_start(self, region: Region | str | None = None) -> tuple[bool, str]:
if region is None:
return False, "缺少参数 region"
def can_start(self, region: Region | str) -> tuple[bool, str]:
try:
r = resolve_region(self.world, region)

View File

@@ -23,18 +23,10 @@ class Sell(InstantAction):
DOABLES_REQUIREMENTS = "在城镇且持有可出售物品/装备"
PARAMS = {"target_name": "str"}
def can_start(self, target_name: str | None = None) -> tuple[bool, str]:
def can_start(self, target_name: str) -> tuple[bool, str]:
region = self.avatar.tile.region
if not isinstance(region, CityRegion):
return False, "仅能在城市区域执行"
if target_name is None:
# 用于动作空间:只要有任何可卖东西即可
has_items = bool(self.avatar.items)
has_weapon = self.avatar.weapon is not None
has_auxiliary = self.avatar.auxiliary is not None
ok = has_items or has_weapon or has_auxiliary
return (ok, "" if ok else "背包为空且无装备,无可出售物品")
# 使用通用解析逻辑获取物品原型和类型
obj, obj_type, _ = resolve_goods_by_name(target_name)

View File

@@ -47,11 +47,7 @@ class SwitchWeapon(InstantAction):
# 切换兵器(使用 Avatar 的 change_weapon 方法)
self.avatar.change_weapon(common_weapon)
def can_start(self, weapon_type_name: str | None = None) -> tuple[bool, str]:
if weapon_type_name is None:
# AI调用总是可以切换兵器
return True, ""
def can_start(self, weapon_type_name: str) -> tuple[bool, str]:
# 处理卸下兵器的情况
if weapon_type_name in ["", "None", "none", ""]:
if self.avatar.weapon is None:

View File

@@ -136,7 +136,7 @@ class MutualAction(DefineAction, LLMAction, ActualActionMixin, TargetingMixin):
pass
# 实现 ActualActionMixin 接口
def can_start(self, target_avatar: "Avatar|str|None" = None) -> tuple[bool, str]:
def can_start(self, target_avatar: "Avatar|str") -> tuple[bool, str]:
"""
检查互动动作能否启动:目标需在发起者的交互范围内。
子类通过实现 _can_start 来添加额外检查。
@@ -144,8 +144,6 @@ class MutualAction(DefineAction, LLMAction, ActualActionMixin, TargetingMixin):
注意:此方法未使用 TargetingMixin.validate_target_avatar()
因为需要额外检查 target == self.avatar 和调用子类的 _can_start()。
"""
if target_avatar is None:
return False, "缺少参数 target_avatar"
target = self._get_target_avatar(target_avatar)
if target is None:
return False, "目标不存在"