diff --git a/scenes/loading_screen/loading_screen.tscn b/scenes/loading_screen/loading_screen.tscn index 66e6b81..4725f64 100644 --- a/scenes/loading_screen/loading_screen.tscn +++ b/scenes/loading_screen/loading_screen.tscn @@ -31,13 +31,8 @@ border_width_right = 5 border_width_bottom = 5 border_color = Color(0, 0, 0, 1) -[node name="loading_screen" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 +[node name="loading_screen" type="CanvasLayer"] +layer = 128 script = ExtResource("1_u2jrd") [node name="Bg" type="TextureRect" parent="."] diff --git a/scenes/lobby.gd b/scenes/lobby.gd index f6c75f2..41c2d8a 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -708,8 +708,9 @@ func _on_room_left() -> void: connection_status.text = "Left room" func _on_host_disconnected() -> void: + # Keep the connection status updated in the UI + connection_status.text = "Host disconnected. Returning to menu..." _show_panel("main_menu") - connection_status.text = "Host disconnected. Match terminated." func _on_player_joined(player_data: Dictionary) -> void: _update_player_slots() diff --git a/scenes/main.gd b/scenes/main.gd index d041747..7048423 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -460,6 +460,8 @@ func _setup_global_match_timer_ui(): add_child(panel) func _process(delta): + if not is_inside_tree(): return + if not check_multiplayer(): return if multiplayer.is_server() and GameStateManager.is_game_started(): if TurnManager.turn_based_mode: rpc("sync_turn_index", TurnManager.current_turn_index) @@ -528,7 +530,9 @@ func _setup_host_game(): # Moved _assign_random_spawn_positions() to after bot loop # Wait for players to be fully ready (player.gd has 0.1s await in _ready before managers init) - await get_tree().create_timer(0.3).timeout + # Faster for LAN mode + var setup_delay = 0.1 if LobbyManager.is_lan_mode else 0.3 + await get_tree().create_timer(setup_delay).timeout # Set host goals - get goals directly from GoalManager var host_goals = GoalManager.get_goals_for_player(0) @@ -651,18 +655,20 @@ func _setup_client_game(): powerup_ui.setup(player_character) print("Client: PowerUpInventoryUI setup forced for ", my_id) - # Wait shorter time for host to be ready, then request full sync to correct positions/state - await get_tree().create_timer(1.0).timeout + # Wait for host to be ready, then request full sync + # Snappier for LAN mode as peer is already established + var client_setup_delay = 0.2 if LobbyManager.is_lan_mode else 1.0 + await get_tree().create_timer(client_setup_delay).timeout - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: + if check_multiplayer(): # Ensure we see the server (Peer 1) if 1 in multiplayer.get_peers(): rpc_id(1, "request_full_player_sync", my_id) rpc_id(1, "request_full_grid_sync") else: print("Client: Connected but Peer 1 not found yet. Retrying in 1s...") - await get_tree().create_timer(1.0).timeout - if 1 in multiplayer.get_peers(): + await get_tree().create_timer(0.5).timeout + if check_multiplayer() and 1 in multiplayer.get_peers(): rpc_id(1, "request_full_player_sync", my_id) rpc_id(1, "request_full_grid_sync") @@ -692,17 +698,19 @@ func _auto_start_from_lobby(): func _start_game(): if multiplayer.is_server(): # Wait for Nakama websocket to actually be open, up to 5 seconds - var nakama = get_node_or_null("/root/NakamaManager") - if nakama and nakama.has_method("is_connected_to_nakama"): - var wait_time = 0.0 - while not nakama.is_connected_to_nakama() and wait_time < 5.0: - await get_tree().create_timer(0.2).timeout - wait_time += 0.2 + # SKIP THIS FOR LAN MODE + if not LobbyManager.is_lan_mode: + var nakama = get_node_or_null("/root/NakamaManager") + if nakama and nakama.has_method("is_connected_to_nakama"): + var wait_time = 0.0 + while not nakama.is_connected_to_nakama() and wait_time < 5.0: + await get_tree().create_timer(0.2).timeout + wait_time += 0.2 - # Allow socket/peer to stabilize before blasting RPCs - # Snappier delay since we already waited for scene load - var delay = 0.2 if LobbyManager.is_lan_mode else 0.5 - await get_tree().create_timer(delay).timeout + # Stabilization delay to allow clients to finish loading and spawning + # We wait 1.5s to ensure the 1.2s loading screen buffer has finished + # before the countdown starts. + await get_tree().create_timer(1.5).timeout # NOW assign spawn positions for EVERYONE (Host, Client, Bots) # This safely sends RPCs over the completed socket connection @@ -1230,6 +1238,7 @@ func add_player_character(peer_id: int, is_bot: bool = false): ui_manager.update_playerboard_ui() func _on_peer_connected(new_peer_id: int): + if not is_inside_tree(): return if multiplayer.is_server(): await get_tree().create_timer(0.1).timeout add_player_character(new_peer_id) @@ -1252,6 +1261,7 @@ func add_newly_connected_player_character(new_peer_id: int): add_player_character(new_peer_id) func _on_peer_disconnected(peer_id: int): + if not is_inside_tree(): return if multiplayer.is_server(): print("[Main] Peer %d disconnected. Checking for bot replacement..." % peer_id) @@ -1305,14 +1315,24 @@ func create_bot_with_state(bot_id: int, pos: Vector2i, p_score: int, p_goals: Ar bot_character.update_player_position(pos) func _on_host_disconnected(): - """Called when the host leaves. Returns clients to the main menu.""" + if not is_inside_tree(): return + """Called when the host leaves. Returns clients to the lobby.""" print("[Main] Host disconnected. Match terminated. Cleaning up and returning to lobby...") LobbyManager.leave_room() - get_tree().change_scene_to_file("res://scenes/lobby.tscn") + + # Use loading screen to return to lobby + var loading_screen_scene = load("res://scenes/loading_screen/loading_screen.tscn") + if loading_screen_scene: + var loading_screen = loading_screen_scene.instantiate() + get_tree().root.add_child(loading_screen) + loading_screen.load_level("res://scenes/lobby.tscn") + else: + get_tree().change_scene_to_file("res://scenes/lobby.tscn") func _on_rematch_starting(): + if not is_inside_tree(): return """Called when a rematch is triggered. Reloads the game scene.""" - print("[Main] Rematch starting! Resetting state and reloading scene...") + print("[Main] Rematch starting. Resetting local state...") # Reset singletons/managers that persist across scene reloads GameStateManager.reset() @@ -2439,8 +2459,7 @@ func _on_joystick_toggled(enabled: bool): touch_controls._save_settings() func can_rpc() -> bool: - if not multiplayer.has_multiplayer_peer(): return false - if multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED: return false + if not check_multiplayer(): return false if LobbyManager.is_lan_mode: return true @@ -2451,6 +2470,15 @@ func can_rpc() -> bool: return true +func check_multiplayer() -> bool: + """Safety check for multiplayer peer access.""" + if not is_inside_tree(): return false + # Accessing multiplayer here is safe because we checked is_inside_tree + var peer = multiplayer.multiplayer_peer + if not peer: return false + if peer.get_connection_status() == MultiplayerPeer.CONNECTION_DISCONNECTED: return false + return true + @rpc("authority", "call_local", "reliable") func display_message(message: String, type: int = 0): """Broadcasts a message to the local player's UI. This is called via main.rpc from various managers.""" diff --git a/scripts/managers/lobby_manager.gd b/scripts/managers/lobby_manager.gd index 459f45c..b49b32f 100644 --- a/scripts/managers/lobby_manager.gd +++ b/scripts/managers/lobby_manager.gd @@ -290,19 +290,21 @@ func leave_room() -> void: reset() _stop_lan_broadcast() + # Emit before nulling peer so UI can still access peer info if needed + emit_signal("room_left") + if is_lan_mode: - # LAN mode: just close the ENet peer directly - if multiplayer.has_multiplayer_peer(): - multiplayer.set_multiplayer_peer(null) - is_lan_mode = false + # LAN mode: Host should keep peer alive long enough to reach lobby + if not is_host or get_tree().current_scene.name == "Lobby": + if multiplayer.has_multiplayer_peer(): + multiplayer.set_multiplayer_peer(null) + is_lan_mode = false else: # Nakama mode: full Nakama cleanup NakamaManager.cleanup() # Important: Clean up game state as well to prevent ghost players GameStateManager.reset() - - emit_signal("room_left") func refresh_room_list() -> void: """Request updated room list from Nakama or scan for LAN rooms.""" diff --git a/scripts/ui/loading_screen.gd b/scripts/ui/loading_screen.gd index 439cb7a..12dda4d 100644 --- a/scripts/ui/loading_screen.gd +++ b/scripts/ui/loading_screen.gd @@ -1,4 +1,4 @@ -extends Control +extends CanvasLayer @export var tips: Array[String] = [ "Use your cards wisely!", @@ -79,6 +79,14 @@ func change_scene(resource: PackedScene): get_tree().change_scene_to_packed(resource) + # Update label to show we are initializing the game world + if scene_name_label: + scene_name_label.text = "Preparing Game..." + + # Wait for assets (Tektons, Spawn Tiles) to initialize in the background + # This keeps the loading screen visible while main.gd runs its _ready() setup + await get_tree().create_timer(1.2).timeout + # Clean up self (Loading Screen) queue_free() @@ -86,7 +94,7 @@ func change_scene(resource: PackedScene): func load_level(_path: String): print("Starting load for: ", _path) path = _path - show() + visible = true content_control.show() if scene_name_label: