feat: Add initial lobby and main scenes with Nakama and lobby management scripts.
This commit is contained in:
+2
-1
@@ -11,7 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="tekton-local"
|
||||
run/main_scene="uid://dxn87yj8qnfpp"
|
||||
run/main_scene="res://scenes/lobby.tscn"
|
||||
config/features=PackedStringArray("4.4", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
@@ -26,6 +26,7 @@ PlayerManager="*res://scripts/managers/player_manager.gd"
|
||||
TurnManager="*res://scripts/managers/turn_manager.gd"
|
||||
GoalManager="*res://scripts/managers/goal_manager.gd"
|
||||
GameStateManager="*res://scripts/managers/game_state_manager.gd"
|
||||
LobbyManager="*res://scripts/managers/lobby_manager.gd"
|
||||
|
||||
[display]
|
||||
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
extends Control
|
||||
|
||||
# UI References - Main Menu
|
||||
@onready var main_menu_panel = $MainMenuPanel
|
||||
@onready var player_name_input = $MainMenuPanel/VBoxContainer/InputSection/PlayerNameInput
|
||||
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
|
||||
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
|
||||
|
||||
# UI References - Room List
|
||||
@onready var room_list_panel = $RoomListPanel
|
||||
@onready var room_list = $RoomListPanel/VBoxContainer/RoomList
|
||||
@onready var match_id_input = $RoomListPanel/VBoxContainer/MatchIdInput
|
||||
@onready var refresh_btn = $RoomListPanel/VBoxContainer/ButtonContainer/RefreshBtn
|
||||
@onready var join_btn = $RoomListPanel/VBoxContainer/ButtonContainer/JoinBtn
|
||||
@onready var back_btn = $RoomListPanel/VBoxContainer/ButtonContainer/BackBtn
|
||||
|
||||
# UI References - Lobby
|
||||
@onready var lobby_panel = $LobbyPanel
|
||||
@onready var room_name_header = $LobbyPanel/VBoxContainer/RoomNameHeader
|
||||
@onready var match_id_display = $LobbyPanel/VBoxContainer/MatchIdContainer/MatchIdDisplay
|
||||
@onready var copy_id_btn = $LobbyPanel/VBoxContainer/MatchIdContainer/CopyIdBtn
|
||||
@onready var player_list = $LobbyPanel/VBoxContainer/PlayerList
|
||||
@onready var status_label = $LobbyPanel/VBoxContainer/StatusLabel
|
||||
@onready var ready_btn = $LobbyPanel/VBoxContainer/ButtonContainer/ReadyBtn
|
||||
@onready var start_game_btn = $LobbyPanel/VBoxContainer/ButtonContainer/StartGameBtn
|
||||
@onready var leave_btn = $LobbyPanel/VBoxContainer/ButtonContainer/LeaveBtn
|
||||
|
||||
# UI References - Status
|
||||
@onready var connection_status = $StatusBar/ConnectionStatus
|
||||
|
||||
# Store current match ID for copy function
|
||||
var current_match_id: String = ""
|
||||
|
||||
func _ready():
|
||||
# Connect button signals
|
||||
create_room_btn.pressed.connect(_on_create_room_pressed)
|
||||
browse_rooms_btn.pressed.connect(_on_browse_rooms_pressed)
|
||||
refresh_btn.pressed.connect(_on_refresh_pressed)
|
||||
join_btn.pressed.connect(_on_join_pressed)
|
||||
back_btn.pressed.connect(_on_back_pressed)
|
||||
ready_btn.toggled.connect(_on_ready_toggled)
|
||||
start_game_btn.pressed.connect(_on_start_game_pressed)
|
||||
leave_btn.pressed.connect(_on_leave_pressed)
|
||||
copy_id_btn.pressed.connect(_on_copy_id_pressed)
|
||||
|
||||
# Connect LobbyManager signals
|
||||
LobbyManager.room_list_updated.connect(_on_room_list_updated)
|
||||
LobbyManager.room_joined.connect(_on_room_joined)
|
||||
LobbyManager.room_left.connect(_on_room_left)
|
||||
LobbyManager.player_joined.connect(_on_player_joined)
|
||||
LobbyManager.player_left.connect(_on_player_left)
|
||||
LobbyManager.ready_state_changed.connect(_on_ready_state_changed)
|
||||
LobbyManager.all_players_ready.connect(_on_all_players_ready)
|
||||
LobbyManager.game_starting.connect(_on_game_starting)
|
||||
|
||||
# Connect NakamaManager signals
|
||||
NakamaManager.connected_to_nakama.connect(_on_connected_to_nakama)
|
||||
NakamaManager.connection_failed.connect(_on_connection_failed)
|
||||
|
||||
# Show main menu initially
|
||||
_show_panel("main_menu")
|
||||
|
||||
# =============================================================================
|
||||
# Panel Management
|
||||
# =============================================================================
|
||||
|
||||
func _show_panel(panel_name: String) -> void:
|
||||
main_menu_panel.visible = panel_name == "main_menu"
|
||||
room_list_panel.visible = panel_name == "room_list"
|
||||
lobby_panel.visible = panel_name == "lobby"
|
||||
|
||||
# =============================================================================
|
||||
# Main Menu Button Handlers
|
||||
# =============================================================================
|
||||
|
||||
func _on_create_room_pressed() -> void:
|
||||
# Set player name
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Host"
|
||||
|
||||
connection_status.text = "Creating room..."
|
||||
# Room name auto-generated since Nakama doesn't support custom names
|
||||
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
|
||||
|
||||
func _on_browse_rooms_pressed() -> void:
|
||||
_show_panel("room_list")
|
||||
connection_status.text = "Loading rooms..."
|
||||
LobbyManager.refresh_room_list()
|
||||
|
||||
# =============================================================================
|
||||
# Room List Button Handlers
|
||||
# =============================================================================
|
||||
|
||||
func _on_refresh_pressed() -> void:
|
||||
connection_status.text = "Refreshing..."
|
||||
room_list.clear()
|
||||
LobbyManager.refresh_room_list()
|
||||
|
||||
func _on_join_pressed() -> void:
|
||||
# First check if there's a match ID entered
|
||||
var match_id = match_id_input.text.strip_edges()
|
||||
|
||||
if match_id.is_empty():
|
||||
# Try to use selected room from list
|
||||
var selected_items = room_list.get_selected_items()
|
||||
if selected_items.size() == 0:
|
||||
connection_status.text = "Please select a room or enter Match ID"
|
||||
return
|
||||
|
||||
var selected_idx = selected_items[0]
|
||||
if selected_idx < LobbyManager.available_rooms.size():
|
||||
match_id = LobbyManager.available_rooms[selected_idx].get("match_id", "")
|
||||
|
||||
if match_id.is_empty():
|
||||
connection_status.text = "No room selected"
|
||||
return
|
||||
|
||||
# Set player name
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Player"
|
||||
|
||||
connection_status.text = "Joining room..."
|
||||
LobbyManager.join_room(match_id)
|
||||
|
||||
func _on_back_pressed() -> void:
|
||||
_show_panel("main_menu")
|
||||
connection_status.text = ""
|
||||
|
||||
# =============================================================================
|
||||
# Lobby Button Handlers
|
||||
# =============================================================================
|
||||
|
||||
func _on_ready_toggled(is_ready: bool) -> void:
|
||||
LobbyManager.set_ready(is_ready)
|
||||
ready_btn.text = "READY ✓" if is_ready else "READY"
|
||||
|
||||
func _on_start_game_pressed() -> void:
|
||||
LobbyManager.start_game()
|
||||
|
||||
func _on_leave_pressed() -> void:
|
||||
LobbyManager.leave_room()
|
||||
_show_panel("main_menu")
|
||||
ready_btn.button_pressed = false
|
||||
ready_btn.text = "READY"
|
||||
|
||||
func _on_copy_id_pressed() -> void:
|
||||
DisplayServer.clipboard_set(current_match_id)
|
||||
connection_status.text = "Match ID copied to clipboard!"
|
||||
|
||||
# =============================================================================
|
||||
# LobbyManager Signal Handlers
|
||||
# =============================================================================
|
||||
|
||||
func _on_room_list_updated(rooms: Array) -> void:
|
||||
room_list.clear()
|
||||
for room in rooms:
|
||||
var room_name = room.get("room_name", "Unknown")
|
||||
var host_name = room.get("host_name", "Unknown")
|
||||
var player_count = room.get("player_count", 1)
|
||||
var max_players = room.get("max_players", 4)
|
||||
room_list.add_item("%s - %s (%d/%d)" % [room_name, host_name, player_count, max_players])
|
||||
|
||||
if rooms.size() == 0:
|
||||
connection_status.text = "No rooms available"
|
||||
else:
|
||||
connection_status.text = "Found %d room(s)" % rooms.size()
|
||||
|
||||
func _on_room_joined(room_data: Dictionary) -> void:
|
||||
_show_panel("lobby")
|
||||
current_match_id = room_data.get("match_id", "")
|
||||
room_name_header.text = "ROOM: %s" % room_data.get("room_name", "Unknown")
|
||||
match_id_display.text = "Match ID: %s " % current_match_id
|
||||
|
||||
# Update start button visibility (host only)
|
||||
start_game_btn.visible = LobbyManager.is_host
|
||||
|
||||
_update_player_list()
|
||||
connection_status.text = "Connected to room"
|
||||
|
||||
func _on_room_left() -> void:
|
||||
_show_panel("main_menu")
|
||||
connection_status.text = "Left room"
|
||||
|
||||
func _on_player_joined(player_data: Dictionary) -> void:
|
||||
_update_player_list()
|
||||
status_label.text = "%s joined!" % player_data.get("name", "Player")
|
||||
|
||||
func _on_player_left(_player_id: int) -> void:
|
||||
_update_player_list()
|
||||
status_label.text = "A player left"
|
||||
|
||||
func _on_ready_state_changed(_player_id: int, _is_ready: bool) -> void:
|
||||
_update_player_list()
|
||||
_update_status()
|
||||
|
||||
func _on_all_players_ready() -> void:
|
||||
if LobbyManager.is_host:
|
||||
start_game_btn.disabled = false
|
||||
status_label.text = "All players ready! Host can start."
|
||||
else:
|
||||
status_label.text = "All players ready! Waiting for host..."
|
||||
|
||||
func _on_game_starting() -> void:
|
||||
connection_status.text = "Starting game..."
|
||||
# Small delay for visual feedback
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
get_tree().change_scene_to_file("res://scenes/main.tscn")
|
||||
|
||||
# =============================================================================
|
||||
# NakamaManager Signal Handlers
|
||||
# =============================================================================
|
||||
|
||||
func _on_connected_to_nakama() -> void:
|
||||
connection_status.text = "Connected to server"
|
||||
|
||||
func _on_connection_failed(error_message: String) -> void:
|
||||
connection_status.text = "Connection failed: %s" % error_message
|
||||
_show_panel("main_menu")
|
||||
|
||||
# =============================================================================
|
||||
# Helper Functions
|
||||
# =============================================================================
|
||||
|
||||
func _update_player_list() -> void:
|
||||
player_list.clear()
|
||||
var players = LobbyManager.get_players()
|
||||
for player in players:
|
||||
var player_name = player.get("name", "Unknown")
|
||||
var is_ready = player.get("is_ready", false)
|
||||
var ready_icon = " ✓" if is_ready else " ✗"
|
||||
var host_tag = " (Host)" if player.get("id") == 1 else ""
|
||||
player_list.add_item("%s%s%s" % [player_name, host_tag, ready_icon])
|
||||
|
||||
func _update_status() -> void:
|
||||
var players = LobbyManager.get_players()
|
||||
var ready_count = 0
|
||||
for player in players:
|
||||
if player.get("is_ready", false):
|
||||
ready_count += 1
|
||||
|
||||
status_label.text = "Ready: %d/%d" % [ready_count, players.size()]
|
||||
|
||||
if LobbyManager.is_host:
|
||||
start_game_btn.disabled = not LobbyManager.is_all_ready()
|
||||
@@ -0,0 +1 @@
|
||||
uid://b5q6yekyk0tld
|
||||
@@ -0,0 +1,448 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://b7nxt2hc4kqp8"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b5q6yekyk0tld" path="res://scenes/lobby.gd" id="1_lobby"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_panel"]
|
||||
content_margin_left = 24.0
|
||||
content_margin_top = 20.0
|
||||
content_margin_right = 24.0
|
||||
content_margin_bottom = 20.0
|
||||
bg_color = Color(0.12, 0.12, 0.14, 0.95)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.9, 0.45, 0.1, 0.8)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_input"]
|
||||
content_margin_left = 12.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 12.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.08, 0.08, 0.1, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.3, 0.3, 0.35, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_hover"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.9, 0.45, 0.1, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(1, 0.6, 0.2, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_pressed"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.7, 0.35, 0.05, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.9, 0.5, 0.15, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_button_normal"]
|
||||
content_margin_left = 16.0
|
||||
content_margin_top = 8.0
|
||||
content_margin_right = 16.0
|
||||
content_margin_bottom = 8.0
|
||||
bg_color = Color(0.18, 0.18, 0.2, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.4, 0.4, 0.45, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[node name="Lobby" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_lobby")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.06, 0.06, 0.08, 1)
|
||||
|
||||
[node name="BackgroundPattern" type="ColorRect" parent="."]
|
||||
modulate = Color(1, 1, 1, 0.03)
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.9, 0.45, 0.1, 1)
|
||||
|
||||
[node name="MainMenuPanel" type="PanelContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -220.0
|
||||
offset_top = -220.0
|
||||
offset_right = 220.0
|
||||
offset_bottom = 220.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_panel")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MainMenuPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="TitleContainer" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="Title" type="Label" parent="MainMenuPanel/VBoxContainer/TitleContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.95, 0.5, 0.15, 1)
|
||||
theme_override_font_sizes/font_size = 42
|
||||
text = "TEKTON DASH"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Subtitle" type="Label" parent="MainMenuPanel/VBoxContainer/TitleContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.5, 0.5, 0.55, 1)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "ARMAGEDDON VERSION"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Separator" type="HSeparator" parent="MainMenuPanel/VBoxContainer"]
|
||||
modulate = Color(0.9, 0.45, 0.1, 0.5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InputSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="PlayerNameLabel" type="Label" parent="MainMenuPanel/VBoxContainer/InputSection"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.7, 0.7, 0.75, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "YOUR NAME"
|
||||
|
||||
[node name="PlayerNameInput" type="LineEdit" parent="MainMenuPanel/VBoxContainer/InputSection"]
|
||||
custom_minimum_size = Vector2(0, 36)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_placeholder_color = Color(0.4, 0.4, 0.45, 1)
|
||||
theme_override_colors/font_color = Color(0.9, 0.9, 0.95, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input")
|
||||
text = "Player"
|
||||
placeholder_text = "Enter your name..."
|
||||
|
||||
[node name="ButtonSection" type="VBoxContainer" parent="MainMenuPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="CreateRoomBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection"]
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "CREATE ROOM"
|
||||
|
||||
[node name="BrowseRoomsBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection"]
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_colors/font_pressed_color = Color(1, 1, 1, 1)
|
||||
theme_override_font_sizes/font_size = 15
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "BROWSE ROOMS"
|
||||
|
||||
[node name="RoomListPanel" type="PanelContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -280.0
|
||||
offset_top = -240.0
|
||||
offset_right = 280.0
|
||||
offset_bottom = 240.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_panel")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="RoomListPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="Header" type="Label" parent="RoomListPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.95, 0.5, 0.15, 1)
|
||||
theme_override_font_sizes/font_size = 28
|
||||
text = "SERVER BROWSER"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="RoomListPanel/VBoxContainer"]
|
||||
modulate = Color(0.9, 0.45, 0.1, 0.5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MatchIdLabel" type="Label" parent="RoomListPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.6, 0.6, 0.65, 1)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "DIRECT CONNECT (MATCH ID)"
|
||||
|
||||
[node name="MatchIdInput" type="LineEdit" parent="RoomListPanel/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 36)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_placeholder_color = Color(0.4, 0.4, 0.45, 1)
|
||||
theme_override_colors/font_color = Color(0.9, 0.9, 0.95, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_input")
|
||||
placeholder_text = "Paste match ID here..."
|
||||
|
||||
[node name="RoomListLabel" type="Label" parent="RoomListPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.6, 0.6, 0.65, 1)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "AVAILABLE SERVERS"
|
||||
|
||||
[node name="RoomList" type="ItemList" parent="RoomListPanel/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 180)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_selected_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.85, 0.85, 0.9, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
allow_reselect = true
|
||||
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="RoomListPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
alignment = 1
|
||||
|
||||
[node name="RefreshBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(100, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "REFRESH"
|
||||
|
||||
[node name="JoinBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(120, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "JOIN SERVER"
|
||||
|
||||
[node name="BackBtn" type="Button" parent="RoomListPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(100, 40)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "BACK"
|
||||
|
||||
[node name="LobbyPanel" type="PanelContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -280.0
|
||||
offset_top = -240.0
|
||||
offset_right = 280.0
|
||||
offset_bottom = 240.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_panel")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="LobbyPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="RoomNameHeader" type="Label" parent="LobbyPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.95, 0.5, 0.15, 1)
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "ROOM: "
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="MatchIdContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="MatchIdDisplay" type="Label" parent="LobbyPanel/VBoxContainer/MatchIdContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.45, 0.45, 0.5, 1)
|
||||
theme_override_font_sizes/font_size = 10
|
||||
text = "Match ID: "
|
||||
|
||||
[node name="CopyIdBtn" type="Button" parent="LobbyPanel/VBoxContainer/MatchIdContainer"]
|
||||
custom_minimum_size = Vector2(70, 24)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.7, 0.7, 0.75, 1)
|
||||
theme_override_font_sizes/font_size = 10
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "COPY"
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="LobbyPanel/VBoxContainer"]
|
||||
modulate = Color(0.9, 0.45, 0.1, 0.5)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PlayersLabel" type="Label" parent="LobbyPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.6, 0.6, 0.65, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "PLAYERS"
|
||||
|
||||
[node name="PlayerList" type="ItemList" parent="LobbyPanel/VBoxContainer"]
|
||||
custom_minimum_size = Vector2(0, 140)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_selected_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.85, 0.85, 0.9, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
allow_reselect = true
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="LobbyPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.95, 0.6, 0.2, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
text = "Waiting for players..."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="LobbyPanel/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
alignment = 1
|
||||
|
||||
[node name="ReadyBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(100, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
toggle_mode = true
|
||||
text = "READY"
|
||||
|
||||
[node name="StartGameBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(130, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
disabled = true
|
||||
text = "START GAME"
|
||||
|
||||
[node name="LeaveBtn" type="Button" parent="LobbyPanel/VBoxContainer/ButtonContainer"]
|
||||
custom_minimum_size = Vector2(100, 44)
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_hover_color = Color(0.1, 0.1, 0.12, 1)
|
||||
theme_override_colors/font_color = Color(0.95, 0.95, 1, 1)
|
||||
theme_override_font_sizes/font_size = 14
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_button_hover")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_button_pressed")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_button_normal")
|
||||
text = "LEAVE"
|
||||
|
||||
[node name="StatusBar" type="PanelContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 39.0
|
||||
offset_top = -83.0
|
||||
offset_right = -39.0
|
||||
offset_bottom = -26.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_panel")
|
||||
|
||||
[node name="ConnectionStatus" type="Label" parent="StatusBar"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.5, 0.5, 0.55, 1)
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "NOT CONNECTED"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="VersionLabel" type="Label" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -126.0
|
||||
offset_top = 19.0
|
||||
offset_right = -18.0
|
||||
offset_bottom = 37.0
|
||||
grow_horizontal = 0
|
||||
theme_override_colors/font_color = Color(0.35, 0.35, 0.4, 1)
|
||||
theme_override_font_sizes/font_size = 11
|
||||
text = "v0.1.0 ALPHA"
|
||||
horizontal_alignment = 2
|
||||
+73
-38
@@ -9,7 +9,6 @@ var ui_manager
|
||||
var obstacle_manager
|
||||
|
||||
# Minimal local state
|
||||
var match_id_input: LineEdit
|
||||
var _connection_check_timer: float = 0.0
|
||||
|
||||
func _ready():
|
||||
@@ -27,7 +26,12 @@ func _ready():
|
||||
ui_manager.setup_action_buttons(_set_action_state_callback)
|
||||
ui_manager.setup_playerboard_ui()
|
||||
_setup_obstacle_ui()
|
||||
_setup_match_input()
|
||||
|
||||
# Auto-start game if coming from lobby (already connected to match)
|
||||
if NakamaManager.is_connected_to_nakama() and multiplayer.get_unique_id() != 0:
|
||||
print("Coming from lobby - auto-starting game...")
|
||||
await get_tree().process_frame
|
||||
_auto_start_from_lobby()
|
||||
|
||||
func _init_managers():
|
||||
# Create and attach scene managers
|
||||
@@ -41,13 +45,6 @@ func _init_managers():
|
||||
add_child(obstacle_manager)
|
||||
obstacle_manager.initialize($EnhancedGridMap)
|
||||
|
||||
func _setup_match_input():
|
||||
match_id_input = LineEdit.new()
|
||||
match_id_input.placeholder_text = "Enter Match ID to Join"
|
||||
match_id_input.custom_minimum_size = Vector2(200, 30)
|
||||
match_id_input.position = Vector2(10, 50)
|
||||
$Menu.add_child(match_id_input)
|
||||
|
||||
func _setup_obstacle_ui():
|
||||
var obstacle_button = Button.new()
|
||||
obstacle_button.text = "Place Obstacle"
|
||||
@@ -80,34 +77,9 @@ func _process(delta):
|
||||
verify_all_connections()
|
||||
|
||||
# =============================================================================
|
||||
# Network Button Handlers
|
||||
# Network Callbacks
|
||||
# =============================================================================
|
||||
|
||||
func _on_host_pressed():
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Server (Creating Match...)"
|
||||
$Menu.visible = false
|
||||
var success = await NakamaManager.connect_to_nakama_async()
|
||||
if not success:
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed"
|
||||
$Menu.visible = true
|
||||
return
|
||||
NakamaManager.host_game()
|
||||
|
||||
func _on_join_pressed():
|
||||
var match_id = match_id_input.text.strip_edges()
|
||||
if match_id.is_empty():
|
||||
print("Please enter a Match ID")
|
||||
return
|
||||
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Client (Joining...)"
|
||||
$Menu.visible = false
|
||||
var success = await NakamaManager.connect_to_nakama_async()
|
||||
if not success:
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed"
|
||||
$Menu.visible = true
|
||||
return
|
||||
NakamaManager.join_game(match_id)
|
||||
|
||||
func _on_match_joined(match_id: String):
|
||||
$NetworkPanel/NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id())
|
||||
|
||||
@@ -151,16 +123,79 @@ func _setup_host_game():
|
||||
_update_player_goals_ui(0, host_goals)
|
||||
ui_manager.update_playerboard_ui()
|
||||
|
||||
# Add bots
|
||||
if GameStateManager.enable_bots:
|
||||
# Spawn client players that joined via lobby
|
||||
var lobby_players = LobbyManager.get_players()
|
||||
for lobby_player in lobby_players:
|
||||
var peer_id = lobby_player.get("id", 0)
|
||||
if peer_id != 1 and peer_id != 0: # Skip host (1) and invalid (0)
|
||||
print("Spawning lobby player: ", peer_id)
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
_spawn_lobby_client(peer_id)
|
||||
|
||||
# Add bots (only if no lobby players connected)
|
||||
if GameStateManager.enable_bots and lobby_players.size() <= 1:
|
||||
for i in range(2, GameStateManager.max_players + 1):
|
||||
_add_bot(i)
|
||||
|
||||
_start_game()
|
||||
|
||||
func _spawn_lobby_client(peer_id: int):
|
||||
"""Spawn a client player that was in the lobby."""
|
||||
if has_node(str(peer_id)):
|
||||
return
|
||||
|
||||
var player_character = PlayerManager.add_player_character(peer_id)
|
||||
add_child(player_character)
|
||||
player_character.add_to_group("Players", true)
|
||||
GameStateManager.add_player(peer_id)
|
||||
|
||||
# Tell all clients to create this player
|
||||
rpc("add_newly_connected_player_character", peer_id)
|
||||
|
||||
# Wait for player to be ready then assign goals
|
||||
await get_tree().create_timer(0.3).timeout
|
||||
var player_index = GameStateManager.players.find(peer_id)
|
||||
if player_index >= 0 and player_index < GoalManager.preset_goals.size():
|
||||
var player_goals = GoalManager.preset_goals[player_index].duplicate()
|
||||
player_character.goals = player_goals
|
||||
call_deferred("_deferred_set_player_goals", peer_id, player_goals)
|
||||
|
||||
func _setup_client_game():
|
||||
"""Setup client when transitioning from lobby."""
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
print("Client setup - my peer ID: ", my_id)
|
||||
|
||||
# Create local player immediately
|
||||
if not has_node(str(my_id)):
|
||||
var player_character = PlayerManager.add_player_character(my_id)
|
||||
add_child(player_character)
|
||||
player_character.add_to_group("Players", true)
|
||||
GameStateManager.add_player(my_id)
|
||||
GameStateManager.local_player_character = player_character
|
||||
ui_manager.set_local_player(player_character)
|
||||
ui_manager.update_button_states()
|
||||
print("Created local player for client: ", my_id)
|
||||
|
||||
# Wait for host to be ready, then request full sync
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
rpc_id(1, "request_full_player_sync", multiplayer.get_unique_id())
|
||||
rpc_id(1, "request_full_player_sync", my_id)
|
||||
|
||||
func _auto_start_from_lobby():
|
||||
"""Called when main.tscn is loaded from lobby - game is already connected."""
|
||||
$NetworkPanel/NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id())
|
||||
|
||||
# Get match ID from LobbyManager
|
||||
var match_id = LobbyManager.current_room.get("match_id", "")
|
||||
var short_id = match_id.substr(0, 8) if match_id.length() > 8 else match_id
|
||||
|
||||
if multiplayer.is_server():
|
||||
print("Auto-starting as HOST - Match: ", short_id)
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Host (Match: %s)" % short_id
|
||||
_setup_host_game()
|
||||
else:
|
||||
print("Auto-starting as CLIENT - Match: ", short_id)
|
||||
$NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Client (Match: %s)" % short_id
|
||||
_setup_client_game()
|
||||
|
||||
func _start_game():
|
||||
if multiplayer.is_server():
|
||||
|
||||
@@ -994,6 +994,7 @@ layout_mode = 2
|
||||
text = "Arrange"
|
||||
|
||||
[node name="Menu" type="VBoxContainer" parent="."]
|
||||
visible = false
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
|
||||
@@ -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