extends Node ## GameController - Top-level controller managing menu and game states enum State { MENU, DECK_BUILDER, GAME_SETUP, PLAYING, PAUSED, LOGIN, REGISTER, ONLINE_LOBBY, ONLINE_GAME, PROFILE, LEADERBOARD } var current_state: State = State.MENU # Menu window size (matches project.godot viewport) const MENU_SIZE := Vector2i(460, 689) # Deck builder window size const DECK_BUILDER_SIZE := Vector2i(1600, 900) # Game setup window size const GAME_SETUP_SIZE := Vector2i(800, 600) # Game window size const GAME_SIZE := Vector2i(2160, 980) # Login screen size const LOGIN_SIZE := Vector2i(400, 500) # Register screen size const REGISTER_SIZE := Vector2i(400, 600) # Online lobby size const ONLINE_LOBBY_SIZE := Vector2i(600, 700) # Profile screen size const PROFILE_SIZE := Vector2i(600, 700) # Leaderboard screen size const LEADERBOARD_SIZE := Vector2i(600, 700) # Scene references var main_menu: MainMenu = null var deck_builder: DeckBuilder = null var game_setup_menu: GameSetupMenu = null var game_scene: Node3D = null var pause_menu: PauseMenu = null var login_screen: LoginScreen = null var register_screen: RegisterScreen = null var online_lobby: OnlineLobby = null var profile_screen: ProfileScreen = null var leaderboard_screen: LeaderboardScreen = null # Selected decks for gameplay var selected_deck: Deck = null var player1_deck: Array = [] # Card IDs for player 1 var player2_deck: Array = [] # Card IDs for player 2 # AI settings var is_vs_ai: bool = false var ai_difficulty: int = AIStrategy.Difficulty.NORMAL # Online game settings var is_online_game: bool = false var online_game_data: Dictionary = {} var online_pause_menu: Control = null # Preload the main game scene script const MainScript = preload("res://scripts/Main.gd") func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS _show_main_menu() func _input(event: InputEvent) -> void: if event is InputEventKey and event.pressed: if event.keycode == KEY_ESCAPE: match current_state: State.DECK_BUILDER: _on_deck_builder_back() State.GAME_SETUP: _on_game_setup_back() State.PLAYING: _show_pause_menu() State.PAUSED: _hide_pause_menu() State.LOGIN: _on_login_back() State.REGISTER: _on_register_back() State.ONLINE_LOBBY: _on_online_lobby_back() State.ONLINE_GAME: _show_online_pause_menu() State.PROFILE: _on_profile_back() State.LEADERBOARD: _on_leaderboard_back() func _show_main_menu() -> void: # Clean up any existing game if game_scene: game_scene.queue_free() game_scene = null if pause_menu: pause_menu.queue_free() pause_menu = null # Switch back to small borderless menu window DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(MENU_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - MENU_SIZE.x) / 2, (screen.y - MENU_SIZE.y) / 2 )) # Reset viewport to main menu size get_tree().root.content_scale_size = MENU_SIZE # Clean up deck builder if exists if deck_builder: deck_builder.queue_free() deck_builder = null # Clean up game setup menu if exists if game_setup_menu: game_setup_menu.queue_free() game_setup_menu = null # Clean up login screen if exists if login_screen: login_screen.queue_free() login_screen = null # Clean up register screen if exists if register_screen: register_screen.queue_free() register_screen = null # Clean up online lobby if exists if online_lobby: online_lobby.queue_free() online_lobby = null # Clean up profile screen if exists if profile_screen: profile_screen.queue_free() profile_screen = null # Clean up leaderboard screen if exists if leaderboard_screen: leaderboard_screen.queue_free() leaderboard_screen = null if not main_menu: main_menu = MainMenu.new() add_child(main_menu) main_menu.play_game.connect(_on_start_game) main_menu.deck_builder.connect(_on_deck_builder) main_menu.online_game.connect(_on_online_game) main_menu.visible = true current_state = State.MENU func _on_start_game() -> void: # Hide menu if main_menu: main_menu.visible = false # Show game setup menu _show_game_setup_menu() func _on_deck_builder() -> void: # Hide menu if main_menu: main_menu.visible = false # Switch to deck builder window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false) DisplayServer.window_set_size(DECK_BUILDER_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - DECK_BUILDER_SIZE.x) / 2, (screen.y - DECK_BUILDER_SIZE.y) / 2 )) # Set viewport to deck builder size get_tree().root.content_scale_size = DECK_BUILDER_SIZE # Create deck builder if not deck_builder: deck_builder = DeckBuilder.new() add_child(deck_builder) deck_builder.back_pressed.connect(_on_deck_builder_back) deck_builder.deck_selected.connect(_on_deck_selected) deck_builder.visible = true current_state = State.DECK_BUILDER func _on_deck_builder_back() -> void: if deck_builder: deck_builder.visible = false _show_main_menu() func _show_game_setup_menu() -> void: # Switch to game setup window size (borderless like main menu) DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(GAME_SETUP_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - GAME_SETUP_SIZE.x) / 2, (screen.y - GAME_SETUP_SIZE.y) / 2 )) # Resize the root viewport to match the window size to avoid letterboxing get_tree().root.content_scale_size = GAME_SETUP_SIZE # Create game setup menu if not game_setup_menu: game_setup_menu = GameSetupMenu.new() add_child(game_setup_menu) game_setup_menu.back_pressed.connect(_on_game_setup_back) game_setup_menu.start_game_requested.connect(_on_game_setup_start) game_setup_menu.visible = true current_state = State.GAME_SETUP func _on_game_setup_back() -> void: if game_setup_menu: game_setup_menu.visible = false _show_main_menu() func _on_game_setup_start(p1_deck: Array, p2_deck: Array, p_is_vs_ai: bool = false, p_ai_difficulty: int = AIStrategy.Difficulty.NORMAL) -> void: player1_deck = p1_deck player2_deck = p2_deck is_vs_ai = p_is_vs_ai ai_difficulty = p_ai_difficulty if game_setup_menu: game_setup_menu.visible = false # Switch to game window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false) DisplayServer.window_set_size(GAME_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - GAME_SIZE.x) / 2, (screen.y - GAME_SIZE.y) / 2 )) # Set viewport to game size get_tree().root.content_scale_size = GAME_SIZE _start_new_game() func _on_deck_selected(deck: Deck) -> void: selected_deck = deck if deck_builder: deck_builder.visible = false # Switch to game window size and start game DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false) DisplayServer.window_set_size(GAME_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - GAME_SIZE.x) / 2, (screen.y - GAME_SIZE.y) / 2 )) # Set viewport to game size get_tree().root.content_scale_size = GAME_SIZE _start_new_game() func _start_new_game() -> void: # Make sure game isn't paused get_tree().paused = false # Clean up existing game if game_scene: game_scene.queue_free() game_scene = null # Reset GameManager state if GameManager: GameManager.is_game_active = false GameManager.game_state = null # Create new game scene game_scene = Node3D.new() game_scene.set_script(MainScript) # Pass deck configurations if available if player1_deck.size() > 0: game_scene.player1_deck = player1_deck if player2_deck.size() > 0: game_scene.player2_deck = player2_deck # Pass AI configuration game_scene.is_vs_ai = is_vs_ai game_scene.ai_difficulty = ai_difficulty add_child(game_scene) # Create pause menu if not pause_menu: pause_menu = PauseMenu.new() add_child(pause_menu) pause_menu.resume_game.connect(_on_resume_game) pause_menu.restart_game.connect(_on_restart_game) pause_menu.return_to_menu.connect(_on_return_to_menu) current_state = State.PLAYING func _show_pause_menu() -> void: if pause_menu: pause_menu.show_menu() current_state = State.PAUSED func _hide_pause_menu() -> void: if pause_menu: pause_menu.hide_menu() current_state = State.PLAYING func _on_resume_game() -> void: current_state = State.PLAYING func _on_restart_game() -> void: _start_new_game() func _on_return_to_menu() -> void: _show_main_menu() # ======= ONLINE PLAY ======= func _on_online_game() -> void: # Hide menu if main_menu: main_menu.visible = false # Check if already authenticated if NetworkManager and NetworkManager.is_authenticated: _show_online_lobby() else: _show_login_screen() func _show_login_screen() -> void: # Switch to login screen window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(LOGIN_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - LOGIN_SIZE.x) / 2, (screen.y - LOGIN_SIZE.y) / 2 )) # Set viewport to login size get_tree().root.content_scale_size = LOGIN_SIZE # Create login screen if not login_screen: login_screen = LoginScreen.new() add_child(login_screen) login_screen.login_successful.connect(_on_login_successful) login_screen.register_requested.connect(_on_register_requested) login_screen.back_pressed.connect(_on_login_back) login_screen.visible = true login_screen.focus_email() current_state = State.LOGIN func _on_login_back() -> void: if login_screen: login_screen.visible = false login_screen.clear_form() _show_main_menu() func _on_login_successful(_user_data: Dictionary) -> void: if login_screen: login_screen.visible = false login_screen.clear_form() _show_online_lobby() func _on_register_requested() -> void: if login_screen: login_screen.visible = false login_screen.clear_form() _show_register_screen() func _show_register_screen() -> void: # Switch to register screen window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(REGISTER_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - REGISTER_SIZE.x) / 2, (screen.y - REGISTER_SIZE.y) / 2 )) # Set viewport to register size get_tree().root.content_scale_size = REGISTER_SIZE # Create register screen if not register_screen: register_screen = RegisterScreen.new() add_child(register_screen) register_screen.registration_successful.connect(_on_registration_successful) register_screen.login_requested.connect(_on_login_from_register) register_screen.back_pressed.connect(_on_register_back) register_screen.visible = true register_screen.focus_email() current_state = State.REGISTER func _on_register_back() -> void: if register_screen: register_screen.visible = false register_screen.clear_form() _show_main_menu() func _on_registration_successful(_message: String) -> void: # After successful registration, show login screen if register_screen: register_screen.visible = false register_screen.clear_form() _show_login_screen() func _on_login_from_register() -> void: if register_screen: register_screen.visible = false register_screen.clear_form() _show_login_screen() func _show_online_lobby() -> void: # Switch to online lobby window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(ONLINE_LOBBY_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - ONLINE_LOBBY_SIZE.x) / 2, (screen.y - ONLINE_LOBBY_SIZE.y) / 2 )) # Set viewport to online lobby size get_tree().root.content_scale_size = ONLINE_LOBBY_SIZE # Create online lobby if not online_lobby: online_lobby = OnlineLobby.new() add_child(online_lobby) online_lobby.back_pressed.connect(_on_online_lobby_back) online_lobby.game_starting.connect(_on_online_game_starting) online_lobby.profile_requested.connect(_show_profile_screen) online_lobby.leaderboard_requested.connect(_show_leaderboard_screen) # Connect to game_ended signal for handling online game completion if NetworkManager and not NetworkManager.game_ended.is_connected(_on_online_game_ended): NetworkManager.game_ended.connect(_on_online_game_ended) online_lobby.visible = true current_state = State.ONLINE_LOBBY func _on_online_lobby_back() -> void: if online_lobby: online_lobby.visible = false _show_main_menu() func _on_online_game_starting(game_data: Dictionary) -> void: if online_lobby: online_lobby.visible = false # Store game data online_game_data = game_data is_online_game = true var opponent = game_data.get("opponent", {}) print("Starting online game against: ", opponent.get("username", "Unknown")) print("Game ID: ", game_data.get("game_id", "")) print("Local player index: ", game_data.get("your_player_index", 0)) print("First player: ", game_data.get("first_player", 0)) # Switch to game window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, false) DisplayServer.window_set_size(GAME_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - GAME_SIZE.x) / 2, (screen.y - GAME_SIZE.y) / 2 )) # Set viewport to game size get_tree().root.content_scale_size = GAME_SIZE _start_online_game(game_data) func _start_online_game(game_data: Dictionary) -> void: # Make sure game isn't paused get_tree().paused = false # Clean up existing game if game_scene: game_scene.queue_free() game_scene = null # Reset GameManager state if GameManager: GameManager.is_game_active = false GameManager.game_state = null # Create new game scene game_scene = Node3D.new() game_scene.set_script(MainScript) # Mark as online game game_scene.is_online_game = true # Get the deck ID from NetworkManager (set when joining queue/room) var deck_id = game_data.get("deck_id", "") var local_player_index = game_data.get("your_player_index", 0) # Load the selected deck for the local player var local_deck = _load_deck_by_id(deck_id) # For online games, player positions are swapped based on index # The local player is always displayed on the bottom (player 1 position visually) # But the game logic uses the server-assigned indices if local_player_index == 0: game_scene.player1_deck = local_deck game_scene.player2_deck = [] # Opponent's deck is hidden else: game_scene.player1_deck = [] # Opponent's deck is hidden game_scene.player2_deck = local_deck # Pass game configuration game_scene.is_vs_ai = false game_scene.online_game_data = game_data add_child(game_scene) # Setup online game specifics after scene is added game_scene.setup_online_game(game_data) # Create online pause menu (no restart option, has forfeit) _create_online_pause_menu() current_state = State.ONLINE_GAME func _load_deck_by_id(deck_id: String) -> Array: # Load deck from CardDatabase saved decks or starter decks if deck_id.is_empty(): # Use default starter deck return CardDatabase.get_starter_deck_ids("Fire Starter") # Check saved decks var saved_decks = CardDatabase.get_saved_decks() for deck in saved_decks: if deck.get("id", "") == deck_id: return deck.get("card_ids", []) # Check starter decks var starter_decks = CardDatabase.get_starter_decks() for deck in starter_decks: if deck.get("id", "") == deck_id: return deck.get("card_ids", []) # Fallback to first starter deck return CardDatabase.get_starter_deck_ids("Fire Starter") func _create_online_pause_menu() -> void: if online_pause_menu: online_pause_menu.queue_free() online_pause_menu = Control.new() online_pause_menu.set_anchors_preset(Control.PRESET_FULL_RECT) online_pause_menu.visible = false # Semi-transparent background var bg = ColorRect.new() bg.set_anchors_preset(Control.PRESET_FULL_RECT) bg.color = Color(0, 0, 0, 0.7) online_pause_menu.add_child(bg) # Menu container var container = VBoxContainer.new() container.set_anchors_preset(Control.PRESET_CENTER) container.custom_minimum_size = Vector2(300, 200) container.add_theme_constant_override("separation", 20) online_pause_menu.add_child(container) # Title var title = Label.new() title.text = "PAUSED" title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER title.add_theme_font_size_override("font_size", 32) container.add_child(title) # Resume button var resume_btn = Button.new() resume_btn.text = "Resume" resume_btn.custom_minimum_size = Vector2(200, 50) resume_btn.pressed.connect(_hide_online_pause_menu) container.add_child(resume_btn) # Forfeit button var forfeit_btn = Button.new() forfeit_btn.text = "Forfeit Game" forfeit_btn.custom_minimum_size = Vector2(200, 50) forfeit_btn.pressed.connect(_on_forfeit_game) container.add_child(forfeit_btn) add_child(online_pause_menu) func _show_online_pause_menu() -> void: if online_pause_menu: online_pause_menu.visible = true get_tree().paused = true func _hide_online_pause_menu() -> void: if online_pause_menu: online_pause_menu.visible = false get_tree().paused = false func _on_forfeit_game() -> void: # Send concede to server if NetworkManager: NetworkManager.send_concede() _hide_online_pause_menu() # Game end will be handled by the game_ended signal func _on_online_game_ended(_result: Dictionary) -> void: is_online_game = false online_game_data = {} if online_pause_menu: online_pause_menu.queue_free() online_pause_menu = null # Return to online lobby after a delay await get_tree().create_timer(3.0).timeout _show_online_lobby() # ======= PROFILE SCREEN ======= func _show_profile_screen() -> void: # Hide online lobby if online_lobby: online_lobby.visible = false # Switch to profile screen window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(PROFILE_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - PROFILE_SIZE.x) / 2, (screen.y - PROFILE_SIZE.y) / 2 )) # Set viewport to profile size get_tree().root.content_scale_size = PROFILE_SIZE # Create profile screen if not profile_screen: profile_screen = ProfileScreen.new() add_child(profile_screen) profile_screen.back_pressed.connect(_on_profile_back) else: profile_screen.refresh() profile_screen.visible = true current_state = State.PROFILE func _on_profile_back() -> void: if profile_screen: profile_screen.visible = false _show_online_lobby() # ======= LEADERBOARD SCREEN ======= func _show_leaderboard_screen() -> void: # Hide online lobby if online_lobby: online_lobby.visible = false # Switch to leaderboard screen window size DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true) DisplayServer.window_set_size(LEADERBOARD_SIZE) var screen := DisplayServer.screen_get_size() DisplayServer.window_set_position(Vector2i( (screen.x - LEADERBOARD_SIZE.x) / 2, (screen.y - LEADERBOARD_SIZE.y) / 2 )) # Set viewport to leaderboard size get_tree().root.content_scale_size = LEADERBOARD_SIZE # Create leaderboard screen if not leaderboard_screen: leaderboard_screen = LeaderboardScreen.new() add_child(leaderboard_screen) leaderboard_screen.back_pressed.connect(_on_leaderboard_back) else: leaderboard_screen.refresh() leaderboard_screen.visible = true current_state = State.LEADERBOARD func _on_leaderboard_back() -> void: if leaderboard_screen: leaderboard_screen.visible = false _show_online_lobby()