kards-env/kards_battle/abilities/keywords.py

283 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
关键词系统
实现KARDS中的各种关键词效果
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from enum import Enum
if TYPE_CHECKING:
from ..units.unit import Unit
from ..battlefield.battlefield import Battlefield
class TriggerTiming(Enum):
"""触发时机枚举"""
ON_DEPLOY = "on_deploy" # 部署时
ON_ATTACK = "on_attack" # 攻击时
ON_DEFEND = "on_defend" # 防御时
ON_DAMAGE_TAKEN = "on_damage_taken" # 受到伤害时
ON_DEATH = "on_death" # 死亡时
ON_TURN_START = "on_turn_start" # 回合开始时
ON_TURN_END = "on_turn_end" # 回合结束时
PASSIVE = "passive" # 被动效果
BEFORE_COMBAT = "before_combat" # 战斗前
AFTER_COMBAT = "after_combat" # 战斗后
class KeywordEffect(ABC):
"""关键词效果基类"""
def __init__(self, name: str):
self.name = name
@abstractmethod
def get_trigger_timing(self) -> List[TriggerTiming]:
"""获取触发时机"""
pass
def can_trigger(self, timing: TriggerTiming, context: Dict[str, Any]) -> bool:
"""检查是否可以在指定时机触发"""
return timing in self.get_trigger_timing()
def apply_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
"""应用关键词效果"""
if self.can_trigger(timing, context):
self._execute_effect(owner, battlefield, timing, context)
@abstractmethod
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
"""执行具体效果(子类实现)"""
pass
def get_description(self) -> str:
"""获取关键词描述"""
return f"{self.name}: {self._get_description_text()}"
@abstractmethod
def _get_description_text(self) -> str:
"""获取描述文本(子类实现)"""
pass
class BlitzKeyword(KeywordEffect):
"""闪击关键词 - 单位可以在部署回合立即行动"""
def __init__(self):
super().__init__("BLITZ")
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.ON_DEPLOY]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_DEPLOY:
owner.can_operate_immediately = True
def _get_description_text(self) -> str:
return "单位可以在部署回合立即行动"
class GuardKeyword(KeywordEffect):
"""守护关键词 - 相邻单位只能被轰炸机和火炮攻击"""
def __init__(self):
super().__init__("GUARD")
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.PASSIVE]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
# 守护是被动效果,在攻击检查时处理
pass
def _get_description_text(self) -> str:
return "相邻单位只能被轰炸机和火炮攻击"
class SmokescreenKeyword(KeywordEffect):
"""烟幕关键词 - 单位不能被攻击,移动或攻击后失效"""
def __init__(self):
super().__init__("SMOKESCREEN")
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.PASSIVE, TriggerTiming.ON_ATTACK]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_ATTACK:
# 攻击后移除烟幕
owner.remove_keyword("SMOKESCREEN")
def _get_description_text(self) -> str:
return "单位不能被攻击,移动或攻击后失效"
class HeavyArmorKeyword(KeywordEffect):
"""重甲关键词 - 受到伤害减少指定数值"""
def __init__(self, armor_value: int = 1):
super().__init__(f"HEAVY_ARMOR_{armor_value}")
self.armor_value = armor_value
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.ON_DAMAGE_TAKEN]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_DAMAGE_TAKEN:
damage = context.get('damage', 0)
reduced_damage = max(0, damage - self.armor_value)
context['damage'] = reduced_damage
def _get_description_text(self) -> str:
return f"受到伤害减少{self.armor_value}"
class AmbushKeyword(KeywordEffect):
"""伏击关键词 - 每回合首次被攻击时先造成伤害"""
def __init__(self):
super().__init__("AMBUSH")
self.has_ambushed_this_turn = False
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.ON_DEFEND, TriggerTiming.ON_TURN_START]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_TURN_START:
self.has_ambushed_this_turn = False
elif timing == TriggerTiming.ON_DEFEND and not self.has_ambushed_this_turn:
attacker = context.get('attacker')
if attacker:
# 先造成伤害
damage = owner.get_effective_attack()
actual_damage = attacker.stats.take_damage(damage)
self.has_ambushed_this_turn = True
# 如果摧毁了攻击者,则自己不受伤害
if not attacker.stats.is_alive():
context['skip_damage'] = True
def _get_description_text(self) -> str:
return "每回合首次被攻击时先造成伤害,若摧毁攻击者则不受伤害"
class FuryKeyword(KeywordEffect):
"""奋战关键词 - 每回合可以攻击两次"""
def __init__(self):
super().__init__("FURY")
self.attacks_this_turn = 0
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.PASSIVE, TriggerTiming.ON_TURN_START, TriggerTiming.ON_ATTACK]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_TURN_START:
self.attacks_this_turn = 0
elif timing == TriggerTiming.ON_ATTACK:
self.attacks_this_turn += 1
def can_attack_again(self) -> bool:
"""检查是否还能再次攻击"""
return self.attacks_this_turn < 2
def _get_description_text(self) -> str:
return "每回合可以攻击两次"
class MobilizeKeyword(KeywordEffect):
"""动员关键词 - 回合开始时+1/+1受伤后失效"""
def __init__(self):
super().__init__("MOBILIZE")
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.ON_TURN_START, TriggerTiming.ON_DAMAGE_TAKEN]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_TURN_START:
# 增加1点攻击和1点防御
owner.stats.attack += 1
owner.stats.defense += 1
owner.stats.current_defense += 1
elif timing == TriggerTiming.ON_DAMAGE_TAKEN:
# 受伤后失去动员
owner.remove_keyword("MOBILIZE")
def _get_description_text(self) -> str:
return "回合开始时+1/+1受伤后失效"
class PinnedKeyword(KeywordEffect):
"""压制关键词 - 单位无法移动或攻击直到下回合结束"""
def __init__(self, duration: int = 1):
super().__init__("PINNED")
self.duration = duration
def get_trigger_timing(self) -> List[TriggerTiming]:
return [TriggerTiming.PASSIVE, TriggerTiming.ON_TURN_END]
def _execute_effect(self, owner: 'Unit', battlefield: 'Battlefield',
timing: TriggerTiming, context: Dict[str, Any]) -> None:
if timing == TriggerTiming.ON_TURN_END:
self.duration -= 1
if self.duration <= 0:
owner.remove_keyword("PINNED")
def _get_description_text(self) -> str:
return f"无法移动或攻击{self.duration}回合"
class KeywordRegistry:
"""关键词注册表"""
_keywords: Dict[str, type] = {
"BLITZ": BlitzKeyword,
"GUARD": GuardKeyword,
"SMOKESCREEN": SmokescreenKeyword,
"HEAVY_ARMOR": HeavyArmorKeyword,
"AMBUSH": AmbushKeyword,
"FURY": FuryKeyword,
"MOBILIZE": MobilizeKeyword,
"PINNED": PinnedKeyword,
}
@classmethod
def create_keyword(cls, keyword_name: str, **kwargs) -> Optional[KeywordEffect]:
"""创建关键词实例"""
if keyword_name.startswith("HEAVY_ARMOR_"):
# 处理带数值的重甲关键词
parts = keyword_name.split("_")
if len(parts) == 3 and parts[2].isdigit():
return HeavyArmorKeyword(int(parts[2]))
keyword_class = cls._keywords.get(keyword_name)
if keyword_class:
return keyword_class(**kwargs)
return None
@classmethod
def get_all_keywords(cls) -> List[str]:
"""获取所有注册的关键词名称"""
return list(cls._keywords.keys())
@classmethod
def register_keyword(cls, name: str, keyword_class: type) -> None:
"""注册新的关键词"""
cls._keywords[name] = keyword_class
@classmethod
def get_keyword_description(cls, keyword_name: str) -> Optional[str]:
"""获取关键词描述"""
keyword = cls.create_keyword(keyword_name)
return keyword.get_description() if keyword else None