extends Node ## GameManager - Main game coordinator singleton ## Bridges game state, visuals, and UI signal game_ready signal game_started signal game_ended(winner_name: String) signal turn_changed(player_name: String, turn_number: int) signal phase_changed(phase_name: String) signal card_played(card_data: Dictionary) signal damage_dealt(player_name: String, amount: int) signal message(text: String) signal action_undone(action_name: String) signal undo_available_changed(available: bool) # Game state var game_state: GameState = null # Undo system var undo_system: UndoSystem = null # State flags var is_initialized: bool = false var is_game_active: bool = false # Current input mode enum InputMode { NONE, SELECT_CARD_TO_PLAY, SELECT_CP_SOURCE, SELECT_ATTACKER, SELECT_BLOCKER, SELECT_TARGET } var input_mode: InputMode = InputMode.NONE # Selection tracking var selected_card: CardInstance = null var pending_action: Callable = func(): pass func _ready() -> void: # Wait for CardDatabase to load if CardDatabase.get_all_cards().size() == 0: CardDatabase.database_loaded.connect(_on_database_loaded) else: _on_database_loaded() func _on_database_loaded() -> void: is_initialized = true game_ready.emit() print("GameManager: Ready") ## Start a new game func start_new_game() -> void: if not is_initialized: push_error("GameManager not initialized") return # Create new game state game_state = GameState.new() # Create undo system undo_system = UndoSystem.new(game_state) undo_system.undo_available_changed.connect(_on_undo_available_changed) undo_system.action_undone.connect(_on_action_undone) # Connect signals _connect_game_signals() # Create test decks var deck1 = CardDatabase.create_test_deck(0) var deck2 = CardDatabase.create_test_deck(1) # Setup and start game_state.setup_game(deck1, deck2) game_state.start_game() is_game_active = true game_started.emit() message.emit("Game started!") ## Connect to game state signals func _connect_game_signals() -> void: game_state.game_ended.connect(_on_game_ended) game_state.card_played.connect(_on_card_played) game_state.damage_dealt.connect(_on_damage_dealt) game_state.forward_broken.connect(_on_forward_broken) game_state.attack_declared.connect(_on_attack_declared) game_state.block_declared.connect(_on_block_declared) game_state.combat_resolved.connect(_on_combat_resolved) game_state.turn_manager.turn_changed.connect(_on_turn_changed) game_state.turn_manager.phase_changed.connect(_on_phase_changed) ## Get current player func get_current_player() -> Player: if game_state: return game_state.get_current_player() return null ## Get opponent func get_opponent() -> Player: if game_state: return game_state.get_opponent() return null ## Check if it's a specific player's turn func is_player_turn(player_index: int) -> bool: if game_state: return game_state.turn_manager.current_player_index == player_index return false ## Get current phase func get_current_phase() -> Enums.TurnPhase: if game_state: return game_state.turn_manager.current_phase return Enums.TurnPhase.ACTIVE ## Try to play a card func try_play_card(card: CardInstance) -> bool: if not game_state or not is_game_active: return false var player_index = card.controller_index if not is_player_turn(player_index): message.emit("Not your turn!") return false if not game_state.turn_manager.is_main_phase(): message.emit("Can only play cards during Main Phase!") return false var player = game_state.get_player(player_index) # Check if we can afford the card if not player.cp_pool.can_afford_card(card.card_data): message.emit("Not enough CP!") # Enter CP generation mode input_mode = InputMode.SELECT_CP_SOURCE selected_card = card return false # Check play restrictions before attempting to play var play_error = _check_play_restrictions(player, card) if play_error != "": message.emit(play_error) return false # Determine target zone var to_zone = Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS # Try to play if game_state.play_card(player_index, card): # Record for undo if undo_system: undo_system.record_play_card(player_index, card, to_zone, {}) message.emit("Played " + card.get_display_name()) return true else: # This shouldn't happen if _check_play_restrictions works correctly # but provide context if it does message.emit("Failed to play " + card.get_display_name() + " (internal error)") return false ## Check play restrictions and return error message, or empty string if playable func _check_play_restrictions(player: Player, card: CardInstance) -> String: # Check if card is in hand if not player.hand.has_card(card): return "Card is no longer in your hand!" # Check backup limit if card.is_backup(): if player.field_backups.get_count() >= Player.MAX_BACKUPS: return "Cannot play: Maximum 5 Backups allowed!" # Check unique name restriction (non-generic cards) if not card.card_data.is_generic: if card.is_forward() and player.field_forwards.has_card_with_name(card.card_data.name): return "Cannot play: You already have " + card.card_data.name + " on the field!" if card.is_backup() and player.field_backups.has_card_with_name(card.card_data.name): return "Cannot play: You already have " + card.card_data.name + " on the field!" # Check Light/Dark restriction if card.is_light_or_dark(): if player.field_forwards.has_light_or_dark() or player.field_backups.has_light_or_dark(): return "Cannot play: You can only have one Light/Dark card on the field!" return "" ## Discard a card to generate CP func discard_card_for_cp(card: CardInstance) -> bool: if not game_state or not is_game_active: return false var player_index = card.owner_index var element = card.get_element() if game_state.discard_for_cp(player_index, card): # Record for undo if undo_system: undo_system.record_discard_for_cp(player_index, card, element) message.emit("Discarded " + card.get_display_name() + " for 2 CP") _check_pending_action() return true return false ## Dull a backup to generate CP func dull_backup_for_cp(card: CardInstance) -> bool: if not game_state or not is_game_active: return false var player_index = card.controller_index var element = card.get_element() if game_state.dull_backup_for_cp(player_index, card): # Record for undo if undo_system: undo_system.record_dull_backup_for_cp(player_index, card, element) message.emit("Dulled " + card.get_display_name() + " for 1 CP") _check_pending_action() return true return false ## Check if we can now afford the pending card func _check_pending_action() -> void: if input_mode == InputMode.SELECT_CP_SOURCE and selected_card: var player = game_state.get_player(selected_card.controller_index) # Check if the card is still in hand (it may have been discarded for CP) if not player.hand.has_card(selected_card): message.emit("The card you wanted to play was discarded!") clear_selection() return if player.cp_pool.can_afford_card(selected_card.card_data): # Can now afford - try to play try_play_card(selected_card) clear_selection() ## Declare an attack func declare_attack(card: CardInstance) -> bool: if not game_state or not is_game_active: return false if game_state.declare_attack(card): input_mode = InputMode.SELECT_BLOCKER return true return false ## Declare a block func declare_block(card: CardInstance) -> bool: if not game_state or not is_game_active: return false if game_state.declare_block(card): game_state.resolve_combat() input_mode = InputMode.SELECT_ATTACKER return true return false ## Skip blocking func skip_block() -> bool: if not game_state or not is_game_active: return false if game_state.skip_block(): game_state.resolve_combat() input_mode = InputMode.SELECT_ATTACKER return true return false ## Pass priority / end current phase func pass_priority() -> void: if not game_state or not is_game_active: return var phase = game_state.turn_manager.current_phase match phase: Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2: game_state.end_main_phase() Enums.TurnPhase.ATTACK: if game_state.turn_manager.attack_step == Enums.AttackStep.DECLARATION: game_state.end_attack_phase() elif game_state.turn_manager.attack_step == Enums.AttackStep.BLOCK_DECLARATION: skip_block() clear_selection() ## Clear current selection func clear_selection() -> void: input_mode = InputMode.NONE selected_card = null pending_action = func(): pass ## Undo the last action func undo_last_action() -> bool: if not undo_system: return false return undo_system.undo() ## Check if undo is available func can_undo() -> bool: if not undo_system: return false return undo_system.can_undo() ## Get description of last undoable action func get_undo_description() -> String: if not undo_system: return "" return undo_system.get_last_action_description() ## Restore the input mode based on the current phase func restore_input_mode_for_phase() -> void: if not game_state: return var phase = game_state.turn_manager.current_phase match phase: Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2: input_mode = InputMode.SELECT_CARD_TO_PLAY Enums.TurnPhase.ATTACK: input_mode = InputMode.SELECT_ATTACKER _: input_mode = InputMode.NONE ## Signal handlers func _on_undo_available_changed(available: bool) -> void: undo_available_changed.emit(available) func _on_action_undone(action_name: String) -> void: message.emit("Undid: " + action_name) action_undone.emit(action_name) func _on_game_ended(winner_index: int) -> void: is_game_active = false var winner_name = game_state.get_player(winner_index).player_name game_ended.emit(winner_name) message.emit(winner_name + " wins!") func _on_card_played(card: CardInstance, _player_index: int) -> void: card_played.emit({ "name": card.get_display_name(), "type": Enums.card_type_to_string(card.card_data.type) }) func _on_damage_dealt(player_index: int, amount: int, _cards: Array) -> void: var player_name = game_state.get_player(player_index).player_name damage_dealt.emit(player_name, amount) message.emit(player_name + " takes " + str(amount) + " damage!") func _on_forward_broken(card: CardInstance) -> void: message.emit(card.get_display_name() + " was broken!") func _on_attack_declared(attacker: CardInstance) -> void: message.emit(attacker.get_display_name() + " attacks!") func _on_block_declared(blocker: CardInstance) -> void: message.emit(blocker.get_display_name() + " blocks!") func _on_combat_resolved(_attacker: CardInstance, blocker: CardInstance) -> void: if blocker == null: message.emit("Attack hits!") else: message.emit("Combat resolved!") func _on_turn_changed(player_index: int) -> void: var player = game_state.get_player(player_index) turn_changed.emit(player.player_name, game_state.turn_manager.turn_number) message.emit(player.player_name + "'s turn") func _on_phase_changed(phase: Enums.TurnPhase) -> void: var phase_name = Enums.phase_to_string(phase) phase_changed.emit(phase_name) # Clear undo history on phase change if undo_system: undo_system.clear_history() # Set appropriate input mode match phase: Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2: input_mode = InputMode.SELECT_CARD_TO_PLAY Enums.TurnPhase.ATTACK: input_mode = InputMode.SELECT_ATTACKER _: input_mode = InputMode.NONE