feat: Add initial lobby and main scenes with Nakama and lobby management scripts.
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
extends Node
|
||||
|
||||
# LobbyManager - Manages room/lobby state across scenes
|
||||
|
||||
# Signals
|
||||
signal room_list_updated(rooms: Array)
|
||||
signal room_joined(room_data: Dictionary)
|
||||
signal room_left()
|
||||
signal player_joined(player_data: Dictionary)
|
||||
signal player_left(player_id: int)
|
||||
signal ready_state_changed(player_id: int, is_ready: bool)
|
||||
signal all_players_ready()
|
||||
signal game_starting()
|
||||
|
||||
# Room data structure
|
||||
var current_room: Dictionary = {}
|
||||
var players_in_room: Array = [] # [{id, name, is_ready}]
|
||||
var available_rooms: Array = []
|
||||
var is_host: bool = false
|
||||
var local_player_name: String = "Player"
|
||||
|
||||
# Ready to start game check
|
||||
var _all_ready: bool = false
|
||||
|
||||
func _ready():
|
||||
# Connect to Nakama signals
|
||||
NakamaManager.match_joined.connect(_on_match_joined)
|
||||
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||
|
||||
# =============================================================================
|
||||
# Room Creation / Joining
|
||||
# =============================================================================
|
||||
|
||||
func create_room(room_name: String) -> void:
|
||||
"""Host creates a new room with the given name."""
|
||||
is_host = true
|
||||
current_room = {
|
||||
"room_name": room_name,
|
||||
"host_name": local_player_name,
|
||||
"max_players": GameStateManager.max_players
|
||||
}
|
||||
|
||||
# Connect to Nakama and create match
|
||||
var success = await NakamaManager.connect_to_nakama_async()
|
||||
if not success:
|
||||
push_error("Failed to connect to Nakama")
|
||||
return
|
||||
|
||||
NakamaManager.host_game()
|
||||
|
||||
func join_room(match_id: String) -> void:
|
||||
"""Client joins an existing room by match ID."""
|
||||
is_host = false
|
||||
|
||||
var success = await NakamaManager.connect_to_nakama_async()
|
||||
if not success:
|
||||
push_error("Failed to connect to Nakama")
|
||||
return
|
||||
|
||||
NakamaManager.join_game(match_id)
|
||||
|
||||
func leave_room() -> void:
|
||||
"""Leave the current room."""
|
||||
current_room = {}
|
||||
players_in_room.clear()
|
||||
is_host = false
|
||||
_all_ready = false
|
||||
|
||||
# Disconnect from Nakama match
|
||||
if NakamaManager.socket:
|
||||
NakamaManager.socket.close()
|
||||
|
||||
emit_signal("room_left")
|
||||
|
||||
func refresh_room_list() -> void:
|
||||
"""Request updated room list from Nakama."""
|
||||
if not NakamaManager.is_connected_to_nakama():
|
||||
var success = await NakamaManager.connect_to_nakama_async()
|
||||
if not success:
|
||||
return
|
||||
|
||||
var rooms = await NakamaManager.list_matches_async()
|
||||
available_rooms = rooms
|
||||
emit_signal("room_list_updated", rooms)
|
||||
|
||||
# =============================================================================
|
||||
# Ready State Management
|
||||
# =============================================================================
|
||||
|
||||
func set_ready(is_ready: bool) -> void:
|
||||
"""Set local player's ready state."""
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
|
||||
# Update local state
|
||||
for player in players_in_room:
|
||||
if player["id"] == my_id:
|
||||
player["is_ready"] = is_ready
|
||||
break
|
||||
|
||||
# Sync to all peers
|
||||
rpc("sync_ready_state", my_id, is_ready)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_ready_state(player_id: int, is_ready: bool) -> void:
|
||||
"""Sync ready state across all clients."""
|
||||
for player in players_in_room:
|
||||
if player["id"] == player_id:
|
||||
player["is_ready"] = is_ready
|
||||
break
|
||||
|
||||
emit_signal("ready_state_changed", player_id, is_ready)
|
||||
_check_all_ready()
|
||||
|
||||
func _check_all_ready() -> void:
|
||||
"""Check if all players are ready."""
|
||||
if players_in_room.size() < 2:
|
||||
_all_ready = false
|
||||
return
|
||||
|
||||
for player in players_in_room:
|
||||
if not player["is_ready"]:
|
||||
_all_ready = false
|
||||
return
|
||||
|
||||
_all_ready = true
|
||||
emit_signal("all_players_ready")
|
||||
|
||||
func is_all_ready() -> bool:
|
||||
return _all_ready
|
||||
|
||||
# =============================================================================
|
||||
# Game Start
|
||||
# =============================================================================
|
||||
|
||||
func start_game() -> void:
|
||||
"""Host triggers game start (transitions all players to main.tscn)."""
|
||||
if not is_host:
|
||||
push_error("Only host can start the game")
|
||||
return
|
||||
|
||||
if not _all_ready:
|
||||
push_error("Not all players are ready")
|
||||
return
|
||||
|
||||
# Notify all clients to start
|
||||
rpc("_on_game_starting")
|
||||
|
||||
@rpc("call_local", "reliable")
|
||||
func _on_game_starting() -> void:
|
||||
"""Called on all clients when game is starting."""
|
||||
emit_signal("game_starting")
|
||||
# Scene change will be handled by lobby.gd after receiving this signal
|
||||
|
||||
# =============================================================================
|
||||
# Player Management
|
||||
# =============================================================================
|
||||
|
||||
func _on_match_joined(match_id: String) -> void:
|
||||
"""Called when successfully joined a Nakama match."""
|
||||
current_room["match_id"] = match_id
|
||||
# Use first 8 chars of match ID as room name (matches server browser)
|
||||
var short_id = match_id.substr(0, 8) if match_id.length() > 8 else match_id
|
||||
current_room["room_name"] = short_id
|
||||
|
||||
# Add self to player list
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
var my_data = {
|
||||
"id": my_id,
|
||||
"name": local_player_name,
|
||||
"is_ready": false
|
||||
}
|
||||
players_in_room.append(my_data)
|
||||
|
||||
if is_host:
|
||||
# Host is automatically in the room
|
||||
emit_signal("room_joined", current_room)
|
||||
# Client will request room info when peer connection is established
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func request_room_info(requester_id: int) -> void:
|
||||
"""Client requests room info from host."""
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
# Send room data to requester
|
||||
rpc_id(requester_id, "receive_room_info", current_room, players_in_room)
|
||||
|
||||
@rpc("reliable")
|
||||
func receive_room_info(room_data: Dictionary, player_list: Array) -> void:
|
||||
"""Client receives room info from host."""
|
||||
current_room = room_data
|
||||
players_in_room = player_list
|
||||
emit_signal("room_joined", current_room)
|
||||
|
||||
func _on_peer_connected(peer_id: int) -> void:
|
||||
"""Called when new peer connects."""
|
||||
print("Peer connected: ", peer_id)
|
||||
|
||||
if multiplayer.is_server():
|
||||
# Host: add new player and sync list
|
||||
var new_player = {
|
||||
"id": peer_id,
|
||||
"name": "Player %d" % peer_id,
|
||||
"is_ready": false
|
||||
}
|
||||
players_in_room.append(new_player)
|
||||
|
||||
# Sync player list to all clients
|
||||
rpc("sync_player_list", players_in_room)
|
||||
emit_signal("player_joined", new_player)
|
||||
else:
|
||||
# Client: if we connected to the host (peer_id 1), request room info
|
||||
if peer_id == 1 and not is_host:
|
||||
# Wait a frame to ensure connection is stable
|
||||
await get_tree().process_frame
|
||||
rpc_id(1, "request_room_info", multiplayer.get_unique_id())
|
||||
|
||||
func _on_peer_disconnected(peer_id: int) -> void:
|
||||
"""Called when peer disconnects."""
|
||||
for i in range(players_in_room.size()):
|
||||
if players_in_room[i]["id"] == peer_id:
|
||||
players_in_room.remove_at(i)
|
||||
break
|
||||
|
||||
if multiplayer.is_server():
|
||||
rpc("sync_player_list", players_in_room)
|
||||
|
||||
emit_signal("player_left", peer_id)
|
||||
_check_all_ready()
|
||||
|
||||
@rpc("reliable")
|
||||
func sync_player_list(player_list: Array) -> void:
|
||||
"""Sync player list from host to all clients."""
|
||||
players_in_room = player_list
|
||||
|
||||
func get_players() -> Array:
|
||||
return players_in_room
|
||||
|
||||
func get_room_name() -> String:
|
||||
return current_room.get("room_name", "Unknown Room")
|
||||
|
||||
func reset() -> void:
|
||||
"""Reset lobby state."""
|
||||
current_room = {}
|
||||
players_in_room.clear()
|
||||
available_rooms.clear()
|
||||
is_host = false
|
||||
_all_ready = false
|
||||
@@ -0,0 +1 @@
|
||||
uid://d23uvudhylph
|
||||
@@ -112,6 +112,46 @@ func _on_bridge_match_join_error(error) -> void:
|
||||
func is_connected_to_nakama() -> bool:
|
||||
return socket != null and socket.is_connected_to_host()
|
||||
|
||||
# --- Match Listing ---
|
||||
|
||||
func list_matches_async() -> Array:
|
||||
"""Query available matches from Nakama server."""
|
||||
if not client:
|
||||
push_error("Cannot list matches: Client not initialized")
|
||||
return []
|
||||
|
||||
if not session or session.is_expired():
|
||||
push_error("Cannot list matches: No valid session")
|
||||
return []
|
||||
|
||||
print("Querying matches from Nakama server...")
|
||||
|
||||
# Query matches - min 0, max 8 players, limit 20, authoritative=false for relayed matches
|
||||
var result = await client.list_matches_async(session, 0, 8, 20, false, "", "")
|
||||
|
||||
if result.is_exception():
|
||||
printerr("Failed to list matches: ", result.get_exception().message)
|
||||
return []
|
||||
|
||||
var rooms: Array = []
|
||||
if result.matches:
|
||||
print("Found %d matches" % result.matches.size())
|
||||
for match_data in result.matches:
|
||||
print(" Match: ", match_data.match_id, " - Size: ", match_data.size)
|
||||
# Use first 8 chars of match ID as room identifier since Nakama doesn't store custom names
|
||||
var short_id = match_data.match_id.substr(0, 8) if match_data.match_id.length() > 8 else match_data.match_id
|
||||
rooms.append({
|
||||
"match_id": match_data.match_id,
|
||||
"room_name": short_id,
|
||||
"host_name": "Host",
|
||||
"player_count": match_data.size if match_data.size else 1,
|
||||
"max_players": 4
|
||||
})
|
||||
else:
|
||||
print("No matches found")
|
||||
|
||||
return rooms
|
||||
|
||||
func _exit_tree():
|
||||
if socket:
|
||||
socket.close()
|
||||
|
||||
Reference in New Issue
Block a user