kards-env/kards_battle/abilities/abilities.py

363 lines
14 KiB
Python

"""
能力系统
实现KARDS中的各种单位能力
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from enum import Enum
from .keywords import TriggerTiming
if TYPE_CHECKING:
from ..units.unit import Unit
from ..battlefield.battlefield import Battlefield
class AbilityType(Enum):
"""能力类型枚举"""
DEPLOY = "deploy" # 部署能力
TRIGGERED = "triggered" # 触发能力
STATIC = "static" # 静态能力(光环)
ACTIVATED = "activated" # 激活能力
DEATH = "death" # 死亡能力
class TargetType(Enum):
"""目标类型枚举"""
SELF = "self" # 自己
FRIENDLY_UNIT = "friendly_unit" # 友方单位
ENEMY_UNIT = "enemy_unit" # 敌方单位
ANY_UNIT = "any_unit" # 任意单位
FRIENDLY_HQ = "friendly_hq" # 友方HQ
ENEMY_HQ = "enemy_hq" # 敌方HQ
ALL_FRIENDLY = "all_friendly" # 所有友方单位
ALL_ENEMY = "all_enemy" # 所有敌方单位
ADJACENT_FRIENDLY = "adjacent_friendly" # 相邻友方单位
NO_TARGET = "no_target" # 无目标
class Ability(ABC):
"""能力基类"""
def __init__(self, name: str, ability_type: AbilityType, description: str = ""):
self.name = name
self.ability_type = ability_type
self.description = description
self.is_used = False # 是否已使用(某些能力只能使用一次)
@abstractmethod
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
"""检查能力是否可以激活"""
pass
@abstractmethod
def get_valid_targets(self, owner: 'Unit', battlefield: 'Battlefield') -> List[Any]:
"""获取有效目标列表"""
pass
@abstractmethod
def execute(self, owner: 'Unit', battlefield: 'Battlefield',
target: Any = None, context: Dict[str, Any] = None) -> Dict[str, Any]:
"""执行能力,返回执行结果"""
pass
def get_trigger_timings(self) -> List[TriggerTiming]:
"""获取能力触发时机"""
timing_map = {
AbilityType.DEPLOY: [TriggerTiming.ON_DEPLOY],
AbilityType.TRIGGERED: [], # 子类指定
AbilityType.STATIC: [TriggerTiming.PASSIVE],
AbilityType.ACTIVATED: [], # 主动激活,无固定时机
AbilityType.DEATH: [TriggerTiming.ON_DEATH]
}
return timing_map.get(self.ability_type, [])
def reset(self) -> None:
"""重置能力状态"""
self.is_used = False
def __str__(self) -> str:
return f"{self.name} ({self.ability_type.value}): {self.description}"
class DeployAbility(Ability):
"""部署能力基类"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, AbilityType.DEPLOY, description)
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
return timing == TriggerTiming.ON_DEPLOY and not self.is_used
class TriggeredAbility(Ability):
"""触发能力基类"""
def __init__(self, name: str, trigger_timing: TriggerTiming,
trigger_condition: callable = None, description: str = ""):
super().__init__(name, AbilityType.TRIGGERED, description)
self.trigger_timing = trigger_timing
self.trigger_condition = trigger_condition or (lambda *args: True)
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
return (timing == self.trigger_timing and
self.trigger_condition(owner, battlefield, context))
def get_trigger_timings(self) -> List[TriggerTiming]:
return [self.trigger_timing]
class StaticAbility(Ability):
"""静态能力(光环效果)基类"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, AbilityType.STATIC, description)
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
return timing == TriggerTiming.PASSIVE
@abstractmethod
def apply_static_effect(self, owner: 'Unit', battlefield: 'Battlefield') -> None:
"""应用静态效果"""
pass
def execute(self, owner: 'Unit', battlefield: 'Battlefield',
target: Any = None, context: Dict[str, Any] = None) -> Dict[str, Any]:
self.apply_static_effect(owner, battlefield)
return {"success": True, "effect_type": "static"}
class ActivatedAbility(Ability):
"""激活能力(主动使用)基类"""
def __init__(self, name: str, cost: int = 0, uses_per_turn: int = 1, description: str = ""):
super().__init__(name, AbilityType.ACTIVATED, description)
self.cost = cost # 使用成本(如果有)
self.uses_per_turn = uses_per_turn
self.uses_this_turn = 0
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
return self.uses_this_turn < self.uses_per_turn
def reset_turn(self) -> None:
"""回合重置"""
self.uses_this_turn = 0
class DeathAbility(Ability):
"""死亡能力基类"""
def __init__(self, name: str, description: str = ""):
super().__init__(name, AbilityType.DEATH, description)
def can_activate(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> bool:
return timing == TriggerTiming.ON_DEATH and not self.is_used
# 具体能力实现示例
class DealDamageOnDeploy(DeployAbility):
"""部署时造成伤害"""
def __init__(self, damage: int, target_type: TargetType = TargetType.ENEMY_UNIT):
super().__init__("DEPLOY_DAMAGE", f"部署时对{target_type.value}造成{damage}点伤害")
self.damage = damage
self.target_type = target_type
def get_valid_targets(self, owner: 'Unit', battlefield: 'Battlefield') -> List['Unit']:
from ..effects.target_selectors import TargetSelector
selector = TargetSelector()
return selector.get_targets_by_type(owner, battlefield, self.target_type)
def execute(self, owner: 'Unit', battlefield: 'Battlefield',
target: 'Unit' = None, context: Dict[str, Any] = None) -> Dict[str, Any]:
if not target:
return {"success": False, "reason": "No target specified"}
actual_damage = target.stats.take_damage(self.damage)
self.is_used = True
result = {
"success": True,
"effect_type": "damage",
"target": target.id,
"damage_dealt": actual_damage,
"target_destroyed": not target.stats.is_alive()
}
# 如果目标被摧毁,从战场移除
if not target.stats.is_alive():
battlefield.remove_unit(target)
return result
class BuffAlliesStatic(StaticAbility):
"""静态增益友方单位"""
def __init__(self, attack_bonus: int = 0, defense_bonus: int = 0):
description = f"友方单位获得+{attack_bonus}/+{defense_bonus}"
super().__init__("BUFF_ALLIES", description)
self.attack_bonus = attack_bonus
self.defense_bonus = defense_bonus
def get_valid_targets(self, owner: 'Unit', battlefield: 'Battlefield') -> List['Unit']:
return battlefield.get_all_units(owner.owner)
def apply_static_effect(self, owner: 'Unit', battlefield: 'Battlefield') -> None:
# 静态效果在战斗计算时动态应用,这里仅做标记
pass
def get_buff_values(self, target: 'Unit', owner: 'Unit') -> tuple:
"""获取对目标的增益值"""
if target.owner == owner.owner and target.id != owner.id:
return (self.attack_bonus, self.defense_bonus)
return (0, 0)
class SacrificeForDamage(ActivatedAbility):
"""牺牲自己造成伤害"""
def __init__(self, damage: int):
super().__init__("SACRIFICE_DAMAGE", 0, 1, f"牺牲自己对敌方单位造成{damage}点伤害")
self.damage = damage
def get_valid_targets(self, owner: 'Unit', battlefield: 'Battlefield') -> List['Unit']:
enemy_id = battlefield.get_enemy_player_id(owner.owner)
return battlefield.get_all_units(enemy_id) if enemy_id else []
def execute(self, owner: 'Unit', battlefield: 'Battlefield',
target: 'Unit' = None, context: Dict[str, Any] = None) -> Dict[str, Any]:
if not target:
return {"success": False, "reason": "No target specified"}
# 造成伤害
actual_damage = target.stats.take_damage(self.damage)
# 牺牲自己
owner.stats.current_defense = 0
battlefield.remove_unit(owner)
self.uses_this_turn += 1
result = {
"success": True,
"effect_type": "sacrifice_damage",
"target": target.id,
"damage_dealt": actual_damage,
"target_destroyed": not target.stats.is_alive(),
"owner_sacrificed": True
}
# 如果目标也被摧毁,从战场移除
if not target.stats.is_alive():
battlefield.remove_unit(target)
return result
class HealOnDeath(DeathAbility):
"""死亡时治疗友方单位"""
def __init__(self, heal_amount: int):
super().__init__("DEATH_HEAL", f"死亡时治疗所有友方单位{heal_amount}")
self.heal_amount = heal_amount
def get_valid_targets(self, owner: 'Unit', battlefield: 'Battlefield') -> List['Unit']:
return battlefield.get_all_units(owner.owner)
def execute(self, owner: 'Unit', battlefield: 'Battlefield',
target: Any = None, context: Dict[str, Any] = None) -> Dict[str, Any]:
friendly_units = self.get_valid_targets(owner, battlefield)
healed_units = []
for unit in friendly_units:
if unit.id != owner.id: # 不治疗自己(已死亡)
heal_amount = unit.stats.heal(self.heal_amount)
if heal_amount > 0:
healed_units.append({
"unit_id": unit.id,
"heal_amount": heal_amount
})
self.is_used = True
return {
"success": True,
"effect_type": "death_heal",
"healed_units": healed_units
}
class AbilityManager:
"""能力管理器,负责处理能力的触发和执行"""
def __init__(self):
self.pending_abilities = [] # 待处理的能力
def trigger_abilities(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> List[Dict[str, Any]]:
"""触发单位的能力"""
results = []
for ability in owner.abilities:
if ability.can_activate(owner, battlefield, timing, context):
# 获取目标
valid_targets = ability.get_valid_targets(owner, battlefield)
if ability.ability_type == AbilityType.STATIC:
# 静态能力直接执行
result = ability.execute(owner, battlefield, context=context)
results.append(result)
elif valid_targets or ability.get_valid_targets(owner, battlefield) == []:
# 有有效目标或无需目标的能力
if len(valid_targets) == 1:
# 单一目标自动选择
target = valid_targets[0]
result = ability.execute(owner, battlefield, target, context)
results.append(result)
elif len(valid_targets) == 0 and ability.ability_type in [AbilityType.DEPLOY, AbilityType.DEATH]:
# 无目标能力(如群体效果)
result = ability.execute(owner, battlefield, context=context)
results.append(result)
else:
# 多目标需要外部选择,添加到待处理列表
self.pending_abilities.append({
"owner": owner,
"ability": ability,
"battlefield": battlefield,
"valid_targets": valid_targets,
"context": context
})
return results
def execute_pending_ability(self, ability_index: int, target_index: int) -> Optional[Dict[str, Any]]:
"""执行待处理的能力"""
if 0 <= ability_index < len(self.pending_abilities):
pending = self.pending_abilities[ability_index]
valid_targets = pending["valid_targets"]
if 0 <= target_index < len(valid_targets):
target = valid_targets[target_index]
result = pending["ability"].execute(
pending["owner"],
pending["battlefield"],
target,
pending["context"]
)
# 移除已处理的能力
self.pending_abilities.pop(ability_index)
return result
return None
def clear_pending(self) -> None:
"""清除所有待处理的能力"""
self.pending_abilities.clear()