feat: Implement comprehensive lobby system with main menu, room management, and loading screen.

This commit is contained in:
Yogi Wiguna
2026-03-17 12:02:20 +08:00
parent b877f94e34
commit 6eb6dfa20d
5 changed files with 71 additions and 37 deletions
+49 -21
View File
@@ -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."""