extends Control ## Main menu with login/register UI wired to the backend auth API. ## ## The AuthPanel is hidden by default. When the user clicks the LoginButton or ## RegisterButton (AuthButtonsRow, above the panel), the auth panel slides in. ## The Login and Register buttons on the AuthButtonsRow directly set the panel's ## mode, so the user can switch between Login and Register at any time while ## the panel is open. ## ## On successful auth the token is stored via ApiClient, AuthButtonsRow hides, ## the welcome label shows the username, and the Play button becomes enabled. ## The auth form fields are hidden, leaving only the welcome message visible. ## ## When returning from another scene (e.g. world_list), _ready() checks whether ## ApiClient already holds a valid token. If so, it restores the authenticated ## UI state (Play enabled, welcome message visible) instead of forcing the user ## back to the login form. ## Emitted once _ready()'s session check (has_token() + the awaited ## get_me() call) has actually resolved, whichever way -- lets a test (or ## anything else) know the UI state is trustworthy now, rather than ## guessing from a frame count or from UI properties that are ambiguous ## mid-await (e.g. AuthButtonsRow.visible is true both before the check ## resolves AND in a genuine logged-out end state). signal auth_state_ready # ─── UI references ──────────────────────────────────────────────────────────── var _auth_buttons_row: HBoxContainer var _login_button: Button var _register_button: Button var _auth_panel: PanelContainer var _auth_mode_label: Label var _username_edit: LineEdit var _password_edit: LineEdit var _email_edit: LineEdit var _over_18_check: CheckBox var _auth_button: Button var _status_label: Label var _welcome_label: Label var _play_button: Button var _quit_button: Button var _is_register_mode: bool = false var _is_authenticating: bool = false # ─── Lifecycle ──────────────────────────────────────────────────────────────── func _ready() -> void: # Cache node references. _auth_buttons_row = $CenterContainer/VBoxContainer/AuthButtonsRow _login_button = $CenterContainer/VBoxContainer/AuthButtonsRow/LoginButton _register_button = $CenterContainer/VBoxContainer/AuthButtonsRow/RegisterButton _auth_panel = $CenterContainer/VBoxContainer/AuthPanel _auth_mode_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthModeLabel _username_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/UsernameEdit _password_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/PasswordEdit _email_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/EmailEdit _over_18_check = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/Over18Check _auth_button = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthButton _status_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/AuthStatusLabel _welcome_label = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/WelcomeLabel _play_button = $CenterContainer/VBoxContainer/PlayButton _quit_button = $CenterContainer/VBoxContainer/QuitButton # Default to Play being gated. When a token exists, the async session # check below may take several frames (a real get_me() round-trip); # keeping the button disabled until validation succeeds prevents a fast # click from navigating to world_list with an expired/invalid token. _play_button.disabled = true # Check whether a valid session already exists (e.g. user logged in, # navigated to world_list, then clicked Back). If so, restore the # authenticated UI state instead of forcing the login form again. if ApiClient.has_token(): var me_result: Dictionary = await ApiClient.get_me() if me_result.ok and me_result.data is Dictionary: var username: String = str((me_result.data as Dictionary).get("username", "")) if username != "": _show_authenticated(username) auth_state_ready.emit() return # Token missing or invalid — fall through to logged-out state. _reset_to_logged_out() auth_state_ready.emit() ## Restore the UI to the authenticated state (Play enabled, welcome message ## visible, auth buttons hidden). AuthPanel itself stays visible -- the ## welcome label lives inside it, so hiding the panel hides the label too ## even when the label's own `visible` is true (real bug, 2026-08-20: a ## review caught the panel being hidden here while the label claimed to be ## visible, which is a no-op UI-wise since Control.visible doesn't override ## an ancestor's hidden state). func _show_authenticated(username: String) -> void: _auth_panel.visible = true _status_label.visible = false _welcome_label.text = "Welcome, %s!" % username _welcome_label.visible = true _email_edit.visible = false _over_18_check.visible = false _play_button.disabled = false _auth_buttons_row.visible = false print("Restored authenticated session for: ", username) ## Reset the UI to the logged-out state (Play disabled, auth panel hidden, ## login/register buttons visible). func _reset_to_logged_out() -> void: _auth_panel.visible = false _status_label.visible = false _welcome_label.visible = false _email_edit.visible = false _over_18_check.visible = false _over_18_check.button_pressed = false _play_button.disabled = true # Gate play behind successful auth. _update_auth_mode_ui() # ─── Play / Quit ────────────────────────────────────────────────────────────── func _on_play_button_pressed() -> void: if _play_button.disabled: return # Browse worlds is the first real screen past login -- actual gameplay # (joining a world) doesn't exist yet, browsing does. get_tree().change_scene_to_file("res://scenes/world_list.tscn") func _on_quit_button_pressed() -> void: get_tree().quit() # ─── Auth panel toggle ──────────────────────────────────────────────────────── func _on_login_button_pressed() -> void: """Show the auth panel in login mode, called from AuthButtonsRow/LoginButton.""" _auth_panel.visible = true _is_register_mode = false _update_auth_mode_ui() _clear_status() _username_edit.grab_focus() func _on_register_button_pressed() -> void: """Show the auth panel in register mode, called from AuthButtonsRow/RegisterButton.""" _auth_panel.visible = true _is_register_mode = true _update_auth_mode_ui() _clear_status() _username_edit.grab_focus() func _update_auth_mode_ui() -> void: """Update labels and field visibility based on login vs register mode.""" if _is_register_mode: _auth_mode_label.text = "Register" _auth_button.text = "Register" _email_edit.visible = true _over_18_check.visible = true else: _auth_mode_label.text = "Login" _auth_button.text = "Login" _email_edit.visible = false _over_18_check.visible = false # ─── Auth submission ────────────────────────────────────────────────────────── func _on_auth_button_pressed() -> void: """Handle login or register button press.""" if _is_authenticating: return # Validate inputs. var username := _username_edit.text.strip_edges() var password := _password_edit.text if username == "": _show_status("Username is required.") return if password == "": _show_status("Password is required.") return if _is_register_mode: var email := _email_edit.text.strip_edges() if email == "": _show_status("Email is required.") return if not _over_18_check.button_pressed: _show_status("You must confirm you are 18 or older to register.") return _auth_button.disabled = true _auth_button.text = "Registering..." _is_authenticating = true _clear_status() var result := await ApiClient.register(username, email, password, true) _on_auth_complete(result) else: _auth_button.disabled = true _auth_button.text = "Logging in..." _is_authenticating = true _clear_status() var result := await ApiClient.login(username, password) _on_auth_complete(result) func _on_auth_complete(result: Dictionary) -> void: """Handle the result of a login or register call.""" _auth_button.disabled = false _auth_button.text = "Register" if _is_register_mode else "Login" _is_authenticating = false if result.ok: # Success — hide the auth form fields, show only the welcome label. _username_edit.visible = false _password_edit.visible = false _email_edit.visible = false _over_18_check.visible = false _auth_button.visible = false _auth_mode_label.visible = false _status_label.visible = false _welcome_label.text = "Welcome, %s!" % _username_edit.text.strip_edges() _welcome_label.visible = true _play_button.disabled = false _auth_buttons_row.visible = false print("Auth successful. Token stored.") else: # Failure — show backend error detail. _show_status(result.detail) # ─── Status helpers ─────────────────────────────────────────────────────────── func _show_status(message: String) -> void: _status_label.text = message _status_label.visible = true func _clear_status() -> void: _status_label.text = "" _status_label.visible = false