Files
gd-playground/Game/Presentation/Battle/Scripts/BattleController.gd
54shitaimzf e465f1cbb0 feat: 基础卡牌场景与卡牌逻辑可扩展框架,以及todo文档
- 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.
2026-04-22 21:58:15 +08:00

263 lines
11 KiB
GDScript
Raw Permalink 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.
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