Files
FFCardGame/scripts/GameController.gd
2026-02-02 16:28:53 -05:00

742 lines
20 KiB
GDScript

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()