Files
CosmicClash/Game/scripts/lobby.gd
T
2026-09-01 16:44:42 +01:00

129 lines
4.3 KiB
GDScript

extends Control
# Lobby (task 1.5): roster list split by team, team swap, ready toggle,
# leave. Reads/writes MatchNet.roster — this scene owns no state of its
# own, it's a view over the autoload. Reached via main_menu.gd's Host/Join
# flow (task 1.7) calling change_scene_to_file("res://scenes/lobby.tscn")
# after NetworkManager.host()/join() succeeds — this scene must always be
# loaded that way (as the real current_scene), not instantiated as a child
# of something else: change_scene_to_file() operates on
# get_tree().current_scene, and _on_disconnected_from_server()/_leave()
# below call it themselves, which hangs if this scene isn't actually the
# tree's current_scene when that happens (see multiplayer-next.md §9
# gotcha 27 — found the hard way while building tests/lobby_smoke.gd).
@onready var _status_label: Label = %StatusLabel
@onready var _team0_list: VBoxContainer = %Team0List
@onready var _team1_list: VBoxContainer = %Team1List
@onready var _controls_row: HBoxContainer = %ControlsRow
@onready var _switch_team_button: Button = %SwitchTeamButton
@onready var _ready_button: CheckButton = %ReadyButton
@onready var _leave_button: Button = %LeaveButton
var _planned_server_shutdown := false
func _ready() -> void:
MatchNet.welcomed.connect(_on_welcomed)
MatchNet.player_joined.connect(_on_roster_changed)
MatchNet.player_left.connect(_on_roster_changed)
MatchNet.player_state_changed.connect(_on_roster_changed)
MatchNet.rejected.connect(_on_rejected)
MatchNet.server_shutdown.connect(_on_server_shutdown)
NetworkManager.disconnected_from_server.connect(_on_disconnected_from_server)
# The server process is never a roster member (§1.1 decision 2) — it
# gets a read-only view, no team/ready controls to operate on itself.
_controls_row.visible = NetworkManager.is_client
_refresh()
if not MatchNet.last_server_shutdown_reason.is_empty():
_planned_server_shutdown = true
_status_label.text = "Server closed: %s" % MatchNet.last_server_shutdown_reason
func _process(_delta: float) -> void:
NetworkManager.poll()
func _physics_process(_delta: float) -> void:
NetworkManager.poll()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"):
_leave()
func _on_roster_changed(_a = null, _b = null, _c = null) -> void:
_refresh()
func _on_welcomed() -> void:
_refresh()
func _on_rejected(reason: String) -> void:
_status_label.text = "Connection rejected: %s" % reason
func _on_server_shutdown(reason: String) -> void:
_planned_server_shutdown = true
_status_label.text = "Server closed: %s" % reason
func _on_disconnected_from_server() -> void:
if _planned_server_shutdown:
return
get_tree().change_scene_to_file(ScenePaths.MAIN_MENU)
func _on_switch_team_pressed() -> void:
var my_id := multiplayer.get_unique_id()
var info: MatchNet.PlayerInfo = MatchNet.roster.get(my_id)
if info == null:
return
MatchNet.request_set_team((info.team + 1) % MatchNet.TEAM_COUNT)
func _on_ready_toggled(pressed: bool) -> void:
MatchNet.request_set_ready(pressed)
func _on_leave_pressed() -> void:
_leave()
func _leave() -> void:
NetworkManager.shutdown()
get_tree().change_scene_to_file(ScenePaths.MAIN_MENU)
func _refresh() -> void:
if NetworkManager.is_server:
_status_label.text = "Hosting — %d player(s) connected" % MatchNet.roster.size()
elif NetworkManager.is_client:
_status_label.text = "Connected" if not MatchNet.roster.is_empty() else "Connecting..."
else:
_status_label.text = "Not connected"
for child in _team0_list.get_children():
child.queue_free()
for child in _team1_list.get_children():
child.queue_free()
var my_id := multiplayer.get_unique_id()
var infos: Array = MatchNet.roster.values()
infos.sort_custom(func(a: MatchNet.PlayerInfo, b: MatchNet.PlayerInfo) -> bool: return a.peer_id < b.peer_id)
for info: MatchNet.PlayerInfo in infos:
var row := Label.new()
var marker := " (you)" if info.peer_id == my_id else ""
var ready_mark := "✓" if info.ready else "…"
row.text = "%s %s%s" % [ready_mark, info.player_name, marker]
var target_list := _team0_list if info.team == 0 else _team1_list
target_list.add_child(row)
if NetworkManager.is_client:
var my_info: MatchNet.PlayerInfo = MatchNet.roster.get(my_id)
if my_info != null:
_ready_button.set_pressed_no_signal(my_info.ready)