diff --git a/weltenauge/01_core/base_classes/account_data.gd b/weltenauge/01_core/base_classes/account_data.gd new file mode 100644 index 0000000..dd659a4 --- /dev/null +++ b/weltenauge/01_core/base_classes/account_data.gd @@ -0,0 +1,19 @@ +class_name AccountData extends Resource + +var username: String = "" +var password_hash: String = "" +var characters: Array[CharacterData] = [] # Characters associated with this account + +func _init(p_username: String = "", p_password_hash: String = ""): + username = p_username + password_hash = p_password_hash + +func to_dict() -> Dictionary: + return { + "username": username, + "password_hash": password_hash + } + +func from_dict(data: Dictionary) -> void: + username = data.get("username", username) + password_hash = data.get("password_hash", password_hash) diff --git a/weltenauge/01_core/base_classes/account_data.gd.uid b/weltenauge/01_core/base_classes/account_data.gd.uid new file mode 100644 index 0000000..c08a00b --- /dev/null +++ b/weltenauge/01_core/base_classes/account_data.gd.uid @@ -0,0 +1 @@ +uid://8bnlnlh750b4 diff --git a/weltenauge/01_core/base_classes/character_data.gd b/weltenauge/01_core/base_classes/character_data.gd index 6ed0079..302b7fc 100644 --- a/weltenauge/01_core/base_classes/character_data.gd +++ b/weltenauge/01_core/base_classes/character_data.gd @@ -1,16 +1,28 @@ -class_name CharacterData +class_name CharacterData extends Resource var id: String = "" # UUID des Charakters -var name: String = "Neuer Held" +var character_name: String = "Neuer Held" var level: int = 1 var area: String = "keuwin_grasland" var position: Vector3 = Vector3.ZERO +func _init(): + if id == "": + generate_id() + +func generate_id() -> void: + # Generate a new UUID and assign it to the 'id' property. + # Godot 4 uses Crypto for UUID generation. + var crypto = Crypto.new() + var bytes = crypto.generate_random_bytes(16) # 16 bytes for UUID + id = bytes.hex_encode() + + # Erstellt ein Dictionary – Vector3 bleibt ein Vector3! func to_dict() -> Dictionary: return { "id": id, - "name": name, + "character_name": character_name, "level": level, "area": area, "position": position @@ -18,7 +30,7 @@ func to_dict() -> Dictionary: func from_dict(data: Dictionary) -> void: id = data.get("id", id) - name = data.get("name", name) + character_name = data.get("character_name", character_name) level = data.get("level", level) area = data.get("area", area) position = data.get("position", position) diff --git a/weltenauge/01_core/systems/game_events.gd b/weltenauge/01_core/systems/game_events.gd index 6527b22..a696011 100644 --- a/weltenauge/01_core/systems/game_events.gd +++ b/weltenauge/01_core/systems/game_events.gd @@ -11,3 +11,4 @@ signal player_area_join_requested(area_key: String) signal network_show_characters(characters:Array) signal network_new_character_received(character: CharacterData) +signal network_login_response(success: bool) # Renamed to avoid confusion with internal RPC, emits only success/failure diff --git a/weltenauge/01_core/systems/network_manager.gd b/weltenauge/01_core/systems/network_manager.gd index f0cede8..164343e 100644 --- a/weltenauge/01_core/systems/network_manager.gd +++ b/weltenauge/01_core/systems/network_manager.gd @@ -3,6 +3,11 @@ extends Node const PORT = 7777 const MAX_CLIENTS = 32 +# AccountData is a global class_name, no need to preload. +# Utils is a global class_name, no need to preload. +# const AccountData = preload("res://01_core/base_classes/account_data.gd") +# const Utils = preload("res://01_core/systems/utils.gd") + # Kamera (wird nur auf dem Client gebraucht) var local_camera: Camera3D = null @@ -15,10 +20,16 @@ var area_paths = { # Server-seitiges Tracking: peer_id -> SubViewportContainer var player_worlds = {} +# Server-seitiges Tracking: peer_id -> username +var _player_account_map: Dictionary[int, String] = {} -# Struktur: "username" -> { "password_hash": String, "chars": Array[CharacterData] } -var _accounts: Dictionary = {} +# Struktur: "username" -> AccountData +var _accounts: Dictionary[String, AccountData] = {} + +# Temporäre Speicherung für Client-Login-Daten +var _temp_username: String = "" +var _temp_password_hash: String = "" # Der Pfad, wo die Benutzerdaten liegen const SAVE_PATH = "user://accounts/" @@ -30,6 +41,8 @@ func _ready(): GameEvents.ui_join_requested.connect(join_game) GameEvents.ui_character_created.connect(request_character_store) + multiplayer.connected_to_server.connect(_on_connected_to_server) + if DisplayServer.get_name() == "headless" or OS.get_cmdline_args().has("--server"): host_game() @@ -48,17 +61,39 @@ func host_game(): _load_all_accounts_from_disk() func save_character(username: String, char_data: Resource) -> void: # Die Resouce ist CharacterData + print("NetworkManager: save_character called for username: ", username, " char_data: ", char_data) + if not is_instance_valid(char_data): + print("ERROR: save_character: char_data is not a valid instance. Cannot save.") + return + if char_data.id.is_empty(): + print("ERROR: save_character: char_data.id is empty. Cannot save.") + return + var account_path = SAVE_PATH + username + "/chars/" if not DirAccess.dir_exists_absolute(account_path): DirAccess.make_dir_recursive_absolute(account_path) # Dateiname ist die UUID des Charakters - var file_path = account_path + char_data.guid + ".tres" - var error = ResourceSaver.save(char_data as Resource, file_path) + var file_path = account_path + char_data.id + ".tres" + var error = ResourceSaver.save(char_data as Resource, file_path) # Cast again just in case if error == OK: - print("Server: Charakter %s gespeichert." % char_data.character_name) - + print("Server: Charakter %s (ID: %s) gespeichert." % [char_data.character_name, char_data.id]) + else: + print("Server: Fehler beim Speichern von Charakter %s (ID: %s): %s" % [char_data.character_name, char_data.id, error]) + +func save_account(account_data: AccountData) -> void: + var user_path = SAVE_PATH + account_data.username + "/" + if not DirAccess.dir_exists_absolute(user_path): + DirAccess.make_dir_recursive_absolute(user_path) + + var file_path = user_path + "account_info.tres" + var error = ResourceSaver.save(account_data as Resource, file_path) + + if error == OK: + print("Server: Account %s gespeichert." % account_data.username) + else: + print("Server: Fehler beim Speichern von Account %s: %s" % [account_data.username, error]) func _load_all_accounts_from_disk(): var dir = DirAccess.open(SAVE_PATH) @@ -66,29 +101,51 @@ func _load_all_accounts_from_disk(): dir.list_dir_begin() var user_dir = dir.get_next() while user_dir != "": - if dir.current_is_dir() and not user_dir.begin_with("."): + if dir.current_is_dir() and not user_dir.begins_with("."): _load_account(user_dir) user_dir = dir.get_next() -func _load_account(username: String): - var char_path = SAVE_PATH + username + "/chars/" - var characters: Array[CharacterData] = [] +func _load_account(username: String) -> void: + var user_path = SAVE_PATH + username + "/" + var account_file_path = user_path + "account_info.tres" + var account_data: AccountData = null - if DirAccess.dir_exists_absolute(char_path): - var dir = DirAccess.open(char_path) - dir.list_dir_begin() - var file_name = dir.get_next() - while file_name != "": - if file_name.ends_with(".tres"): - var res = ResourceLoader.load(char_path + file_name) - if res is CharacterData: - characters.append(res) - file_name = dir.get_next() - - _accounts[username] = { - "password_hash": "...", # Hier würde man eine account_info.json laden - "characters": characters - } + if ResourceLoader.exists(account_file_path): + var res = ResourceLoader.load(account_file_path) + if res is AccountData: + account_data = res + # If account_data loaded from disk has no password, this indicates old/corrupted data. + # For security, we should not assume a blank password. It should be set on first login. + # If it's empty, a new login with proper hash should overwrite it. + if account_data.password_hash.is_empty(): + print("WARNING: Account %s loaded with empty password hash. Will require fresh login to set password." % username) + else: + print("ERROR: Loaded resource for account %s is not AccountData. Skipping account load." % username) + return # Do not load this account if it's corrupted + else: + # If the account info file does not exist, do not create a new AccountData here with a blank password. + # A new account will be created by srv_handle_login_request when the client first attempts to log in. + print("INFO: Account info file not found for %s. Will be created on first login attempt." % username) + return # Skip loading if file doesn't exist yet. + + # At this point, account_data should be a valid AccountData instance loaded from disk. + if account_data: + # Load characters for this account + var char_path = user_path + "chars/" + if DirAccess.dir_exists_absolute(char_path): + var dir = DirAccess.open(char_path) + dir.list_dir_begin() + var file_name = dir.get_next() + while file_name != "": + if file_name.ends_with(".tres"): + var res = ResourceLoader.load(char_path + file_name) + if res is CharacterData: + account_data.characters.append(res) + file_name = dir.get_next() + + _accounts[username] = account_data # Store AccountData instance + else: + print("ERROR: AccountData is null for user %s after attempted load. This should not happen." % username) func join_game(ip: String, username: String, password: String): @@ -96,17 +153,41 @@ func join_game(ip: String, username: String, password: String): var peer = ENetMultiplayerPeer.new() peer.create_client(ip, PORT) multiplayer.multiplayer_peer = peer + + # Temporarily store credentials for when the client connects to the server + _temp_username = username + # The password needs to be hashed before storing if it's not already. + # It's better to hash it once here before storing. + _temp_password_hash = load("res://01_core/systems/utils.gd").hash_password(password) # Hash here once + # The RPC call will be sent after successful connection in _on_connected_to_server. -func _on_player_connected(id): +func _on_connected_to_server(): + print("Client: Successfully connected to server.") + # Now that connection is established, send the login request to the server + if not multiplayer.is_server(): # Ensure this logic only runs on the client + print("Client: About to send srv_handle_login_request RPC for user: ", _temp_username) + # Use the stored temporary credentials for the RPC call + get_node("/root/NetworkManager").rpc_id(1, "srv_handle_login_request", _temp_username, _temp_password_hash) + _temp_username = "" # Clear temporary storage + _temp_password_hash = "" # Clear temporary storage + +func _on_player_connected(id): # This is for server-side peer_connected print("Player connected with id:", id) player_worlds[id] = null # SubViewport wird erst connected, wenn area geladen wurde. - cl_show_characters.rpc_id(id, []) + # Characters are sent after successful login via cl_login_response. + # No need to send an empty list here anymore. @rpc("authority", "call_remote", "reliable") -func cl_show_characters(characters): +func cl_show_characters(characters: Array): print("Got client_show_characters") - GameEvents.network_show_characters.emit(characters) + # Convert dictionaries back to CharacterData instances before emitting + var character_instances: Array[CharacterData] = [] + for char_dict in characters: + var new_character = CharacterData.new() + new_character.from_dict(char_dict) + character_instances.append(new_character) + GameEvents.network_show_characters.emit(character_instances) func _on_player_disconnected(id): @@ -120,7 +201,39 @@ func _on_player_disconnected(id): print("Removing player ", id, " from internal tracking.") player_worlds.erase(id) -func change_player_area(id: int, area_key: String): +@rpc("any_peer", "call_remote", "reliable") +func srv_handle_area_change_request(area_key: String, character_id: String): + print("Server received srv_handle_area_change_request for char_id: ", character_id) + var peer_id = multiplayer.get_remote_sender_id() + if not player_worlds.has(peer_id): + print("ERROR: Server srv_handle_area_change_request could not be handled because client peer_id is not in player_worlds list") + return + + if not _player_account_map.has(peer_id): + print("ERROR: Server srv_handle_area_change_request could not be handled because peer_id %d is not logged in." % peer_id) + return + + var username = _player_account_map[peer_id] + if not _accounts.has(username): + print("ERROR: Server srv_handle_area_change_request could not be handled because account %s does not exist." % username) + return + + var selected_character: CharacterData = null + for char in _accounts[username].characters: + if char.id == character_id: + selected_character = char + break + + if not selected_character: + print("ERROR: Server srv_handle_area_change_request: Character with ID %s not found for user %s." % [character_id, username]) + return + + print("Server: Player %d (%s) requests area change to %s with character %s (ID: %s)" % [peer_id, username, area_key, selected_character.character_name, selected_character.id]) + + # Pass peer_id and selected_character to change_player_area for further processing + change_player_area(peer_id, area_key, selected_character) + +func change_player_area(id: int, area_key: String, character_data: CharacterData): if not multiplayer.is_server(): return print("Server change_player_area: Server is loading ", area_key) var path = area_paths[area_key] @@ -152,23 +265,30 @@ func change_player_area(id: int, area_key: String): # --- ALTEN ZUSTAND AUFRÄUMEN --- if player_worlds.has(id): + # Get the old container node to remove the player from the old area + var old_container = player_worlds[id] + if old_container: + var old_viewport = old_container.get_child(0) + var player_node = old_viewport.get_child(0).get_node("SpawnPoints").get_node(str(id)) + if player_node: + player_node.queue_free() player_worlds.erase(id) # --- NEUEN ZUSTAND REGISTRIEREN --- player_worlds[id] = container_node # Client anweisen, Szene zu laden - cl_load_area.rpc_id(id, area_key) + cl_load_area.rpc_id(id, area_key, character_data.id) # Pass character_data.id to client # Avatar auf Server erstellen erfolgt erst wenn Client Vollzug meldet (client_ready_to_spawn) @rpc("any_peer", "call_local", "reliable") -func client_ready_to_spawn(area_key:String): - var id = multiplayer.get_remote_sender_id() +func client_ready_to_spawn(area_key:String, character_id: String): # Added character_id + var peer_id = multiplayer.get_remote_sender_id() if not multiplayer.is_server(): return - if player_worlds.has(id): - var container_node = player_worlds[id] + if player_worlds.has(peer_id): + var container_node = player_worlds[peer_id] var viewport_node = container_node.get_node(area_key+"_viewport") var spawn_target_node = viewport_node.get_node(area_key).get_node("SpawnPoints") @@ -176,12 +296,24 @@ func client_ready_to_spawn(area_key:String): print("Viewport Node:", viewport_node) print("spawn target Node:", spawn_target_node) - print("Client ", id, " bereits für Spawning.") - _spawn_player_in_world(id, spawn_target_node) + print("Client ", peer_id, " bereits für Spawning.") + + var username = _player_account_map[peer_id] + var account_data: AccountData = _accounts[username] + var selected_character: CharacterData = null + for char in account_data.characters: + if char.id == character_id: + selected_character = char + break + + if selected_character: + _spawn_player_in_world(peer_id, spawn_target_node, selected_character) # Pass selected_character + else: + print("ERROR: client_ready_to_spawn: Character with ID %s not found for user %s." % [character_id, username]) @rpc("authority", "call_remote", "reliable") -func cl_load_area(area_key): +func cl_load_area(area_key: String, character_id: String): # Added character_id print("Client loads area") # Client löscht alte Map, lädt neue lokal in das Root for child in get_children(): @@ -214,7 +346,8 @@ func cl_load_area(area_key): setup_client_camera(viewport_node) # Jetzt können wir dem Server ok fürs Spawnen geben - client_ready_to_spawn.rpc_id(1, area_key) # Server ist immer id=1 + client_ready_to_spawn.rpc_id(1, area_key, character_id) # Pass character_id + func setup_client_camera(node): local_camera = Camera3D.new() @@ -226,11 +359,11 @@ func setup_client_camera(node): local_camera.add_child(AudioListener3D.new()) -func _spawn_player_in_world(id, spawn_target_node): +func _spawn_player_in_world(id: int, spawn_target_node: Node, character_data: CharacterData): # Added character_data var player_scene = load("res://04_entities/player/player.tscn") var player = player_scene.instantiate() player.name = str(id) - player.get_node("PlayerNameLabel").text = str(id) + player.get_node("PlayerNameLabel").text = character_data.character_name # Use character name #player.set_multiplayer_authority(id) player.set_multiplayer_authority(1) @@ -266,30 +399,6 @@ func cl_assign_camera_target(node_name: String): # var camera = get_viewport().get_camera_3d() -@rpc("any_peer", "call_remote", "reliable") -func srv_handle_area_change_request(area_key: String): - print("Server received srv_handle_area_change_request") - var id = multiplayer.get_remote_sender_id() - if not player_worlds.has(id): - print("ERROR: Server srv_handle_area_change_request could not be handled because client peer_id is not in player_worlds list") - return - - print("Server: Player ", id, " requests area change to ", area_key) - - # 1. Alten Spieler finden und entfernen falls vorhanden - var old_container = player_worlds[id] - if old_container: - print("container name=", old_container) - var old_viewport = old_container.get_child(0) - print("Viewport name=", old_viewport) - var player_node = old_viewport.get_child(0).get_node("SpawnPoints").get_node(str(id)) - - if player_node: - player_node.queue_free() - - # 2. Bestehende Funktion für den Wechsel aufrufen - change_player_area(id, area_key) - func request_character_store(new_character: CharacterData): var character_dict = new_character.to_dict() srv_store_new_character.rpc_id(1,character_dict) @@ -298,12 +407,85 @@ func request_character_store(new_character: CharacterData): func srv_store_new_character(character_dict: Dictionary): var new_character = CharacterData.new() new_character.from_dict(character_dict) + + if not is_instance_valid(new_character) or new_character.id.is_empty(): + print("ERROR: srv_store_new_character: new_character is invalid or has no ID after from_dict. Dict: ", character_dict) + return # Prevent further errors + # TODO: In Zukunft erhalten neue Charaktere einen area_key und position gesetzt var client_id = multiplayer.get_remote_sender_id() - cl_receive_new_character.rpc_id(client_id, new_character.to_dict()) + + # Add the character to the account + if _player_account_map.has(client_id): + var username = _player_account_map[client_id] + if _accounts.has(username): + var account_data: AccountData = _accounts[username] + account_data.characters.append(new_character) + save_account(account_data) # Save the updated account + # Explicitly cast new_character to Resource to satisfy type hint. + save_character(username, new_character as Resource) # Save the new character as a separate .tres + + cl_receive_new_character.rpc_id(client_id, new_character.to_dict()) # Pass dictionary @rpc("authority", "call_remote", "reliable") func cl_receive_new_character(char_dict: Dictionary): var new_character = CharacterData.new() new_character.from_dict(char_dict) GameEvents.network_new_character_received.emit(new_character) + + +@rpc("any_peer", "call_remote", "reliable") +func srv_handle_login_request(username: String, password_hash: String): + var peer_id = multiplayer.get_remote_sender_id() + + var account_data: AccountData = null + if _accounts.has(username): + account_data = _accounts[username] + + if account_data.password_hash.is_empty(): + # Account exists but has no password hash set (e.g., loaded from old/initial save). + # Set the password hash from the client's first login attempt. + account_data.password_hash = password_hash + save_account(account_data) # Persist the new password hash + print("Server: Player %d logged in as existing user %s. Password hash updated." % [peer_id, username]) + _player_account_map[peer_id] = username + var char_dicts: Array = [] + for char in account_data.characters: + char_dicts.append(char.to_dict()) + cl_login_response.rpc_id(peer_id, true, char_dicts) + elif account_data.password_hash == password_hash: + print("Server: Player %d logged in as existing user %s" % [peer_id, username]) + _player_account_map[peer_id] = username + # Send characters to client + var char_dicts: Array = [] + for char in account_data.characters: + char_dicts.append(char.to_dict()) + cl_login_response.rpc_id(peer_id, true, char_dicts) + else: + print("Server: Player %d failed login for existing user %s (wrong password)") + print("Server: Stored hash: ", account_data.password_hash, " Received hash: ", password_hash) + cl_login_response.rpc_id(peer_id, false, []) + else: + # New account + account_data = AccountData.new(username, password_hash) + _accounts[username] = account_data + save_account(account_data) + print("Server: Player %d created new account %s and logged in" % [peer_id, username]) + _player_account_map[peer_id] = username + cl_login_response.rpc_id(peer_id, true, []) # No characters yet for a new account + +@rpc("authority", "call_remote", "reliable") +func cl_login_response(success: bool, characters_dicts: Array): + print("Client: Received login response. Success: ", success) + GameEvents.network_login_response.emit(success) # Emit success/failure + if success: + var character_instances: Array[CharacterData] = [] + for char_dict in characters_dicts: + var new_character = CharacterData.new() + new_character.from_dict(char_dict) + character_instances.append(new_character) + print("Client: Emitting network_show_characters with ", character_instances.size(), " characters.") + GameEvents.network_show_characters.emit(character_instances) # Show characters + else: + print("Client: Login failed.") + # The login.gd script will handle showing the error message. \ No newline at end of file diff --git a/weltenauge/01_core/systems/utils.gd b/weltenauge/01_core/systems/utils.gd index ff71853..8e3d0e6 100644 --- a/weltenauge/01_core/systems/utils.gd +++ b/weltenauge/01_core/systems/utils.gd @@ -16,3 +16,10 @@ func generate_uuid_v4_formatted() -> String: s.substr(16, 4), s.substr(20, 12) ] + +## Hashes a password using SHA256. For production, consider a stronger KDF. +static func hash_password(password: String) -> String: + # Fallback to String.sha256_text() for basic hashing, + # as HashingContext.init seems to be problematic in this environment. + # For production, a more robust password hashing algorithm should be used. + return password.sha256_text() diff --git a/weltenauge/04_entities/ui/windows/account/login.gd b/weltenauge/04_entities/ui/windows/account/login.gd index 3231b54..669226a 100644 --- a/weltenauge/04_entities/ui/windows/account/login.gd +++ b/weltenauge/04_entities/ui/windows/account/login.gd @@ -1,6 +1,26 @@ extends Control +# Utils and NetworkManager are global class_names/singletons, no need to preload. +# const Utils = preload("res://01_core/systems/utils.gd") +# const NetworkManager = preload("res://01_core/systems/network_manager.gd") + @onready var ip_input: TextEdit = $IPInput +@onready var username_line_edit: LineEdit = $UsernameLineEdit +@onready var password_line_edit: LineEdit = $PasswordLineEdit +@onready var error_label: Label = $ErrorLabel # Assuming an ErrorLabel exists in the scene + +func _ready(): + GameEvents.network_login_response.connect(_on_network_login_response) # Connect to the new login response signal + error_label.hide() # Hide error label by default + +func _on_network_login_response(success: bool): + if success: + error_label.hide() + # Client will receive characters via network_show_characters, and this screen will be hidden by char select. + else: + error_label.text = "Login failed. Check username and password." + error_label.show() + show() # Show login screen again for retry func _on_host_button_pressed() -> void: print("host pressed") @@ -10,7 +30,18 @@ func _on_host_button_pressed() -> void: func _on_join_button_pressed(): var username = $UsernameLineEdit.text - var password = $PasswordLineEdit.text # TODO: Handle Password + var password = $PasswordLineEdit.text + + # Hash the password before sending it to the server + var hashed_password = load("res://01_core/systems/utils.gd").hash_password(password) - GameEvents.ui_join_requested.emit(ip_input.text, username, password) + # Store credentials temporarily and initiate client connection + # NetworkManager.join_game will now handle sending the login request after connection is established. + get_node("/root/NetworkManager")._temp_username = username + get_node("/root/NetworkManager")._temp_password_hash = hashed_password + + # This initiates the client connection process. + get_node("/root/NetworkManager").join_game(ip_input.text, username, password) + + # Hide the login screen; character select or an error will appear later. hide() diff --git a/weltenauge/04_entities/ui/windows/account/login.tscn b/weltenauge/04_entities/ui/windows/account/login.tscn index 566182b..b6e1472 100644 --- a/weltenauge/04_entities/ui/windows/account/login.tscn +++ b/weltenauge/04_entities/ui/windows/account/login.tscn @@ -77,6 +77,14 @@ offset_right = 230.0 offset_bottom = -7.0 text = "test_pw" +[node name="ErrorLabel" type="Label" parent="." unique_id=638509728] +visible = false +layout_mode = 0 +offset_left = 29.0 +offset_top = 55.0 +offset_right = 534.0 +offset_bottom = 78.0 + [connection signal="pressed" from="Join_Button" to="." method="_on_join_button_pressed"] [connection signal="button_up" from="Host_Button" to="." method="_on_host_pressed"] [connection signal="pressed" from="Host_Button" to="." method="_on_host_button_pressed"] diff --git a/weltenauge/04_entities/ui/windows/character_create/character_create.gd b/weltenauge/04_entities/ui/windows/character_create/character_create.gd index 153b254..ed00cc5 100644 --- a/weltenauge/04_entities/ui/windows/character_create/character_create.gd +++ b/weltenauge/04_entities/ui/windows/character_create/character_create.gd @@ -11,8 +11,8 @@ func show_creation_screen(): func _on_create_button_pressed() -> void: var new_character = CharacterData.new() - new_character.name = name_line_edit.text - print("Create Button pressed") + new_character.character_name = name_line_edit.text + print("CharacterCreate: Created new character: ", new_character.character_name, " ID: ", new_character.id) GameEvents.ui_character_created.emit(new_character) hide() diff --git a/weltenauge/04_entities/ui/windows/character_select/character_select.gd b/weltenauge/04_entities/ui/windows/character_select/character_select.gd index bf8eabf..02bf027 100644 --- a/weltenauge/04_entities/ui/windows/character_select/character_select.gd +++ b/weltenauge/04_entities/ui/windows/character_select/character_select.gd @@ -10,15 +10,17 @@ func _ready(): func receive_new_character(character: CharacterData): characters.append(character) - character_item_list.add_item(character.name) + character_item_list.add_item(character.character_name) show() -func show_list(characters: Array): - print("show_list triggered") +func show_list(p_characters: Array[CharacterData]): # Renamed parameter to avoid conflict with member variable + print("CharacterSelect: show_list triggered.") + characters = p_characters # Store the characters character_item_list.clear() for character: CharacterData in characters: - character_item_list.add_item(character.name) - show() + character_item_list.add_item(character.character_name) + show() # Make the CharacterSelect UI visible + print("CharacterSelect: Setting visible property to true.") func _on_create_character_button_pressed() -> void: GameEvents.ui_create_character_screen.emit() @@ -27,12 +29,9 @@ func _on_create_character_button_pressed() -> void: # Doubleclick on Character should start area transition func _on_character_item_list_item_activated(index: int) -> void: print("Doubleclick for index=", index) - var item = character_item_list.get_selected_items()[0] - print("=>",character_item_list.get_item_text(item)) + var selected_character: CharacterData = characters[index] + print("Selected character: ", selected_character.character_name, " ID: ", selected_character.id) - #for name in character_item_list.get_selected_items(): - # #character_item_list.add_item(character.name) - # print(name) print("sending to server: srv_handle_area_change_request") - NetworkManager.srv_handle_area_change_request.rpc_id(1, 'keuwin_grasland') + NetworkManager.srv_handle_area_change_request.rpc_id(1, 'keuwin_grasland', selected_character.id) hide() diff --git a/weltenauge/PROJEKT_UEBERSICHT.md b/weltenauge/PROJEKT_UEBERSICHT.md index d89e04a..bdde070 100644 --- a/weltenauge/PROJEKT_UEBERSICHT.md +++ b/weltenauge/PROJEKT_UEBERSICHT.md @@ -183,3 +183,5 @@ Variablen: löst aus: GameEvents.create_character_screen.emit() , der von CharacterCreate abonniert ist, und dort die Methode show_creation_screen auslöst. ** Wird das Formular ausgefüllt und Create angeklickt, löst dies die an den Server gerichtete rpc store_player_charakter aus. + +Next step: Name des Charakters persistieren. Aktuell heissen die nach deserialisierung immer "Neuer Held" :-) \ No newline at end of file