299 lines
11 KiB
Python
299 lines
11 KiB
Python
|
|
"""
|
|||
|
|
Unit基类定义
|
|||
|
|
KARDS游戏中的单位基础实现
|
|||
|
|
"""
|
|||
|
|
from typing import Set, List, Optional, Dict, Any
|
|||
|
|
from dataclasses import dataclass, field
|
|||
|
|
from uuid import uuid4, UUID
|
|||
|
|
|
|||
|
|
from ..core.enums import UnitType, LineType
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class UnitStats:
|
|||
|
|
"""单位属性数据类"""
|
|||
|
|
attack: int
|
|||
|
|
defense: int
|
|||
|
|
current_defense: int
|
|||
|
|
operation_cost: int
|
|||
|
|
|
|||
|
|
def __post_init__(self):
|
|||
|
|
if self.current_defense > self.defense:
|
|||
|
|
self.current_defense = self.defense
|
|||
|
|
|
|||
|
|
def take_damage(self, damage: int) -> int:
|
|||
|
|
"""受到伤害,返回实际受到的伤害"""
|
|||
|
|
if damage <= 0:
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
# 记录伤害前的血量
|
|||
|
|
old_defense = self.current_defense
|
|||
|
|
|
|||
|
|
# 直接扣除伤害,血量可以降到0或更低
|
|||
|
|
self.current_defense -= damage
|
|||
|
|
|
|||
|
|
# 但不能低于0
|
|||
|
|
if self.current_defense < 0:
|
|||
|
|
self.current_defense = 0
|
|||
|
|
|
|||
|
|
# 返回实际造成的伤害
|
|||
|
|
return old_defense - self.current_defense
|
|||
|
|
|
|||
|
|
def heal(self, amount: int) -> int:
|
|||
|
|
"""恢复防御值,返回实际恢复的量"""
|
|||
|
|
max_heal = self.defense - self.current_defense
|
|||
|
|
actual_heal = min(amount, max_heal)
|
|||
|
|
self.current_defense += actual_heal
|
|||
|
|
return actual_heal
|
|||
|
|
|
|||
|
|
def is_alive(self) -> bool:
|
|||
|
|
"""判断单位是否存活"""
|
|||
|
|
return self.current_defense > 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Unit:
|
|||
|
|
"""KARDS单位基类"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
name: str,
|
|||
|
|
unit_type: UnitType,
|
|||
|
|
attack: int,
|
|||
|
|
defense: int,
|
|||
|
|
operation_cost: int,
|
|||
|
|
keywords: Optional[Set[str]] = None,
|
|||
|
|
abilities: Optional[List] = None
|
|||
|
|
):
|
|||
|
|
self.id: UUID = uuid4()
|
|||
|
|
self.name = name
|
|||
|
|
self.unit_type = unit_type
|
|||
|
|
self.stats = UnitStats(attack, defense, defense, operation_cost)
|
|||
|
|
|
|||
|
|
# 关键词集合
|
|||
|
|
self.keywords: Set[str] = keywords or set()
|
|||
|
|
|
|||
|
|
# 能力列表(稍后实现具体的Ability类)
|
|||
|
|
self.abilities: List = abilities or []
|
|||
|
|
|
|||
|
|
# 单位状态
|
|||
|
|
self.position: Optional[tuple] = None # (line_type, index)
|
|||
|
|
self.owner: Optional[str] = None # 玩家ID
|
|||
|
|
|
|||
|
|
# 回合制行动限制
|
|||
|
|
self.deployed_this_turn: bool = True # 刚部署的单位当回合不能行动
|
|||
|
|
self.has_moved_this_turn: bool = False # 本回合是否已移动
|
|||
|
|
self.has_attacked_this_turn: bool = False # 本回合是否已攻击
|
|||
|
|
self.attacks_this_turn: int = 0 # 本回合攻击次数
|
|||
|
|
self.max_attacks_per_turn: int = 1 # 每回合最大攻击次数
|
|||
|
|
|
|||
|
|
# 特殊能力标记
|
|||
|
|
self.can_operate_immediately: bool = False # Blitz关键词 - 部署当回合就能行动
|
|||
|
|
self.can_move_and_attack: bool = False # Tank关键词 - 可以移动后攻击
|
|||
|
|
self.can_attack_multiple_times: bool = False # Fury等关键词
|
|||
|
|
|
|||
|
|
# YAML加载的额外属性
|
|||
|
|
self.nation: Optional[str] = None # 单位所属国家
|
|||
|
|
self.definition_id: Optional[str] = None # YAML定义中的ID
|
|||
|
|
|
|||
|
|
# 临时效果和修正器
|
|||
|
|
self.temporary_modifiers: Dict[str, Any] = {}
|
|||
|
|
self.status_effects: Set[str] = set() # 如"Pinned"等状态
|
|||
|
|
|
|||
|
|
# 应用关键词能力
|
|||
|
|
self.apply_keyword_abilities()
|
|||
|
|
|
|||
|
|
def has_keyword(self, keyword: str) -> bool:
|
|||
|
|
"""检查单位是否具有特定关键词"""
|
|||
|
|
return keyword in self.keywords
|
|||
|
|
|
|||
|
|
def add_keyword(self, keyword: str) -> None:
|
|||
|
|
"""添加关键词"""
|
|||
|
|
self.keywords.add(keyword)
|
|||
|
|
|
|||
|
|
# 添加守护关键词时移除烟幕
|
|||
|
|
if keyword == "GUARD" and self.has_keyword("SMOKESCREEN"):
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
|
|||
|
|
def remove_keyword(self, keyword: str) -> None:
|
|||
|
|
"""移除关键词"""
|
|||
|
|
self.keywords.discard(keyword)
|
|||
|
|
|
|||
|
|
def check_smokescreen_conditions(self) -> None:
|
|||
|
|
"""检查并移除不符合条件的烟幕"""
|
|||
|
|
if not self.has_keyword("SMOKESCREEN"):
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 检查是否具有守护关键词
|
|||
|
|
if self.has_keyword("GUARD"):
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 检查是否在前线(前线单位自动失去烟幕)
|
|||
|
|
if self.position and self.position[0] == LineType.FRONT:
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 检查是否不在己方支援线
|
|||
|
|
if self.position and self.owner:
|
|||
|
|
current_line = self.position[0]
|
|||
|
|
# 如果不在支援线,失去烟幕
|
|||
|
|
if current_line != LineType.PLAYER1_SUPPORT and current_line != LineType.PLAYER2_SUPPORT:
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
def get_effective_attack(self) -> int:
|
|||
|
|
"""获取有效攻击力(包含修正器)"""
|
|||
|
|
base_attack = self.stats.attack
|
|||
|
|
# TODO: 应用临时修正器
|
|||
|
|
return base_attack
|
|||
|
|
|
|||
|
|
def get_effective_defense(self) -> int:
|
|||
|
|
"""获取有效防御力上限(包含修正器)"""
|
|||
|
|
base_defense = self.stats.defense
|
|||
|
|
# TODO: 应用临时修正器
|
|||
|
|
return base_defense
|
|||
|
|
|
|||
|
|
def can_move(self) -> bool:
|
|||
|
|
"""检查单位是否可以移动"""
|
|||
|
|
# 刚部署的单位不能移动(除非有Blitz)
|
|||
|
|
if self.deployed_this_turn and not self.can_operate_immediately:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 已经移动过的单位不能再移动
|
|||
|
|
if self.has_moved_this_turn:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 已经攻击过的单位不能移动(除非有Tank能力)
|
|||
|
|
if self.has_attacked_this_turn and not self.can_move_and_attack:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def can_attack(self) -> bool:
|
|||
|
|
"""检查单位是否可以攻击"""
|
|||
|
|
# 被压制的单位不能主动攻击
|
|||
|
|
if self.has_keyword("PINNED"):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 刚部署的单位不能攻击(除非有Blitz)
|
|||
|
|
if self.deployed_this_turn and not self.can_operate_immediately:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 已经达到最大攻击次数
|
|||
|
|
if self.attacks_this_turn >= self.max_attacks_per_turn:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 已经移动过的单位不能攻击(除非有Tank能力)
|
|||
|
|
if self.has_moved_this_turn and not self.can_move_and_attack:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def perform_move(self):
|
|||
|
|
"""执行移动行动"""
|
|||
|
|
self.has_moved_this_turn = True
|
|||
|
|
|
|||
|
|
# 移动后失去烟幕
|
|||
|
|
if self.has_keyword("SMOKESCREEN"):
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
|
|||
|
|
def perform_attack(self):
|
|||
|
|
"""执行攻击行动"""
|
|||
|
|
self.has_attacked_this_turn = True
|
|||
|
|
self.attacks_this_turn += 1
|
|||
|
|
|
|||
|
|
# 攻击后失去烟幕
|
|||
|
|
if self.has_keyword("SMOKESCREEN"):
|
|||
|
|
self.remove_keyword("SMOKESCREEN")
|
|||
|
|
|
|||
|
|
def start_new_turn(self):
|
|||
|
|
"""开始新回合时重置状态"""
|
|||
|
|
self.deployed_this_turn = False
|
|||
|
|
self.has_moved_this_turn = False
|
|||
|
|
self.has_attacked_this_turn = False
|
|||
|
|
self.attacks_this_turn = 0
|
|||
|
|
|
|||
|
|
def apply_keyword_abilities(self):
|
|||
|
|
"""根据关键词设置特殊能力"""
|
|||
|
|
if self.has_keyword("BLITZ"):
|
|||
|
|
self.can_operate_immediately = True
|
|||
|
|
|
|||
|
|
if self.has_keyword("TANK") or self.unit_type == UnitType.TANK:
|
|||
|
|
self.can_move_and_attack = True
|
|||
|
|
|
|||
|
|
if self.has_keyword("FURY"):
|
|||
|
|
self.can_attack_multiple_times = True
|
|||
|
|
self.max_attacks_per_turn = 2
|
|||
|
|
|
|||
|
|
def can_attack_target(self, target: 'Unit', battlefield) -> bool:
|
|||
|
|
"""判断是否能攻击目标单位"""
|
|||
|
|
# 基础规则:前线只能攻击对方前线,除非有特殊能力
|
|||
|
|
if self.position is None or target.position is None:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
my_line, my_pos = self.position
|
|||
|
|
target_line, target_pos = target.position
|
|||
|
|
|
|||
|
|
# 特殊单位类型的攻击规则
|
|||
|
|
if self.unit_type == UnitType.ARTILLERY:
|
|||
|
|
# 火炮可以跨战线攻击
|
|||
|
|
return True
|
|||
|
|
elif self.unit_type == UnitType.FIGHTER:
|
|||
|
|
# 战斗机可以攻击任意位置,但不能攻击被守护的目标
|
|||
|
|
return not self._is_target_guarded(target, battlefield)
|
|||
|
|
elif self.unit_type == UnitType.BOMBER:
|
|||
|
|
# 轰炸机可以攻击任意位置,但不能攻击与战斗机同排的目标
|
|||
|
|
return not self._is_target_protected_by_fighter(target, battlefield)
|
|||
|
|
else:
|
|||
|
|
# 常规单位攻击规则:支援线攻击敌方前线,前线攻击敌方支援线
|
|||
|
|
if my_line == LineType.FRONT:
|
|||
|
|
# 前线单位攻击敌方支援线
|
|||
|
|
return target_line in [LineType.PLAYER1_SUPPORT, LineType.PLAYER2_SUPPORT]
|
|||
|
|
elif my_line in [LineType.PLAYER1_SUPPORT, LineType.PLAYER2_SUPPORT]:
|
|||
|
|
# 支援线单位攻击敌方前线
|
|||
|
|
return target_line == LineType.FRONT
|
|||
|
|
else:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def _is_target_guarded(self, target: 'Unit', battlefield) -> bool:
|
|||
|
|
"""检查目标是否被守护"""
|
|||
|
|
# TODO: 实现守护逻辑检查
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def _is_target_protected_by_fighter(self, target: 'Unit', battlefield) -> bool:
|
|||
|
|
"""检查目标是否被战斗机保护"""
|
|||
|
|
# TODO: 实现战斗机保护逻辑
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def can_be_attacked_by(self, attacker: 'Unit', battlefield) -> bool:
|
|||
|
|
"""判断是否能被指定单位攻击"""
|
|||
|
|
# 烟幕保护
|
|||
|
|
if self.has_keyword("SMOKESCREEN"):
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 守护保护
|
|||
|
|
if self._is_guarded(battlefield):
|
|||
|
|
# 只有轰炸机和火炮能攻击被守护的单位
|
|||
|
|
return attacker.unit_type in [UnitType.BOMBER, UnitType.ARTILLERY]
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def _is_guarded(self, battlefield) -> bool:
|
|||
|
|
"""检查自身是否被守护"""
|
|||
|
|
# TODO: 实现守护检查逻辑
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def reset_operation_status(self) -> None:
|
|||
|
|
"""重置行动状态(回合开始时调用)"""
|
|||
|
|
# 已移除 has_operated_this_turn,使用新的细分状态
|
|||
|
|
# 移除Blitz状态(如果是临时的)
|
|||
|
|
if "BLITZ" in self.temporary_modifiers:
|
|||
|
|
self.can_operate_immediately = False
|
|||
|
|
|
|||
|
|
def __str__(self) -> str:
|
|||
|
|
keywords_str = f" [{', '.join(self.keywords)}]" if self.keywords else ""
|
|||
|
|
nation_str = f" ({self.nation})" if self.nation else ""
|
|||
|
|
return f"{self.name}{nation_str} ({self.unit_type}) {self.stats.attack}/{self.stats.current_defense}{keywords_str}"
|
|||
|
|
|
|||
|
|
def __repr__(self) -> str:
|
|||
|
|
return f"Unit(id={self.id.hex[:8]}, name='{self.name}', type={self.unit_type}, stats={self.stats.attack}/{self.stats.current_defense})"
|