647 lines
25 KiB
Python
647 lines
25 KiB
Python
|
|
"""
|
|||
|
|
KARDS 战斗引擎核心 - 专注于战斗系统
|
|||
|
|
不包含卡牌系统,单位通过函数调用直接部署
|
|||
|
|
"""
|
|||
|
|
from typing import Dict, List, Optional, Any, Union
|
|||
|
|
from uuid import UUID
|
|||
|
|
|
|||
|
|
from ..battlefield.battlefield import Battlefield, HQ
|
|||
|
|
from ..units.unit import Unit
|
|||
|
|
from .enums import LineType
|
|||
|
|
from .unit_combat_rules import UnitCombatRules
|
|||
|
|
from ..events.event_system import EventType, GameEvent, publish_event
|
|||
|
|
from ..events.managers.smokescreen_manager import setup_smokescreen_for_unit
|
|||
|
|
|
|||
|
|
|
|||
|
|
class BattleEngine:
|
|||
|
|
"""KARDS 战斗引擎 - 纯战斗系统"""
|
|||
|
|
|
|||
|
|
def __init__(self, player1_name: str, player2_name: str, debug_mode: bool = False):
|
|||
|
|
self.battlefield = Battlefield(player1_name, player2_name)
|
|||
|
|
self.player_names = [player1_name, player2_name]
|
|||
|
|
self.debug_mode = debug_mode
|
|||
|
|
|
|||
|
|
self.current_turn = 1
|
|||
|
|
self.active_player = 0 # 0 for player1, 1 for player2
|
|||
|
|
self.turn_phase = 0 # 0=player1回合, 1=player2回合
|
|||
|
|
|
|||
|
|
# Kredits Slot 系统 - 指挥点槽
|
|||
|
|
self.player1_kredits_slot = 0 # 当前槽数
|
|||
|
|
self.player2_kredits_slot = 0
|
|||
|
|
self.max_kredits_slot = 12 # 自然增长上限
|
|||
|
|
self.absolute_max_kredits = 24 # 绝对上限
|
|||
|
|
|
|||
|
|
# Kredits 系统 - 当前回合可用指挥点
|
|||
|
|
self.player1_kredits = 0 # 每回合重置为槽数
|
|||
|
|
self.player2_kredits = 0
|
|||
|
|
|
|||
|
|
# 事件历史
|
|||
|
|
self.event_history = []
|
|||
|
|
|
|||
|
|
# 初始化第一回合的Kredits
|
|||
|
|
self._start_player_turn()
|
|||
|
|
|
|||
|
|
def _start_player_turn(self):
|
|||
|
|
"""开始玩家回合,更新Kredits Slot和Kredits"""
|
|||
|
|
if self.active_player == 0:
|
|||
|
|
# 玩家1在每次轮到自己时增长槽数(新回合开始时)
|
|||
|
|
if self.turn_phase == 0 and self.player1_kredits_slot < self.max_kredits_slot:
|
|||
|
|
self.player1_kredits_slot += 1
|
|||
|
|
# 设置本回合Kredits为槽数
|
|||
|
|
self.player1_kredits = self.player1_kredits_slot
|
|||
|
|
else:
|
|||
|
|
# 玩家2在每次轮到自己且完成一个完整回合后增长槽数
|
|||
|
|
if self.turn_phase == 1 and self.player2_kredits_slot < self.max_kredits_slot:
|
|||
|
|
self.player2_kredits_slot += 1
|
|||
|
|
self.player2_kredits = self.player2_kredits_slot
|
|||
|
|
|
|||
|
|
def get_kredits(self, player_id: int) -> int:
|
|||
|
|
"""获取玩家当前Kredits"""
|
|||
|
|
if player_id == 0:
|
|||
|
|
return self.player1_kredits
|
|||
|
|
elif player_id == 1:
|
|||
|
|
return self.player2_kredits
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
def get_kredits_slot(self, player_id: int) -> int:
|
|||
|
|
"""获取玩家当前Kredits Slot"""
|
|||
|
|
if player_id == 0:
|
|||
|
|
return self.player1_kredits_slot
|
|||
|
|
elif player_id == 1:
|
|||
|
|
return self.player2_kredits_slot
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
def spend_kredits(self, player_id: int, amount: int) -> bool:
|
|||
|
|
"""消耗Kredits"""
|
|||
|
|
if amount <= 0:
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
current_kredits = self.get_kredits(player_id)
|
|||
|
|
if current_kredits >= amount:
|
|||
|
|
if player_id == 0:
|
|||
|
|
self.player1_kredits -= amount
|
|||
|
|
elif player_id == 1:
|
|||
|
|
self.player2_kredits -= amount
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# === 单位直接部署功能 ===
|
|||
|
|
|
|||
|
|
def deploy_unit_to_support(self, unit: Unit, player_id: int, position: Optional[int] = None) -> Dict[str, Any]:
|
|||
|
|
"""直接将单位部署到己方支援线的指定位置"""
|
|||
|
|
if player_id not in [0, 1]:
|
|||
|
|
return {"success": False, "reason": "Invalid player ID"}
|
|||
|
|
|
|||
|
|
support_line = self.battlefield.get_player_support_line(self.player_names[player_id])
|
|||
|
|
if not support_line:
|
|||
|
|
return {"success": False, "reason": "Support line not found"}
|
|||
|
|
|
|||
|
|
if support_line.is_full():
|
|||
|
|
return {"success": False, "reason": "Support line is full"}
|
|||
|
|
|
|||
|
|
# 设置单位所有者
|
|||
|
|
unit.owner = self.player_names[player_id]
|
|||
|
|
|
|||
|
|
# 如果指定了位置,尝试在该位置部署
|
|||
|
|
if position is not None:
|
|||
|
|
# 检查位置是否有效
|
|||
|
|
if position < 0:
|
|||
|
|
return {"success": False, "reason": "Invalid position: position cannot be negative"}
|
|||
|
|
|
|||
|
|
# 如果位置超出当前范围,添加到末尾
|
|||
|
|
current_count = len(support_line.objectives)
|
|||
|
|
if position >= current_count:
|
|||
|
|
success = support_line.add_unit(unit)
|
|||
|
|
actual_position = current_count if success else None
|
|||
|
|
else:
|
|||
|
|
# 在指定位置插入,其他目标向后挤
|
|||
|
|
support_line.objectives.insert(position, unit)
|
|||
|
|
support_line._reindex_objectives()
|
|||
|
|
success = True
|
|||
|
|
actual_position = position
|
|||
|
|
else:
|
|||
|
|
# 没有指定位置,添加到末尾
|
|||
|
|
success = support_line.add_unit(unit)
|
|||
|
|
objectives = support_line.get_all_objectives()
|
|||
|
|
actual_position = len(objectives) - 1 if success else None
|
|||
|
|
|
|||
|
|
if success:
|
|||
|
|
self.battlefield.unit_registry[unit.id] = unit
|
|||
|
|
|
|||
|
|
# 设置烟幕管理器
|
|||
|
|
setup_smokescreen_for_unit(unit)
|
|||
|
|
|
|||
|
|
# 发布部署事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_DEPLOYED,
|
|||
|
|
source=unit,
|
|||
|
|
data={
|
|||
|
|
"position": unit.position,
|
|||
|
|
"player_id": player_id,
|
|||
|
|
"line_type": support_line.line_type,
|
|||
|
|
"actual_position": actual_position
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "deploy_to_support",
|
|||
|
|
"unit_id": unit.id,
|
|||
|
|
"player": player_id,
|
|||
|
|
"position": actual_position
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {"success": False, "reason": "Failed to deploy unit"}
|
|||
|
|
|
|||
|
|
# === 单位操作(移动/攻击) ===
|
|||
|
|
|
|||
|
|
def move_unit(self, unit_id: UUID, target_position: tuple, player_id: int) -> Dict[str, Any]:
|
|||
|
|
"""移动单位到指定位置"""
|
|||
|
|
if player_id != self.active_player:
|
|||
|
|
return {"success": False, "reason": "Not your turn"}
|
|||
|
|
|
|||
|
|
unit = self.battlefield.find_unit(unit_id)
|
|||
|
|
if not unit or unit.owner != self.player_names[player_id]:
|
|||
|
|
return {"success": False, "reason": "Unit not found or not yours"}
|
|||
|
|
|
|||
|
|
# 检查是否可以移动
|
|||
|
|
if not unit.can_move():
|
|||
|
|
if unit.deployed_this_turn and not unit.can_operate_immediately:
|
|||
|
|
return {"success": False, "reason": "Unit was deployed this turn and cannot move"}
|
|||
|
|
elif unit.has_moved_this_turn:
|
|||
|
|
return {"success": False, "reason": "Unit has already moved this turn"}
|
|||
|
|
elif unit.has_attacked_this_turn and not unit.can_move_and_attack:
|
|||
|
|
return {"success": False, "reason": "Unit has attacked and cannot move"}
|
|||
|
|
else:
|
|||
|
|
return {"success": False, "reason": "Unit cannot move"}
|
|||
|
|
|
|||
|
|
# 检查激活成本
|
|||
|
|
activation_cost = unit.stats.operation_cost
|
|||
|
|
if not self.spend_kredits(player_id, activation_cost):
|
|||
|
|
return {"success": False, "reason": "Insufficient Kredits for activation"}
|
|||
|
|
|
|||
|
|
result = self._handle_move_to_position(unit, target_position)
|
|||
|
|
|
|||
|
|
# 如果移动成功,标记单位已移动并发布事件
|
|||
|
|
if result['success']:
|
|||
|
|
old_position = unit.position
|
|||
|
|
unit.perform_move()
|
|||
|
|
|
|||
|
|
# 发布移动事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_MOVED,
|
|||
|
|
source=unit,
|
|||
|
|
data={
|
|||
|
|
"old_position": old_position,
|
|||
|
|
"new_position": target_position,
|
|||
|
|
"player_id": player_id
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 发布位置改变事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_POSITION_CHANGED,
|
|||
|
|
source=unit,
|
|||
|
|
data={
|
|||
|
|
"old_position": old_position,
|
|||
|
|
"new_position": unit.position
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def attack_target(self, attacker_id: UUID, target_id: Union[UUID, str], player_id: int) -> Dict[str, Any]:
|
|||
|
|
"""攻击指定目标"""
|
|||
|
|
if player_id != self.active_player:
|
|||
|
|
return {"success": False, "reason": "Not your turn"}
|
|||
|
|
|
|||
|
|
attacker = self.battlefield.find_unit(attacker_id)
|
|||
|
|
if not attacker or attacker.owner != self.player_names[player_id]:
|
|||
|
|
return {"success": False, "reason": "Unit not found or not yours"}
|
|||
|
|
|
|||
|
|
# 检查是否可以攻击
|
|||
|
|
if not attacker.can_attack():
|
|||
|
|
if attacker.deployed_this_turn and not attacker.can_operate_immediately:
|
|||
|
|
return {"success": False, "reason": "Unit was deployed this turn and cannot attack"}
|
|||
|
|
elif attacker.attacks_this_turn >= attacker.max_attacks_per_turn:
|
|||
|
|
return {"success": False, "reason": "Unit has reached maximum attacks per turn"}
|
|||
|
|
elif attacker.has_moved_this_turn and not attacker.can_move_and_attack:
|
|||
|
|
return {"success": False, "reason": "Unit has moved and cannot attack"}
|
|||
|
|
else:
|
|||
|
|
return {"success": False, "reason": "Unit cannot attack"}
|
|||
|
|
|
|||
|
|
# 检查激活成本
|
|||
|
|
activation_cost = attacker.stats.operation_cost
|
|||
|
|
if not self.spend_kredits(player_id, activation_cost):
|
|||
|
|
return {"success": False, "reason": "Insufficient Kredits for activation"}
|
|||
|
|
|
|||
|
|
result = self._handle_attack_target(attacker, target_id)
|
|||
|
|
|
|||
|
|
# 如果攻击成功,标记单位已攻击并发布事件
|
|||
|
|
if result['success']:
|
|||
|
|
attacker.perform_attack()
|
|||
|
|
|
|||
|
|
# 发布单位攻击事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_ATTACKED,
|
|||
|
|
source=attacker,
|
|||
|
|
data={
|
|||
|
|
"target_id": target_id,
|
|||
|
|
"attack_result": result
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 检查游戏是否结束(HQ被摧毁)
|
|||
|
|
game_over, winner = self.battlefield.is_game_over()
|
|||
|
|
if game_over:
|
|||
|
|
result["game_over"] = True
|
|||
|
|
result["winner"] = winner # winner是player_id字符串
|
|||
|
|
result["winner_name"] = winner # 直接使用player_id作为名称
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def _handle_move_to_position(self, unit: Unit, target_pos: tuple) -> Dict[str, Any]:
|
|||
|
|
"""处理移动到位置"""
|
|||
|
|
target_line_type, target_index = target_pos
|
|||
|
|
|
|||
|
|
if not unit.position:
|
|||
|
|
return {"success": False, "reason": "Unit not on battlefield"}
|
|||
|
|
|
|||
|
|
current_line, current_index = unit.position
|
|||
|
|
|
|||
|
|
# 只能从支援线移动到前线
|
|||
|
|
if target_line_type != LineType.FRONT:
|
|||
|
|
return {"success": False, "reason": "Can only move to front line"}
|
|||
|
|
|
|||
|
|
if current_line == LineType.FRONT:
|
|||
|
|
return {"success": False, "reason": "Units on front line cannot move"}
|
|||
|
|
|
|||
|
|
return self._move_to_front_line(unit, target_index)
|
|||
|
|
|
|||
|
|
def _move_to_front_line(self, unit: Unit, target_index: int) -> Dict[str, Any]:
|
|||
|
|
"""从支援线移动到前线"""
|
|||
|
|
front_line = self.battlefield.front_line
|
|||
|
|
|
|||
|
|
# 检查前线控制权
|
|||
|
|
if (self.battlefield.front_line_controller is not None and
|
|||
|
|
self.battlefield.front_line_controller != unit.owner):
|
|||
|
|
return {"success": False, "reason": "Front line controlled by enemy"}
|
|||
|
|
|
|||
|
|
# 检查是否要挤位置
|
|||
|
|
if target_index < len(front_line.objectives):
|
|||
|
|
if front_line.is_full():
|
|||
|
|
return {"success": False, "reason": "Front line is full, cannot squeeze"}
|
|||
|
|
else:
|
|||
|
|
if front_line.is_full():
|
|||
|
|
return {"success": False, "reason": "Front line is full"}
|
|||
|
|
|
|||
|
|
# 从支援线移除
|
|||
|
|
support_line = self.battlefield.get_player_support_line(unit.owner)
|
|||
|
|
if not support_line.remove_unit(unit):
|
|||
|
|
return {"success": False, "reason": "Failed to remove from support line"}
|
|||
|
|
|
|||
|
|
# 执行移动
|
|||
|
|
if target_index >= len(front_line.objectives):
|
|||
|
|
front_line.add_unit(unit)
|
|||
|
|
actual_position = len(front_line.objectives) - 1
|
|||
|
|
else:
|
|||
|
|
front_line.objectives.insert(target_index, unit)
|
|||
|
|
front_line._reindex_objectives()
|
|||
|
|
actual_position = target_index
|
|||
|
|
|
|||
|
|
# 更新前线控制权
|
|||
|
|
self.battlefield._update_front_line_control()
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "move_to_front",
|
|||
|
|
"unit_id": unit.id,
|
|||
|
|
"to_position": actual_position,
|
|||
|
|
"front_line_controller": self.battlefield.front_line_controller
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def _handle_attack_target(self, attacker: Unit, target_id: Union[UUID, str]) -> Dict[str, Any]:
|
|||
|
|
"""处理攻击目标"""
|
|||
|
|
# 查找目标
|
|||
|
|
target = None
|
|||
|
|
|
|||
|
|
if isinstance(target_id, str) and target_id.startswith("hq"):
|
|||
|
|
if target_id == "hq1":
|
|||
|
|
target = self.battlefield.player1_hq
|
|||
|
|
elif target_id == "hq2":
|
|||
|
|
target = self.battlefield.player2_hq
|
|||
|
|
else:
|
|||
|
|
target = self.battlefield.find_unit(target_id)
|
|||
|
|
|
|||
|
|
if not target:
|
|||
|
|
return {"success": False, "reason": "Target not found"}
|
|||
|
|
|
|||
|
|
# 检查是否能攻击该目标
|
|||
|
|
if not UnitCombatRules.can_attack_target(attacker, target, self.battlefield):
|
|||
|
|
return {"success": False, "reason": "Cannot attack this target"}
|
|||
|
|
|
|||
|
|
# 发布攻击前检查事件(可取消)
|
|||
|
|
before_attack_event = GameEvent(
|
|||
|
|
event_type=EventType.BEFORE_ATTACK_CHECK,
|
|||
|
|
source=attacker,
|
|||
|
|
target=target,
|
|||
|
|
data={"attack_type": "unit_attack"},
|
|||
|
|
cancellable=True
|
|||
|
|
)
|
|||
|
|
publish_event(before_attack_event)
|
|||
|
|
|
|||
|
|
# 如果攻击被阻止,返回失败
|
|||
|
|
if before_attack_event.cancelled:
|
|||
|
|
return {"success": False, "reason": before_attack_event.cancel_reason}
|
|||
|
|
|
|||
|
|
# 执行攻击
|
|||
|
|
return self._execute_attack(attacker, target)
|
|||
|
|
|
|||
|
|
def _execute_attack(self, attacker: Unit, target: Union[Unit, HQ]) -> Dict[str, Any]:
|
|||
|
|
"""执行攻击"""
|
|||
|
|
damage = attacker.get_effective_attack()
|
|||
|
|
|
|||
|
|
if isinstance(target, Unit):
|
|||
|
|
return self._unit_combat(attacker, target)
|
|||
|
|
else:
|
|||
|
|
# 攻击HQ
|
|||
|
|
actual_damage = target.take_damage(damage)
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "attack_hq",
|
|||
|
|
"attacker_id": attacker.id,
|
|||
|
|
"target": "HQ",
|
|||
|
|
"damage_dealt": actual_damage,
|
|||
|
|
"hq_destroyed": target.is_destroyed()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def _unit_combat(self, attacker: Unit, target: Unit) -> Dict[str, Any]:
|
|||
|
|
"""单位间战斗"""
|
|||
|
|
# 处理伏击
|
|||
|
|
if target.has_keyword("AMBUSH"):
|
|||
|
|
ambush_damage = target.get_effective_attack()
|
|||
|
|
attacker_damage = attacker.stats.take_damage(ambush_damage)
|
|||
|
|
if not attacker.stats.is_alive():
|
|||
|
|
self.battlefield.remove_unit(attacker)
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "attack_unit",
|
|||
|
|
"ambush_triggered": True,
|
|||
|
|
"attacker_destroyed": True,
|
|||
|
|
"ambush_damage": attacker_damage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 计算攻击伤害
|
|||
|
|
attack_damage = attacker.get_effective_attack()
|
|||
|
|
|
|||
|
|
# 应用重甲减伤
|
|||
|
|
for keyword in target.keywords:
|
|||
|
|
if keyword.startswith("HEAVY_ARMOR_"):
|
|||
|
|
armor_value = int(keyword.split("_")[-1])
|
|||
|
|
attack_damage = max(0, attack_damage - armor_value)
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
# 战斗是同时进行的:先计算双方攻击力,再同时造成伤害
|
|||
|
|
# 1. 计算双方攻击力
|
|||
|
|
counter_attack = 0
|
|||
|
|
counter_damage = 0
|
|||
|
|
|
|||
|
|
# 检查防御方是否能够反击
|
|||
|
|
if UnitCombatRules.can_counter_attack(target, attacker):
|
|||
|
|
counter_attack = target.get_effective_attack()
|
|||
|
|
|
|||
|
|
# 2. 同时造成伤害
|
|||
|
|
actual_damage = target.stats.take_damage(attack_damage)
|
|||
|
|
if counter_attack > 0:
|
|||
|
|
counter_damage = attacker.stats.take_damage(counter_attack)
|
|||
|
|
|
|||
|
|
# 3. 发布伤害事件
|
|||
|
|
if actual_damage > 0:
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_TAKES_DAMAGE,
|
|||
|
|
source=attacker,
|
|||
|
|
target=target,
|
|||
|
|
data={
|
|||
|
|
"damage": actual_damage,
|
|||
|
|
"damage_type": "attack"
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
if counter_damage > 0:
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_COUNTER_ATTACKS,
|
|||
|
|
source=target,
|
|||
|
|
target=attacker,
|
|||
|
|
data={
|
|||
|
|
"damage": counter_damage,
|
|||
|
|
"counter_attack_damage": counter_attack
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_TAKES_DAMAGE,
|
|||
|
|
source=target,
|
|||
|
|
target=attacker,
|
|||
|
|
data={
|
|||
|
|
"damage": counter_damage,
|
|||
|
|
"damage_type": "counter_attack"
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
# 检查单位死亡并发布事件
|
|||
|
|
units_destroyed = []
|
|||
|
|
if not target.stats.is_alive():
|
|||
|
|
self.battlefield.remove_unit(target)
|
|||
|
|
units_destroyed.append(target.id)
|
|||
|
|
|
|||
|
|
# 发布单位摧毁事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_DESTROYED,
|
|||
|
|
source=target,
|
|||
|
|
data={
|
|||
|
|
"destroyed_by": attacker.id,
|
|||
|
|
"destruction_cause": "combat_damage"
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
if not attacker.stats.is_alive():
|
|||
|
|
self.battlefield.remove_unit(attacker)
|
|||
|
|
units_destroyed.append(attacker.id)
|
|||
|
|
|
|||
|
|
# 发布单位摧毁事件
|
|||
|
|
publish_event(GameEvent(
|
|||
|
|
event_type=EventType.UNIT_DESTROYED,
|
|||
|
|
source=attacker,
|
|||
|
|
data={
|
|||
|
|
"destroyed_by": target.id,
|
|||
|
|
"destruction_cause": "counter_attack_damage"
|
|||
|
|
}
|
|||
|
|
))
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "attack_unit",
|
|||
|
|
"attacker_id": attacker.id,
|
|||
|
|
"target_id": target.id,
|
|||
|
|
"damage_dealt": actual_damage,
|
|||
|
|
"counter_damage": counter_damage,
|
|||
|
|
"units_destroyed": units_destroyed
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# === 回合管理 ===
|
|||
|
|
|
|||
|
|
def end_turn(self) -> Dict[str, Any]:
|
|||
|
|
"""结束当前玩家的回合"""
|
|||
|
|
old_player = self.active_player
|
|||
|
|
old_phase = self.turn_phase
|
|||
|
|
|
|||
|
|
# 切换玩家
|
|||
|
|
if self.turn_phase == 0:
|
|||
|
|
# 从玩家1切换到玩家2,但还在同一回合
|
|||
|
|
self.active_player = 1
|
|||
|
|
self.turn_phase = 1
|
|||
|
|
else:
|
|||
|
|
# 从玩家2切换回玩家1,进入下一回合
|
|||
|
|
self.active_player = 0
|
|||
|
|
self.turn_phase = 0
|
|||
|
|
self.current_turn += 1 # 只有双方都行动过才算一个完整回合
|
|||
|
|
|
|||
|
|
# 开始新玩家回合(更新Kredits Slot和Kredits)
|
|||
|
|
self._start_player_turn()
|
|||
|
|
|
|||
|
|
# 重置活跃玩家的单位状态
|
|||
|
|
for unit in self.battlefield.get_all_units(self.player_names[self.active_player]):
|
|||
|
|
unit.start_new_turn() # 使用新的方法重置所有状态
|
|||
|
|
|
|||
|
|
# 检查游戏结束
|
|||
|
|
game_over, winner = self.battlefield.is_game_over()
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"turn_ended": True,
|
|||
|
|
"previous_player": old_player,
|
|||
|
|
"new_active_player": self.active_player,
|
|||
|
|
"turn_number": self.current_turn,
|
|||
|
|
"turn_phase": self.turn_phase,
|
|||
|
|
"is_new_round": self.turn_phase == 0, # 是否是新一轮的开始
|
|||
|
|
"kredits": self.get_kredits(self.active_player),
|
|||
|
|
"kredits_slot": self.get_kredits_slot(self.active_player),
|
|||
|
|
"game_over": game_over,
|
|||
|
|
"winner": winner
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# === DEBUG 功能 ===
|
|||
|
|
|
|||
|
|
def debug_set_unit_stats(self, unit_id: UUID, attack: Optional[int] = None,
|
|||
|
|
defense: Optional[int] = None) -> Dict[str, Any]:
|
|||
|
|
"""DEBUG: 设置单位属性"""
|
|||
|
|
if not self.debug_mode:
|
|||
|
|
return {"success": False, "reason": "Debug mode not enabled"}
|
|||
|
|
|
|||
|
|
unit = self.battlefield.find_unit(unit_id)
|
|||
|
|
if not unit:
|
|||
|
|
return {"success": False, "reason": "Unit not found"}
|
|||
|
|
|
|||
|
|
changes = {}
|
|||
|
|
if attack is not None:
|
|||
|
|
unit.stats.attack = attack
|
|||
|
|
changes["attack"] = attack
|
|||
|
|
|
|||
|
|
if defense is not None:
|
|||
|
|
unit.stats.current_defense = defense
|
|||
|
|
unit.stats.max_defense = defense
|
|||
|
|
changes["defense"] = defense
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "debug_set_unit_stats",
|
|||
|
|
"unit_id": unit_id,
|
|||
|
|
"changes": changes
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def debug_set_kredits(self, player_id: int, kredits: Optional[int] = None,
|
|||
|
|
kredits_slot: Optional[int] = None) -> Dict[str, Any]:
|
|||
|
|
"""DEBUG: 设置玩家Kredits和Kredits Slot"""
|
|||
|
|
if not self.debug_mode:
|
|||
|
|
return {"success": False, "reason": "Debug mode not enabled"}
|
|||
|
|
|
|||
|
|
if player_id not in [0, 1]:
|
|||
|
|
return {"success": False, "reason": "Invalid player ID"}
|
|||
|
|
|
|||
|
|
changes = {}
|
|||
|
|
|
|||
|
|
if kredits is not None:
|
|||
|
|
kredits = max(0, min(kredits, self.absolute_max_kredits))
|
|||
|
|
if player_id == 0:
|
|||
|
|
self.player1_kredits = kredits
|
|||
|
|
else:
|
|||
|
|
self.player2_kredits = kredits
|
|||
|
|
changes["kredits"] = kredits
|
|||
|
|
|
|||
|
|
if kredits_slot is not None:
|
|||
|
|
kredits_slot = max(0, min(kredits_slot, self.absolute_max_kredits))
|
|||
|
|
if player_id == 0:
|
|||
|
|
self.player1_kredits_slot = kredits_slot
|
|||
|
|
else:
|
|||
|
|
self.player2_kredits_slot = kredits_slot
|
|||
|
|
changes["kredits_slot"] = kredits_slot
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "debug_set_kredits",
|
|||
|
|
"player_id": player_id,
|
|||
|
|
"changes": changes
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def debug_set_hq_defense(self, player_id: int, defense: int) -> Dict[str, Any]:
|
|||
|
|
"""DEBUG: 设置HQ防御值"""
|
|||
|
|
if not self.debug_mode:
|
|||
|
|
return {"success": False, "reason": "Debug mode not enabled"}
|
|||
|
|
|
|||
|
|
hq = None
|
|||
|
|
if player_id == 0:
|
|||
|
|
hq = self.battlefield.player1_hq
|
|||
|
|
elif player_id == 1:
|
|||
|
|
hq = self.battlefield.player2_hq
|
|||
|
|
else:
|
|||
|
|
return {"success": False, "reason": "Invalid player ID"}
|
|||
|
|
|
|||
|
|
defense = max(0, defense)
|
|||
|
|
hq.current_defense = defense
|
|||
|
|
# HQ没有max_defense属性,只更新current_defense
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"success": True,
|
|||
|
|
"action": "debug_set_hq_defense",
|
|||
|
|
"player_id": player_id,
|
|||
|
|
"new_defense": defense
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# === 游戏状态 ===
|
|||
|
|
|
|||
|
|
def get_game_state(self) -> Dict[str, Any]:
|
|||
|
|
"""获取完整游戏状态"""
|
|||
|
|
return {
|
|||
|
|
"turn": self.current_turn,
|
|||
|
|
"turn_phase": self.turn_phase,
|
|||
|
|
"active_player": self.active_player,
|
|||
|
|
"kredits": {
|
|||
|
|
"player1": self.player1_kredits,
|
|||
|
|
"player2": self.player2_kredits,
|
|||
|
|
"active_player": self.get_kredits(self.active_player)
|
|||
|
|
},
|
|||
|
|
"kredits_slots": {
|
|||
|
|
"player1": self.player1_kredits_slot,
|
|||
|
|
"player2": self.player2_kredits_slot,
|
|||
|
|
"active_player": self.get_kredits_slot(self.active_player)
|
|||
|
|
},
|
|||
|
|
"battlefield": str(self.battlefield),
|
|||
|
|
"player1_hq": self.battlefield.player1_hq.current_defense,
|
|||
|
|
"player2_hq": self.battlefield.player2_hq.current_defense,
|
|||
|
|
"front_line_controller": self.battlefield.front_line_controller,
|
|||
|
|
"units_count": {
|
|||
|
|
"player1_total": len(self.battlefield.get_all_units(self.player_names[0])),
|
|||
|
|
"player2_total": len(self.battlefield.get_all_units(self.player_names[1])),
|
|||
|
|
"front_line": len(self.battlefield.front_line.get_all_units())
|
|||
|
|
},
|
|||
|
|
"debug_mode": self.debug_mode
|
|||
|
|
}
|