feat: Implement core multiplayer features including user authentication, profile management, lobby, game mode managers, and leaderboard.

This commit is contained in:
2026-03-12 03:55:20 +08:00
parent 650d241a72
commit 4f6783b468
13 changed files with 1151 additions and 31 deletions
+183 -11
View File
@@ -12,6 +12,9 @@ extends Control
@onready var server_option = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerOption
@onready var server_ip_input = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerIPInput
# Leaderboard Reference
@onready var leaderboard_btn = $MainMenuPanel/VBoxContainer/ButtonSection/LeaderboardBtn
# UI References - Room List
@onready var room_list_panel = $RoomListPanel
@onready var room_list = $RoomListPanel/VBoxContainer/RoomList
@@ -42,9 +45,23 @@ extends Control
@onready var enable_timer_label = $LobbyPanel/TopBar/SettingsSection/EnableTimerLabel
@onready var scarcity_option = $LobbyPanel/TopBar/SettingsSection/ScarcityOption
@onready var scarcity_label = $LobbyPanel/TopBar/SettingsSection/ScarcityLabel
@onready var scarcity_spacer = $LobbyPanel/TopBar/SettingsSection/ScarcitySpacer
@onready var spawn_spacer = $LobbyPanel/TopBar/SettingsSection/SpawnSpacer
@onready var timer_spacer = $LobbyPanel/TopBar/SettingsSection/TimerSpacer
@onready var game_mode_option = $LobbyPanel/TopBar/SettingsSection/GameModeOption
@onready var game_mode_text_label = $LobbyPanel/TopBar/SettingsSection/GameModeTextLabel
# Custom Settings Containers
var sng_settings_container: HBoxContainer
var sng_go_option: OptionButton
var sng_stop_option: OptionButton
var sng_goals_option: OptionButton
var doors_settings_container: HBoxContainer
var doors_swap_option: OptionButton
var doors_refresh_option: OptionButton
var doors_goals_option: OptionButton
# UI References - Player Slots
@onready var players_container = $LobbyPanel/PlayersContainer
@onready var players_container2 = $LobbyPanel/PlayersContainer2
@@ -75,6 +92,8 @@ var admin_panel_instance: Control
# Store current match ID for copy function
var current_match_id: String = ""
var leaderboard_panel_instance: Control
# Server Selection Controls (Now in tscn)
# var server_option: OptionButton
# var server_ip_input: LineEdit
@@ -92,6 +111,9 @@ func _ready():
# Get player slot references
_setup_player_slots()
# Setup Game Mode specific UI dynamically
_create_custom_settings_ui()
# Set player name from profile and configure input visibility
if player_name_input:
# Get the parent container for the input to hide/show properly
@@ -118,6 +140,8 @@ func _ready():
main_menu_profile_btn.pressed.connect(_on_profile_btn_pressed)
if lobby_settings_btn:
lobby_settings_btn.pressed.connect(_on_settings_pressed)
if leaderboard_btn:
leaderboard_btn.pressed.connect(_on_leaderboard_pressed)
# Connect Server Selection signals
if server_option:
@@ -176,6 +200,13 @@ func _ready():
LobbyManager.game_mode_changed.connect(_on_game_mode_changed)
LobbyManager.player_list_changed.connect(_update_player_slots)
LobbyManager.sng_go_duration_changed.connect(_on_sng_update)
LobbyManager.sng_stop_duration_changed.connect(_on_sng_update)
LobbyManager.sng_required_goals_changed.connect(_on_sng_update)
LobbyManager.doors_swap_time_changed.connect(_on_doors_update)
LobbyManager.doors_refresh_time_changed.connect(_on_doors_update)
LobbyManager.doors_required_goals_changed.connect(_on_doors_update)
# Connect NakamaManager signals
NakamaManager.connected_to_nakama.connect(_on_connected_to_nakama)
NakamaManager.connection_failed.connect(_on_connection_failed)
@@ -331,6 +362,116 @@ func _on_back_pressed() -> void:
_show_panel("main_menu")
connection_status.text = ""
func _update_settings_visibility() -> void:
var is_host = LobbyManager.is_host
var is_freemode = LobbyManager.game_mode == "Freemode"
# Duration
var show_duration = is_freemode
duration_option.visible = is_host and show_duration
duration_text_label.visible = not is_host and show_duration
$LobbyPanel/TopBar/SettingsSection/DurationLabel.visible = show_duration
# Random Spawn
var show_spawn = is_freemode
random_spawn_check.visible = is_host and show_spawn
random_spawn_label.visible = not is_host and show_spawn
if spawn_spacer: spawn_spacer.visible = show_spawn
# Timer
var show_timer = is_freemode
enable_timer_check.visible = is_host and show_timer
enable_timer_label.visible = not is_host and show_timer
if timer_spacer: timer_spacer.visible = show_timer
# Scarcity
var show_scarcity = is_freemode
if scarcity_option: scarcity_option.visible = is_host and show_scarcity
if scarcity_label: scarcity_label.visible = not is_host and show_scarcity
if scarcity_spacer: scarcity_spacer.visible = show_scarcity
# Custom mode sets
var is_sng = LobbyManager.game_mode == "Stop n Go"
if sng_settings_container:
sng_settings_container.visible = is_sng
sng_go_option.disabled = not is_host
sng_stop_option.disabled = not is_host
sng_goals_option.disabled = not is_host
var is_doors = LobbyManager.game_mode == "Tekton Doors"
if doors_settings_container:
doors_settings_container.visible = is_doors
doors_swap_option.disabled = not is_host
doors_refresh_option.disabled = not is_host
doors_goals_option.disabled = not is_host
func _create_custom_settings_ui() -> void:
var settings_section = $LobbyPanel/TopBar/SettingsSection
if not settings_section: return
# Stop n Go
sng_settings_container = HBoxContainer.new()
sng_settings_container.visible = false
settings_section.add_child(sng_settings_container)
_add_label(sng_settings_container, "Go Time:")
sng_go_option = OptionButton.new()
sng_go_option.add_item("10s"); sng_go_option.add_item("15s"); sng_go_option.add_item("25s")
sng_go_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_sng_go_duration([10, 15, 25][idx]))
sng_settings_container.add_child(sng_go_option)
_add_label(sng_settings_container, "Stop Time:")
sng_stop_option = OptionButton.new()
sng_stop_option.add_item("3s"); sng_stop_option.add_item("4s"); sng_stop_option.add_item("5s")
sng_stop_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_sng_stop_duration([3, 4, 5][idx]))
sng_settings_container.add_child(sng_stop_option)
_add_label(sng_settings_container, "Req Goals:")
sng_goals_option = OptionButton.new()
sng_goals_option.add_item("5"); sng_goals_option.add_item("8"); sng_goals_option.add_item("12")
sng_goals_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_sng_required_goals([5, 8, 12][idx]))
sng_settings_container.add_child(sng_goals_option)
# Tekton Doors
doors_settings_container = HBoxContainer.new()
doors_settings_container.visible = false
settings_section.add_child(doors_settings_container)
_add_label(doors_settings_container, "Swap Wait:")
doors_swap_option = OptionButton.new()
doors_swap_option.add_item("10s"); doors_swap_option.add_item("15s"); doors_swap_option.add_item("30s")
doors_swap_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_doors_swap_time([10, 15, 30][idx]))
doors_settings_container.add_child(doors_swap_option)
_add_label(doors_settings_container, "Tile Refresh:")
doors_refresh_option = OptionButton.new()
doors_refresh_option.add_item("15s"); doors_refresh_option.add_item("25s"); doors_refresh_option.add_item("40s")
doors_refresh_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_doors_refresh_time([15, 25, 40][idx]))
doors_settings_container.add_child(doors_refresh_option)
_add_label(doors_settings_container, "Req Goals:")
doors_goals_option = OptionButton.new()
doors_goals_option.add_item("5"); doors_goals_option.add_item("8"); doors_goals_option.add_item("12")
doors_goals_option.item_selected.connect(func(idx): if LobbyManager.is_host: LobbyManager.set_doors_required_goals([5, 8, 12][idx]))
doors_settings_container.add_child(doors_goals_option)
# Move Game Mode selector to the far right
var gm_spacer = settings_section.get_node_or_null("GameModeSpacer")
var gm_option = settings_section.get_node_or_null("GameModeOption")
var gm_label = settings_section.get_node_or_null("GameModeTextLabel")
if gm_spacer: settings_section.move_child(gm_spacer, -1)
if gm_option: settings_section.move_child(gm_option, -1)
if gm_label: settings_section.move_child(gm_label, -1)
func _add_label(parent: Control, text: String):
var spacer = Control.new()
spacer.custom_minimum_size = Vector2(10, 0)
parent.add_child(spacer)
var lbl = Label.new()
lbl.text = text
parent.add_child(lbl)
# =============================================================================
# Lobby Button Handlers
# =============================================================================
@@ -393,7 +534,32 @@ func _on_game_mode_changed(mode: String) -> void:
break
if game_mode_text_label:
game_mode_text_label.text = mode
_update_settings_visibility()
func _on_sng_update(_val: int = 0) -> void:
if not sng_go_option: return
var go_idx = [10, 15, 25].find(LobbyManager.sng_go_duration)
if go_idx != -1: sng_go_option.selected = go_idx
var stop_idx = [3, 4, 5].find(LobbyManager.sng_stop_duration)
if stop_idx != -1: sng_stop_option.selected = stop_idx
var goals_idx = [5, 8, 12].find(LobbyManager.sng_required_goals)
if goals_idx != -1: sng_goals_option.selected = goals_idx
func _on_doors_update(_val: int = 0) -> void:
if not doors_swap_option: return
var swap_idx = [10, 15, 30].find(LobbyManager.doors_swap_time)
if swap_idx != -1: doors_swap_option.selected = swap_idx
var refresh_idx = [15, 25, 40].find(LobbyManager.doors_refresh_time)
if refresh_idx != -1: doors_refresh_option.selected = refresh_idx
var goals_idx = [5, 8, 12].find(LobbyManager.doors_required_goals)
if goals_idx != -1: doors_goals_option.selected = goals_idx
func _update_random_spawn_label(enabled: bool) -> void:
if random_spawn_label:
@@ -431,6 +597,18 @@ func _on_settings_pressed():
if settings_menu:
settings_menu.open()
func _on_leaderboard_pressed() -> void:
if not leaderboard_panel_instance:
var leaderboard_panel_scene := load("res://scenes/ui/leaderboard_panel.tscn")
if leaderboard_panel_scene:
leaderboard_panel_instance = leaderboard_panel_scene.instantiate()
leaderboard_panel_instance.closed.connect(func(): leaderboard_panel_instance.hide())
add_child(leaderboard_panel_instance)
if leaderboard_panel_instance:
leaderboard_panel_instance.show_panel()
# Center it and apply some offset if needed
func _go_to_login() -> void:
if get_tree():
get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn")
@@ -463,15 +641,7 @@ func _on_room_joined(room_data: Dictionary) -> void:
host_banner.visible = is_host
start_game_btn.visible = is_host
# Duration: host sees dropdown, clients see text
duration_option.visible = is_host
duration_text_label.visible = not is_host
random_spawn_check.visible = is_host
random_spawn_label.visible = not is_host
enable_timer_check.visible = is_host
enable_timer_label.visible = not is_host
_update_settings_visibility()
# Update values from LobbyManager
_on_match_duration_changed(LobbyManager.match_duration)
@@ -479,10 +649,12 @@ func _on_room_joined(room_data: Dictionary) -> void:
_on_enable_cycle_timer_changed(LobbyManager.enable_cycle_timer)
# Scarcity Update
if scarcity_option: scarcity_option.visible = is_host
if scarcity_label: scarcity_label.visible = not is_host
_on_scarcity_mode_changed(LobbyManager.scarcity_mode)
# Initial UI sync for custom modes
_on_sng_update()
_on_doors_update()
# Area selector: only host can interact
area_left_btn.disabled = not is_host
area_right_btn.disabled = not is_host