extends Node3D ## Main - Root scene that coordinates the game # Components var table_setup: TableSetup var game_ui: GameUI var hand_display: HandDisplay var hand_layer: CanvasLayer var action_log: ActionLog # Player damage displays var damage_displays: Array[DamageDisplay] = [] func _ready() -> void: _setup_table() _setup_ui() _connect_signals() # Start game when ready if GameManager.is_initialized: _start_game() else: GameManager.game_ready.connect(_start_game) func _setup_table() -> void: table_setup = TableSetup.new() add_child(table_setup) # Connect table signals table_setup.card_clicked.connect(_on_table_card_clicked) func _setup_ui() -> void: # Main game UI overlay (has its own CanvasLayer) game_ui = GameUI.new() game_ui.layer = 10 # Base UI layer add_child(game_ui) # Action log (collapsible panel on right side) var log_layer = CanvasLayer.new() log_layer.layer = 12 # Above other UI add_child(log_layer) action_log = ActionLog.new() log_layer.add_child(action_log) # Position on right side of screen action_log.set_anchors_preset(Control.PRESET_CENTER_RIGHT) action_log.offset_left = -290 action_log.offset_right = -10 action_log.offset_top = -200 action_log.offset_bottom = 200 # Connect undo signal action_log.undo_requested.connect(_on_undo_requested) # Connect to GameManager undo signals GameManager.undo_available_changed.connect(_on_undo_available_changed) # Ensure ActionLog connects to GameManager after it's in the tree action_log.call_deferred("_connect_game_manager_signals") # Hand display needs its own CanvasLayer to render on top of 3D hand_layer = CanvasLayer.new() hand_layer.layer = 11 # Above the game UI add_child(hand_layer) # Container for hand at bottom of screen - must fill the viewport var hand_container = Control.new() hand_layer.add_child(hand_container) hand_container.set_anchors_preset(Control.PRESET_FULL_RECT) hand_container.mouse_filter = Control.MOUSE_FILTER_IGNORE # Hand display positioned at bottom - use explicit positioning hand_display = HandDisplay.new() hand_container.add_child(hand_display) # Position at bottom of screen with explicit coordinates # We'll update position in _process or use a deferred call after container is sized call_deferred("_position_hand_display") hand_display.card_action_requested.connect(_on_hand_card_action) hand_display.card_hovered.connect(_on_hand_card_hovered) hand_display.card_unhovered.connect(_on_hand_card_unhovered) hand_display.card_selected.connect(_on_hand_card_selected) # Create damage displays (positioned by 3D camera overlay later) for i in range(2): var damage_display = DamageDisplay.new() damage_displays.append(damage_display) func _position_hand_display() -> void: # Get viewport size and position hand at bottom var viewport = get_viewport() if viewport: var vp_size = viewport.get_visible_rect().size # Hand cards are 195x273px (triple original), positioned at y=0 in container var card_height = 273.0 var hand_height = card_height + 25.0 # Extra space for hover lift # Position so card bottom is 100px above viewport bottom var bottom_offset = 100.0 hand_display.position = Vector2(10, vp_size.y - bottom_offset - card_height) hand_display.size = Vector2(vp_size.x - 20, hand_height) if not viewport.size_changed.is_connected(_on_viewport_resized): viewport.size_changed.connect(_on_viewport_resized) func _on_viewport_resized() -> void: var viewport = get_viewport() if viewport and hand_display: var vp_size = viewport.get_visible_rect().size var card_height = 273.0 # Triple size hand cards var hand_height = card_height + 25.0 var bottom_offset = 100.0 hand_display.position = Vector2(10, vp_size.y - bottom_offset - card_height) hand_display.size = Vector2(vp_size.x - 20, hand_height) func _connect_signals() -> void: # GameManager signals GameManager.game_started.connect(_on_game_started) GameManager.game_ended.connect(_on_game_ended) GameManager.turn_changed.connect(_on_turn_changed) GameManager.phase_changed.connect(_on_phase_changed) GameManager.damage_dealt.connect(_on_damage_dealt) func _start_game() -> void: GameManager.start_new_game() # Force an update of visuals after a frame to ensure everything is ready call_deferred("_force_initial_update") func _force_initial_update() -> void: _sync_visuals() _update_hand_display() _update_cp_display() func _on_game_started() -> void: _sync_visuals() _update_hand_display() func _on_game_ended(winner_name: String) -> void: game_ui.show_message(winner_name + " wins the game!") func _on_turn_changed(_player_name: String, _turn_number: int) -> void: _sync_visuals() _update_hand_display() _update_cp_display() func _on_phase_changed(_phase_name: String) -> void: _update_playable_highlights() _update_cp_display() func _on_damage_dealt(player_name: String, _amount: int) -> void: # Find player index if GameManager.game_state: for i in range(2): var player = GameManager.game_state.get_player(i) if player and player.player_name == player_name: if i < damage_displays.size(): damage_displays[i].set_damage(player.damage_zone.get_count()) func _sync_visuals() -> void: if GameManager.game_state and table_setup: table_setup.sync_with_game_state(GameManager.game_state) func _update_hand_display() -> void: if not GameManager.game_state: return # Show current player's hand var current_player = GameManager.get_current_player() if current_player: var cards = current_player.hand.get_cards() hand_display.update_hand(cards) func _update_cp_display() -> void: if not GameManager.game_state: return var current_player = GameManager.get_current_player() if current_player: game_ui.update_cp_display(current_player.cp_pool) func _update_playable_highlights() -> void: if not GameManager.game_state: return var phase = GameManager.get_current_phase() var player = GameManager.get_current_player() if not player: hand_display.clear_highlights() return match phase: Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2: # Highlight cards that can be played hand_display.highlight_playable(func(card: CardInstance) -> bool: return player.cp_pool.can_afford_card(card.card_data) ) _: hand_display.clear_highlights() func _on_hand_card_action(card: CardInstance, action: String) -> void: match action: "play": # Try to play the card GameManager.try_play_card(card) _sync_visuals() _update_hand_display() _update_cp_display() "discard_cp": # Discard for CP GameManager.discard_card_for_cp(card) _sync_visuals() _update_hand_display() _update_cp_display() "view": # Show detailed card view game_ui.show_card_detail(card) func _on_hand_card_hovered(_card: CardInstance) -> void: # Hand cards use the selection panel for detail view, not the GameUI hover preview # So we intentionally do nothing here - no hover preview for hand cards pass func _on_hand_card_unhovered() -> void: game_ui.hide_card_detail() func _on_hand_card_selected(_card: CardInstance) -> void: # Selection panel is now visible - ensure any stale hover preview is hidden game_ui.hide_card_detail() func _on_undo_requested() -> void: if GameManager.undo_last_action(): _sync_visuals() _update_hand_display() _update_cp_display() _update_playable_highlights() # Restore input mode based on current phase GameManager.restore_input_mode_for_phase() func _on_undo_available_changed(available: bool) -> void: if action_log: action_log.set_undo_available(available) func _on_table_card_clicked(card: CardInstance, zone_type: Enums.ZoneType, player_index: int) -> void: var input_mode = GameManager.input_mode match input_mode: GameManager.InputMode.SELECT_CP_SOURCE: # Check if it's a backup we can dull if zone_type == Enums.ZoneType.FIELD_BACKUPS: if player_index == GameManager.game_state.turn_manager.current_player_index: GameManager.dull_backup_for_cp(card) _sync_visuals() _update_cp_display() GameManager.InputMode.SELECT_ATTACKER: # Select attacker if zone_type == Enums.ZoneType.FIELD_FORWARDS: if player_index == GameManager.game_state.turn_manager.current_player_index: GameManager.declare_attack(card) _sync_visuals() GameManager.InputMode.SELECT_BLOCKER: # Select blocker if zone_type == Enums.ZoneType.FIELD_FORWARDS: var opponent_index = 1 - GameManager.game_state.turn_manager.current_player_index if player_index == opponent_index: GameManager.declare_block(card) _sync_visuals() # Show card detail on any click game_ui.show_card_detail(card) func _input(event: InputEvent) -> void: # Keyboard shortcuts if event is InputEventKey and event.pressed: # Ctrl+Z for undo if event.keycode == KEY_Z and event.ctrl_pressed: _on_undo_requested() return # L key to toggle action log if event.keycode == KEY_L and not event.ctrl_pressed: if action_log: action_log.toggle_panel() return match event.keycode: KEY_SPACE: # Pass priority / end phase GameManager.pass_priority() _sync_visuals() _update_hand_display() _update_cp_display() KEY_ESCAPE: # Cancel current selection GameManager.clear_selection() table_setup.clear_all_highlights() hand_display.clear_highlights()