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 in AuthButtonsRow also switch the panel's mode ## when clicked while the panel is already open, so the user can toggle between ## Login and Register modes at any time. # ─── 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 _dob_edit: LineEdit 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 _dob_edit = $CenterContainer/VBoxContainer/AuthPanel/MarginContainer/AuthVBox/DOBEdit _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 # Initial state. _auth_panel.visible = false _status_label.visible = false _welcome_label.visible = false _email_edit.visible = false _dob_edit.visible = 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 _dob_edit.visible = true else: _auth_mode_label.text = "Login" _auth_button.text = "Login" _email_edit.visible = false _dob_edit.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() var dob := _dob_edit.text.strip_edges() if email == "": _show_status("Email is required.") return if dob == "": _show_status("Date of birth is required.") return # Basic YYYY-MM-DD format check. if not _is_valid_date(dob): _show_status("Date of birth must be in YYYY-MM-DD format.") return _auth_button.disabled = true _auth_button.text = "Registering..." _is_authenticating = true _clear_status() var result := await ApiClient.register(username, email, password, dob) _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 — show welcome, enable Play, hide the now-redundant # Login/Register entry points (the auth panel itself stays open, # showing the welcome message in place of the form). _welcome_label.text = "Welcome, %s!" % _username_edit.text.strip_edges() _welcome_label.visible = true _status_label.visible = false _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 # ─── Validation ─────────────────────────────────────────────────────────────── func _is_valid_date(date_str: String) -> bool: """Check if a string looks like a valid YYYY-MM-DD date.""" if date_str.length() != 10: return false var parts := date_str.split("-") if parts.size() != 3: return false var year := parts[0].to_int() var month := parts[1].to_int() var day := parts[2].to_int() if month < 1 or month > 12: return false if day < 1 or day > 31: return false # Rough month-day validation. var days_in_month := [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if day > days_in_month[month]: return false return true