|
|
|
|
@ -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()
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
_accounts[username] = {
|
|
|
|
|
"password_hash": "...", # Hier würde man eine account_info.json laden
|
|
|
|
|
"characters": characters
|
|
|
|
|
}
|
|
|
|
|
# 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):
|
|
|
|
|
@ -97,16 +154,40 @@ func join_game(ip: String, username: String, password: String):
|
|
|
|
|
peer.create_client(ip, PORT)
|
|
|
|
|
multiplayer.multiplayer_peer = peer
|
|
|
|
|
|
|
|
|
|
func _on_player_connected(id):
|
|
|
|
|
# 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_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.
|