feat: Implement comprehensive lobby system with main menu, room management, and loading screen.
This commit is contained in:
@@ -31,13 +31,8 @@ border_width_right = 5
|
|||||||
border_width_bottom = 5
|
border_width_bottom = 5
|
||||||
border_color = Color(0, 0, 0, 1)
|
border_color = Color(0, 0, 0, 1)
|
||||||
|
|
||||||
[node name="loading_screen" type="Control"]
|
[node name="loading_screen" type="CanvasLayer"]
|
||||||
layout_mode = 3
|
layer = 128
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
script = ExtResource("1_u2jrd")
|
script = ExtResource("1_u2jrd")
|
||||||
|
|
||||||
[node name="Bg" type="TextureRect" parent="."]
|
[node name="Bg" type="TextureRect" parent="."]
|
||||||
|
|||||||
+2
-1
@@ -708,8 +708,9 @@ func _on_room_left() -> void:
|
|||||||
connection_status.text = "Left room"
|
connection_status.text = "Left room"
|
||||||
|
|
||||||
func _on_host_disconnected() -> void:
|
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")
|
_show_panel("main_menu")
|
||||||
connection_status.text = "Host disconnected. Match terminated."
|
|
||||||
|
|
||||||
func _on_player_joined(player_data: Dictionary) -> void:
|
func _on_player_joined(player_data: Dictionary) -> void:
|
||||||
_update_player_slots()
|
_update_player_slots()
|
||||||
|
|||||||
+42
-14
@@ -460,6 +460,8 @@ func _setup_global_match_timer_ui():
|
|||||||
add_child(panel)
|
add_child(panel)
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
|
if not is_inside_tree(): return
|
||||||
|
if not check_multiplayer(): return
|
||||||
if multiplayer.is_server() and GameStateManager.is_game_started():
|
if multiplayer.is_server() and GameStateManager.is_game_started():
|
||||||
if TurnManager.turn_based_mode:
|
if TurnManager.turn_based_mode:
|
||||||
rpc("sync_turn_index", TurnManager.current_turn_index)
|
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
|
# 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)
|
# 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
|
# Set host goals - get goals directly from GoalManager
|
||||||
var host_goals = GoalManager.get_goals_for_player(0)
|
var host_goals = GoalManager.get_goals_for_player(0)
|
||||||
@@ -651,18 +655,20 @@ func _setup_client_game():
|
|||||||
powerup_ui.setup(player_character)
|
powerup_ui.setup(player_character)
|
||||||
print("Client: PowerUpInventoryUI setup forced for ", my_id)
|
print("Client: PowerUpInventoryUI setup forced for ", my_id)
|
||||||
|
|
||||||
# Wait shorter time for host to be ready, then request full sync to correct positions/state
|
# Wait for host to be ready, then request full sync
|
||||||
await get_tree().create_timer(1.0).timeout
|
# 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)
|
# Ensure we see the server (Peer 1)
|
||||||
if 1 in multiplayer.get_peers():
|
if 1 in multiplayer.get_peers():
|
||||||
rpc_id(1, "request_full_player_sync", my_id)
|
rpc_id(1, "request_full_player_sync", my_id)
|
||||||
rpc_id(1, "request_full_grid_sync")
|
rpc_id(1, "request_full_grid_sync")
|
||||||
else:
|
else:
|
||||||
print("Client: Connected but Peer 1 not found yet. Retrying in 1s...")
|
print("Client: Connected but Peer 1 not found yet. Retrying in 1s...")
|
||||||
await get_tree().create_timer(1.0).timeout
|
await get_tree().create_timer(0.5).timeout
|
||||||
if 1 in multiplayer.get_peers():
|
if check_multiplayer() and 1 in multiplayer.get_peers():
|
||||||
rpc_id(1, "request_full_player_sync", my_id)
|
rpc_id(1, "request_full_player_sync", my_id)
|
||||||
rpc_id(1, "request_full_grid_sync")
|
rpc_id(1, "request_full_grid_sync")
|
||||||
|
|
||||||
@@ -692,6 +698,8 @@ func _auto_start_from_lobby():
|
|||||||
func _start_game():
|
func _start_game():
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
# Wait for Nakama websocket to actually be open, up to 5 seconds
|
# Wait for Nakama websocket to actually be open, up to 5 seconds
|
||||||
|
# SKIP THIS FOR LAN MODE
|
||||||
|
if not LobbyManager.is_lan_mode:
|
||||||
var nakama = get_node_or_null("/root/NakamaManager")
|
var nakama = get_node_or_null("/root/NakamaManager")
|
||||||
if nakama and nakama.has_method("is_connected_to_nakama"):
|
if nakama and nakama.has_method("is_connected_to_nakama"):
|
||||||
var wait_time = 0.0
|
var wait_time = 0.0
|
||||||
@@ -699,10 +707,10 @@ func _start_game():
|
|||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
wait_time += 0.2
|
wait_time += 0.2
|
||||||
|
|
||||||
# Allow socket/peer to stabilize before blasting RPCs
|
# Stabilization delay to allow clients to finish loading and spawning
|
||||||
# Snappier delay since we already waited for scene load
|
# We wait 1.5s to ensure the 1.2s loading screen buffer has finished
|
||||||
var delay = 0.2 if LobbyManager.is_lan_mode else 0.5
|
# before the countdown starts.
|
||||||
await get_tree().create_timer(delay).timeout
|
await get_tree().create_timer(1.5).timeout
|
||||||
|
|
||||||
# NOW assign spawn positions for EVERYONE (Host, Client, Bots)
|
# NOW assign spawn positions for EVERYONE (Host, Client, Bots)
|
||||||
# This safely sends RPCs over the completed socket connection
|
# 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()
|
ui_manager.update_playerboard_ui()
|
||||||
|
|
||||||
func _on_peer_connected(new_peer_id: int):
|
func _on_peer_connected(new_peer_id: int):
|
||||||
|
if not is_inside_tree(): return
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
await get_tree().create_timer(0.1).timeout
|
await get_tree().create_timer(0.1).timeout
|
||||||
add_player_character(new_peer_id)
|
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)
|
add_player_character(new_peer_id)
|
||||||
|
|
||||||
func _on_peer_disconnected(peer_id: int):
|
func _on_peer_disconnected(peer_id: int):
|
||||||
|
if not is_inside_tree(): return
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
print("[Main] Peer %d disconnected. Checking for bot replacement..." % peer_id)
|
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)
|
bot_character.update_player_position(pos)
|
||||||
|
|
||||||
func _on_host_disconnected():
|
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...")
|
print("[Main] Host disconnected. Match terminated. Cleaning up and returning to lobby...")
|
||||||
LobbyManager.leave_room()
|
LobbyManager.leave_room()
|
||||||
|
|
||||||
|
# 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")
|
get_tree().change_scene_to_file("res://scenes/lobby.tscn")
|
||||||
|
|
||||||
func _on_rematch_starting():
|
func _on_rematch_starting():
|
||||||
|
if not is_inside_tree(): return
|
||||||
"""Called when a rematch is triggered. Reloads the game scene."""
|
"""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
|
# Reset singletons/managers that persist across scene reloads
|
||||||
GameStateManager.reset()
|
GameStateManager.reset()
|
||||||
@@ -2439,8 +2459,7 @@ func _on_joystick_toggled(enabled: bool):
|
|||||||
touch_controls._save_settings()
|
touch_controls._save_settings()
|
||||||
|
|
||||||
func can_rpc() -> bool:
|
func can_rpc() -> bool:
|
||||||
if not multiplayer.has_multiplayer_peer(): return false
|
if not check_multiplayer(): return false
|
||||||
if multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED: return false
|
|
||||||
|
|
||||||
if LobbyManager.is_lan_mode:
|
if LobbyManager.is_lan_mode:
|
||||||
return true
|
return true
|
||||||
@@ -2451,6 +2470,15 @@ func can_rpc() -> bool:
|
|||||||
|
|
||||||
return true
|
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")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func display_message(message: String, type: int = 0):
|
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."""
|
"""Broadcasts a message to the local player's UI. This is called via main.rpc from various managers."""
|
||||||
|
|||||||
@@ -290,8 +290,12 @@ func leave_room() -> void:
|
|||||||
reset()
|
reset()
|
||||||
_stop_lan_broadcast()
|
_stop_lan_broadcast()
|
||||||
|
|
||||||
|
# Emit before nulling peer so UI can still access peer info if needed
|
||||||
|
emit_signal("room_left")
|
||||||
|
|
||||||
if is_lan_mode:
|
if is_lan_mode:
|
||||||
# LAN mode: just close the ENet peer directly
|
# 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():
|
if multiplayer.has_multiplayer_peer():
|
||||||
multiplayer.set_multiplayer_peer(null)
|
multiplayer.set_multiplayer_peer(null)
|
||||||
is_lan_mode = false
|
is_lan_mode = false
|
||||||
@@ -302,8 +306,6 @@ func leave_room() -> void:
|
|||||||
# Important: Clean up game state as well to prevent ghost players
|
# Important: Clean up game state as well to prevent ghost players
|
||||||
GameStateManager.reset()
|
GameStateManager.reset()
|
||||||
|
|
||||||
emit_signal("room_left")
|
|
||||||
|
|
||||||
func refresh_room_list() -> void:
|
func refresh_room_list() -> void:
|
||||||
"""Request updated room list from Nakama or scan for LAN rooms."""
|
"""Request updated room list from Nakama or scan for LAN rooms."""
|
||||||
if is_lan_mode:
|
if is_lan_mode:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
extends Control
|
extends CanvasLayer
|
||||||
|
|
||||||
@export var tips: Array[String] = [
|
@export var tips: Array[String] = [
|
||||||
"Use your cards wisely!",
|
"Use your cards wisely!",
|
||||||
@@ -79,6 +79,14 @@ func change_scene(resource: PackedScene):
|
|||||||
|
|
||||||
get_tree().change_scene_to_packed(resource)
|
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)
|
# Clean up self (Loading Screen)
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
@@ -86,7 +94,7 @@ func change_scene(resource: PackedScene):
|
|||||||
func load_level(_path: String):
|
func load_level(_path: String):
|
||||||
print("Starting load for: ", _path)
|
print("Starting load for: ", _path)
|
||||||
path = _path
|
path = _path
|
||||||
show()
|
visible = true
|
||||||
content_control.show()
|
content_control.show()
|
||||||
|
|
||||||
if scene_name_label:
|
if scene_name_label:
|
||||||
|
|||||||
Reference in New Issue
Block a user