- Added BattleState class to manage battle flow, including turn management and event handling. - Introduced BuffInstance class to represent buffs applied to combatants. - Created CardInstance class to handle card definitions and cost calculations. - Developed CombatantState class to manage combatant attributes and actions. - Implemented EffectRegistry to apply effects based on event specifications. - Added various handlers (BlockHandler, DamageHandler, DrawHandler, etc.) to process specific events. - Created IntentPlanner and IntentState classes to manage enemy actions and intents. - Established a queue system for handling battle events with BattleEventQueue and BattleEventTask. - Introduced triggers for applying effects based on game events (e.g., OnCardDrawnGainBlockTrigger). - Added necessary UID files for new scripts to ensure proper resource management.
263 lines
11 KiB
GDScript
263 lines
11 KiB
GDScript
extends Control
|
||
|
||
const CARD_SCENE := preload("res://Game/Presentation/Cards/Scenes/BaseCard.tscn")
|
||
const BattleStateScript = preload("res://Game/Runtime/BattleState.gd")
|
||
const EffectRegistryScript = preload("res://Game/Runtime/EffectRegistry.gd")
|
||
const IntentPlannerScript = preload("res://Game/Runtime/IntentPlanner.gd")
|
||
const PlayCardCommandScript = preload("res://Game/Application/Commands/PlayCardCommand.gd")
|
||
const EndTurnCommandScript = preload("res://Game/Application/Commands/EndTurnCommand.gd")
|
||
const CombatantStateScript = preload("res://Game/Runtime/CombatantState.gd")
|
||
const CardInstanceScript = preload("res://Game/Runtime/CardInstance.gd")
|
||
const BuffInstanceScript = preload("res://Game/Runtime/Buffs/BuffInstance.gd")
|
||
const BattlePhaseScript = preload("res://Game/Runtime/BattlePhase.gd")
|
||
const EffectSpecScript = preload("res://Game/Content/Cards/EffectSpec.gd")
|
||
const CardDefScript = preload("res://Game/Content/Cards/CardDef.gd")
|
||
const ECardTargetingScript = preload("res://Game/Content/Cards/Enums/ECardTargeting.gd")
|
||
const ECardTagScript = preload("res://Game/Content/Cards/Enums/ECardTag.gd")
|
||
const OnCardDrawnGainBlockTriggerScript = preload("res://Game/Runtime/Triggers/Templates/OnCardDrawnGainBlockTrigger.gd")
|
||
const OnPhaseEnteredGainBlockTriggerScript = preload("res://Game/Runtime/Triggers/Templates/OnPhaseEnteredGainBlockTrigger.gd")
|
||
const DebugLogger = preload("res://Game/Infrastructure/Diagnostics/DebugLogger.gd")
|
||
|
||
var battle_state
|
||
var effect_registry = EffectRegistryScript.new()
|
||
var intent_planner = IntentPlannerScript.new()
|
||
var play_card_command = PlayCardCommandScript.new()
|
||
var end_turn_command = EndTurnCommandScript.new()
|
||
|
||
var selected_hand_index: int = -1
|
||
|
||
@onready var turn_label: Label = $RootLayout/TopHUD/TurnLabel
|
||
@onready var energy_label: Label = $RootLayout/TopHUD/EnergyLabel
|
||
@onready var phase_label: Label = $RootLayout/TopHUD/PhaseLabel
|
||
@onready var status_label: Label = $RootLayout/TopHUD/StatusLabel
|
||
@onready var end_turn_button: Button = $RootLayout/TopHUD/EndTurnButton
|
||
|
||
@onready var player_hp_label: Label = $RootLayout/BattleRow/PlayerPanel/PlayerBody/PlayerHp
|
||
@onready var player_block_label: Label = $RootLayout/BattleRow/PlayerPanel/PlayerBody/PlayerBlock
|
||
@onready var player_strength_label: Label = $RootLayout/BattleRow/PlayerPanel/PlayerBody/PlayerStrength
|
||
@onready var player_weak_label: Label = $RootLayout/BattleRow/PlayerPanel/PlayerBody/PlayerWeak
|
||
@onready var player_buffs_label: Label = $RootLayout/BattleRow/PlayerPanel/PlayerBody/PlayerBuffs
|
||
|
||
@onready var enemy_hp_label: Label = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyHp
|
||
@onready var enemy_block_label: Label = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyBlock
|
||
@onready var enemy_strength_label: Label = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyStrength
|
||
@onready var enemy_weak_label: Label = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyWeak
|
||
@onready var enemy_buffs_label: Label = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyBuffs
|
||
@onready var enemy_target_button: Button = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyTargetButton
|
||
@onready var enemy_intent_view = $RootLayout/BattleRow/EnemyPanel/EnemyBody/EnemyIntent
|
||
|
||
@onready var hand_cards: HBoxContainer = $RootLayout/HandPanel/HandBody/HandCards
|
||
|
||
func _ready() -> void:
|
||
end_turn_button.pressed.connect(_on_end_turn_pressed)
|
||
enemy_target_button.pressed.connect(_on_enemy_target_pressed)
|
||
_initialize_battle()
|
||
_refresh_ui()
|
||
|
||
func _initialize_battle() -> void:
|
||
DebugLogger.info("BattleController", "初始化战斗状态")
|
||
battle_state = BattleStateScript.new()
|
||
battle_state.setup(int(Time.get_unix_time_from_system()))
|
||
battle_state.player = CombatantStateScript.new("玩家", 50)
|
||
battle_state.enemy = CombatantStateScript.new("强盗", 45)
|
||
|
||
battle_state.draw_pile = _build_starter_deck()
|
||
battle_state.shuffle_draw_pile()
|
||
_attach_demo_trigger_buff()
|
||
battle_state.enemy_intent = intent_planner.advance(battle_state.enemy)
|
||
battle_state.event_bus.published.connect(_on_battle_event)
|
||
battle_state.start_player_turn()
|
||
status_label.text = "你的回合,选择一张卡牌。"
|
||
|
||
func _attach_demo_trigger_buff() -> void:
|
||
# 演示用 Buff:抽牌获得 1 格挡,进入结束阶段获得 1 格挡。
|
||
var buff = BuffInstanceScript.new("demo_trigger_guard", battle_state.player, 1, -1)
|
||
buff.add_subscriber(OnCardDrawnGainBlockTriggerScript.new(1))
|
||
buff.add_subscriber(OnPhaseEnteredGainBlockTriggerScript.new(BattlePhaseScript.PLAYER_END, 1))
|
||
battle_state.player.add_buff(buff)
|
||
|
||
func _build_starter_deck() -> Array:
|
||
var deck: Array = []
|
||
for _i in range(5):
|
||
deck.append(CardInstanceScript.new(_make_attack_card()))
|
||
for _i in range(4):
|
||
deck.append(CardInstanceScript.new(_make_block_card()))
|
||
for _i in range(2):
|
||
deck.append(CardInstanceScript.new(_make_weak_card()))
|
||
return deck
|
||
|
||
func _make_attack_card():
|
||
var effect = EffectSpecScript.new()
|
||
effect.type_id = "damage"
|
||
effect.params = {"amount": 6}
|
||
|
||
var card = CardDefScript.new()
|
||
card.id = "strike"
|
||
card.name_text = "打击"
|
||
card.description = "造成 6 点伤害。"
|
||
card.base_cost = 1
|
||
card.targeting = ECardTargetingScript.SINGLE_ENEMY
|
||
var tags: Array[int] = []
|
||
tags.append(ECardTagScript.ATTACK)
|
||
card.tags = tags
|
||
var effects: Array[EffectSpec] = []
|
||
effects.append(effect)
|
||
card.on_play_effects = effects
|
||
return card
|
||
|
||
func _make_block_card():
|
||
var effect = EffectSpecScript.new()
|
||
effect.type_id = "block"
|
||
effect.params = {"amount": 5}
|
||
|
||
var card = CardDefScript.new()
|
||
card.id = "defend"
|
||
card.name_text = "防御"
|
||
card.description = "获得 5 点格挡。"
|
||
card.base_cost = 1
|
||
card.targeting = ECardTargetingScript.SELF
|
||
var tags: Array[int] = []
|
||
tags.append(ECardTagScript.SKILL)
|
||
card.tags = tags
|
||
var effects: Array[EffectSpec] = []
|
||
effects.append(effect)
|
||
card.on_play_effects = effects
|
||
return card
|
||
|
||
func _make_weak_card():
|
||
var effect = EffectSpecScript.new()
|
||
effect.type_id = "weak"
|
||
effect.params = {"turns": 1}
|
||
|
||
var card = CardDefScript.new()
|
||
card.id = "taunt"
|
||
card.name_text = "恫吓"
|
||
card.description = "施加 1 层虚弱。"
|
||
card.base_cost = 1
|
||
card.targeting = ECardTargetingScript.SINGLE_ENEMY
|
||
var tags: Array[int] = []
|
||
tags.append(ECardTagScript.SKILL)
|
||
card.tags = tags
|
||
var effects: Array[EffectSpec] = []
|
||
effects.append(effect)
|
||
card.on_play_effects = effects
|
||
return card
|
||
|
||
func _on_card_pressed(card_view) -> void:
|
||
if not battle_state.is_player_turn:
|
||
return
|
||
|
||
selected_hand_index = int(card_view.get_meta("hand_index"))
|
||
var card = battle_state.hand[selected_hand_index]
|
||
if card.card_def.targeting == ECardTargetingScript.SELF or card.card_def.targeting == ECardTargetingScript.NONE:
|
||
_play_card(selected_hand_index, "self")
|
||
return
|
||
|
||
status_label.text = "请选择敌方目标。"
|
||
_refresh_hand_view()
|
||
|
||
func _on_enemy_target_pressed() -> void:
|
||
if selected_hand_index < 0:
|
||
status_label.text = "请先选择一张卡牌。"
|
||
return
|
||
_play_card(selected_hand_index, "enemy")
|
||
|
||
func _play_card(hand_index: int, target_side: String) -> void:
|
||
DebugLogger.info("BattleController", "尝试出牌 index=%d target=%s" % [hand_index, target_side])
|
||
var ok: bool = play_card_command.execute(battle_state, effect_registry, hand_index, target_side)
|
||
selected_hand_index = -1
|
||
if not ok:
|
||
status_label.text = "当前无法打出该卡牌。"
|
||
else:
|
||
status_label.text = "卡牌已结算。"
|
||
_refresh_ui()
|
||
|
||
func _on_end_turn_pressed() -> void:
|
||
DebugLogger.info("BattleController", "结束回合按钮被点击")
|
||
selected_hand_index = -1
|
||
end_turn_command.execute(battle_state, effect_registry, intent_planner)
|
||
if battle_state.player.is_dead() or battle_state.enemy.is_dead():
|
||
_refresh_ui()
|
||
return
|
||
status_label.text = "新回合开始。"
|
||
_refresh_ui()
|
||
|
||
func _refresh_ui() -> void:
|
||
turn_label.text = "回合 %d" % battle_state.turn_index
|
||
energy_label.text = "能量 %d/%d" % [battle_state.energy, battle_state.max_energy]
|
||
phase_label.text = "阶段 %s" % BattlePhaseScript.to_text(battle_state.phase)
|
||
|
||
player_hp_label.text = "生命 %d/%d" % [battle_state.player.hp, battle_state.player.max_hp]
|
||
player_block_label.text = "格挡 %d" % battle_state.player.block
|
||
player_strength_label.text = "力量 %d" % battle_state.player.strength
|
||
player_weak_label.text = "虚弱 %d" % battle_state.player.weak_turns
|
||
player_buffs_label.text = _format_buff_summary(battle_state.player)
|
||
|
||
enemy_hp_label.text = "生命 %d/%d" % [battle_state.enemy.hp, battle_state.enemy.max_hp]
|
||
enemy_block_label.text = "格挡 %d" % battle_state.enemy.block
|
||
enemy_strength_label.text = "力量 %d" % battle_state.enemy.strength
|
||
enemy_weak_label.text = "虚弱 %d" % battle_state.enemy.weak_turns
|
||
enemy_buffs_label.text = _format_buff_summary(battle_state.enemy)
|
||
enemy_intent_view.set_intent(battle_state.enemy_intent)
|
||
|
||
end_turn_button.disabled = not battle_state.is_player_turn
|
||
enemy_target_button.disabled = battle_state.enemy.is_dead()
|
||
_refresh_hand_view()
|
||
_check_battle_end()
|
||
|
||
func _refresh_hand_view() -> void:
|
||
for child in hand_cards.get_children():
|
||
child.queue_free()
|
||
|
||
for i in range(battle_state.hand.size()):
|
||
var view = CARD_SCENE.instantiate()
|
||
view.set_card_instance(battle_state.hand[i])
|
||
view.set_meta("hand_index", i)
|
||
view.pressed.connect(_on_card_pressed)
|
||
hand_cards.add_child(view)
|
||
view.set_selected(i == selected_hand_index)
|
||
|
||
func _check_battle_end() -> void:
|
||
if battle_state.player.is_dead():
|
||
DebugLogger.info("BattleController", "战斗结束:玩家失败")
|
||
status_label.text = "失败。"
|
||
end_turn_button.disabled = true
|
||
if battle_state.enemy.is_dead():
|
||
DebugLogger.info("BattleController", "战斗结束:玩家胜利")
|
||
status_label.text = "胜利。"
|
||
end_turn_button.disabled = true
|
||
enemy_target_button.disabled = true
|
||
|
||
func _on_battle_event(event_type: String, payload: Dictionary) -> void:
|
||
DebugLogger.event("BattleController", event_type, payload)
|
||
match event_type:
|
||
"card_played":
|
||
status_label.text = "已打出:%s" % str(payload.get("card_name", "卡牌"))
|
||
"enemy_action_resolved":
|
||
status_label.text = "敌方使用:%s %s" % [str(payload.get("intent_code", "")), str(payload.get("value", ""))]
|
||
"damage_applied":
|
||
if battle_state.player.is_dead() or battle_state.enemy.is_dead():
|
||
status_label.text = "致命一击已命中。"
|
||
"block_cleared":
|
||
status_label.text = "%s 的格挡已清空" % str(payload.get("target", "单位"))
|
||
"phase_changed":
|
||
phase_label.text = "阶段 %s" % str(payload.get("phase_text", "未知阶段"))
|
||
|
||
func _format_buff_summary(unit) -> String:
|
||
if unit == null:
|
||
return "Buff -"
|
||
if unit.buffs.is_empty():
|
||
return "Buff -"
|
||
|
||
var ordered = unit.get_buffs_in_order()
|
||
var visible_count: int = min(2, ordered.size())
|
||
var parts: Array[String] = []
|
||
for i in range(visible_count):
|
||
var buff = ordered[i]
|
||
parts.append("%s x%d" % [str(buff.buff_id), int(buff.stack)])
|
||
|
||
var text := "Buff %s" % ", ".join(parts)
|
||
if ordered.size() > visible_count:
|
||
text += " ..."
|
||
return text
|