update
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
extends Node
|
||||
## UserProfileManager - Manages user profile data with Nakama storage
|
||||
|
||||
signal profile_loaded(profile: Dictionary)
|
||||
signal profile_updated
|
||||
signal profile_update_failed(error: String)
|
||||
signal avatar_changed(url: String)
|
||||
|
||||
# Profile data
|
||||
var profile: Dictionary = {}
|
||||
var stats: Dictionary = {}
|
||||
|
||||
# Nakama storage collection names
|
||||
const PROFILE_COLLECTION := "profiles"
|
||||
const STATS_COLLECTION := "stats"
|
||||
|
||||
# Available avatars (predefined)
|
||||
const AVATARS := [
|
||||
"res://assets/avatars/avatar_default.png",
|
||||
"res://assets/avatars/avatar_warrior.png",
|
||||
"res://assets/avatars/avatar_mage.png",
|
||||
"res://assets/avatars/avatar_rogue.png",
|
||||
"res://assets/avatars/avatar_tank.png",
|
||||
"res://assets/avatars/avatar_healer.png",
|
||||
]
|
||||
|
||||
func _ready() -> void:
|
||||
# Connect to auth signals
|
||||
if has_node("/root/AuthManager"):
|
||||
var auth := get_node("/root/AuthManager")
|
||||
auth.auth_completed.connect(_on_auth_completed)
|
||||
auth.logged_out.connect(_on_logged_out)
|
||||
|
||||
func _on_auth_completed(_success: bool, user_data: Dictionary) -> void:
|
||||
if _success:
|
||||
await load_profile()
|
||||
|
||||
func _on_logged_out() -> void:
|
||||
profile = {}
|
||||
stats = {}
|
||||
|
||||
# =============================================================================
|
||||
# Profile Loading
|
||||
# =============================================================================
|
||||
|
||||
func load_profile() -> Dictionary:
|
||||
if not NakamaManager.session:
|
||||
push_error("[UserProfileManager] No session available")
|
||||
return {}
|
||||
|
||||
# First get basic account info
|
||||
var account := await NakamaManager.client.get_account_async(NakamaManager.session)
|
||||
|
||||
if account.is_exception():
|
||||
push_error("[UserProfileManager] Failed to load account")
|
||||
return {}
|
||||
|
||||
profile = {
|
||||
"user_id": account.user.id,
|
||||
"username": account.user.username,
|
||||
"display_name": account.user.display_name if account.user.display_name else account.user.username,
|
||||
"avatar_url": account.user.avatar_url,
|
||||
"avatar_index": 0,
|
||||
"email": account.email,
|
||||
"created_at": account.user.create_time,
|
||||
"online": account.user.online,
|
||||
"bio": "",
|
||||
"country": "",
|
||||
"language": "en"
|
||||
}
|
||||
|
||||
# Load custom profile data from storage
|
||||
var storage_result := await NakamaManager.client.read_storage_objects_async(
|
||||
NakamaManager.session,
|
||||
[NakamaStorageObjectId.new(PROFILE_COLLECTION, "profile", account.user.id)]
|
||||
)
|
||||
|
||||
if not storage_result.is_exception() and storage_result.objects.size() > 0:
|
||||
var stored_data = JSON.parse_string(storage_result.objects[0].value)
|
||||
if stored_data:
|
||||
profile.merge(stored_data, true)
|
||||
|
||||
# Load stats
|
||||
await load_stats()
|
||||
|
||||
emit_signal("profile_loaded", profile)
|
||||
print("[UserProfileManager] Profile loaded: ", profile.display_name)
|
||||
return profile
|
||||
|
||||
func load_stats() -> Dictionary:
|
||||
if not NakamaManager.session:
|
||||
return {}
|
||||
|
||||
var user_id := NakamaManager.session.user_id
|
||||
|
||||
var storage_result := await NakamaManager.client.read_storage_objects_async(
|
||||
NakamaManager.session,
|
||||
[NakamaStorageObjectId.new(STATS_COLLECTION, "game_stats", user_id)]
|
||||
)
|
||||
|
||||
if not storage_result.is_exception() and storage_result.objects.size() > 0:
|
||||
var stored_data = JSON.parse_string(storage_result.objects[0].value)
|
||||
if stored_data:
|
||||
stats = stored_data
|
||||
else:
|
||||
# Initialize default stats
|
||||
stats = {
|
||||
"games_played": 0,
|
||||
"games_won": 0,
|
||||
"games_lost": 0,
|
||||
"total_score": 0,
|
||||
"high_score": 0,
|
||||
"play_time_minutes": 0
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
# =============================================================================
|
||||
# Profile Updates
|
||||
# =============================================================================
|
||||
|
||||
func update_display_name(new_name: String) -> bool:
|
||||
if not NakamaManager.session:
|
||||
emit_signal("profile_update_failed", "Not authenticated")
|
||||
return false
|
||||
|
||||
if new_name.strip_edges().is_empty():
|
||||
emit_signal("profile_update_failed", "Display name cannot be empty")
|
||||
return false
|
||||
|
||||
if new_name.length() > 50:
|
||||
emit_signal("profile_update_failed", "Display name too long (max 50 characters)")
|
||||
return false
|
||||
|
||||
var result := await NakamaManager.client.update_account_async(
|
||||
NakamaManager.session,
|
||||
null, # username (don't change)
|
||||
new_name # display_name
|
||||
)
|
||||
|
||||
if result.is_exception():
|
||||
emit_signal("profile_update_failed", result.get_exception().message)
|
||||
return false
|
||||
|
||||
profile["display_name"] = new_name
|
||||
emit_signal("profile_updated")
|
||||
return true
|
||||
|
||||
func update_avatar(avatar_index: int) -> bool:
|
||||
if avatar_index < 0 or avatar_index >= AVATARS.size():
|
||||
emit_signal("profile_update_failed", "Invalid avatar index")
|
||||
return false
|
||||
|
||||
if not NakamaManager.session:
|
||||
emit_signal("profile_update_failed", "Not authenticated")
|
||||
return false
|
||||
|
||||
# Store avatar in custom profile data
|
||||
profile["avatar_index"] = avatar_index
|
||||
profile["avatar_url"] = AVATARS[avatar_index]
|
||||
|
||||
var success := await _save_profile_data()
|
||||
if success:
|
||||
emit_signal("avatar_changed", AVATARS[avatar_index])
|
||||
emit_signal("profile_updated")
|
||||
|
||||
return success
|
||||
|
||||
func update_bio(new_bio: String) -> bool:
|
||||
if new_bio.length() > 200:
|
||||
emit_signal("profile_update_failed", "Bio too long (max 200 characters)")
|
||||
return false
|
||||
|
||||
profile["bio"] = new_bio
|
||||
return await _save_profile_data()
|
||||
|
||||
func _save_profile_data() -> bool:
|
||||
if not NakamaManager.session:
|
||||
return false
|
||||
|
||||
var custom_data := {
|
||||
"avatar_index": profile.get("avatar_index", 0),
|
||||
"bio": profile.get("bio", ""),
|
||||
"country": profile.get("country", ""),
|
||||
"language": profile.get("language", "en")
|
||||
}
|
||||
|
||||
var write_obj := NakamaWriteStorageObject.new(
|
||||
PROFILE_COLLECTION,
|
||||
"profile",
|
||||
2, # Public read
|
||||
1, # Owner write
|
||||
JSON.stringify(custom_data),
|
||||
"" # Version (empty = overwrite)
|
||||
)
|
||||
|
||||
var result := await NakamaManager.client.write_storage_objects_async(
|
||||
NakamaManager.session,
|
||||
[write_obj]
|
||||
)
|
||||
|
||||
if result.is_exception():
|
||||
emit_signal("profile_update_failed", result.get_exception().message)
|
||||
return false
|
||||
|
||||
emit_signal("profile_updated")
|
||||
return true
|
||||
|
||||
# =============================================================================
|
||||
# Stats Management
|
||||
# =============================================================================
|
||||
|
||||
func update_stats(new_stats: Dictionary) -> bool:
|
||||
stats.merge(new_stats, true)
|
||||
|
||||
if not NakamaManager.session:
|
||||
return false
|
||||
|
||||
var write_obj := NakamaWriteStorageObject.new(
|
||||
STATS_COLLECTION,
|
||||
"game_stats",
|
||||
2, # Public read
|
||||
1, # Owner write
|
||||
JSON.stringify(stats),
|
||||
""
|
||||
)
|
||||
|
||||
var result := await NakamaManager.client.write_storage_objects_async(
|
||||
NakamaManager.session,
|
||||
[write_obj]
|
||||
)
|
||||
|
||||
return not result.is_exception()
|
||||
|
||||
func record_game_result(won: bool, score: int) -> void:
|
||||
stats["games_played"] = stats.get("games_played", 0) + 1
|
||||
|
||||
if won:
|
||||
stats["games_won"] = stats.get("games_won", 0) + 1
|
||||
else:
|
||||
stats["games_lost"] = stats.get("games_lost", 0) + 1
|
||||
|
||||
stats["total_score"] = stats.get("total_score", 0) + score
|
||||
|
||||
if score > stats.get("high_score", 0):
|
||||
stats["high_score"] = score
|
||||
|
||||
await update_stats(stats)
|
||||
|
||||
# =============================================================================
|
||||
# Getters
|
||||
# =============================================================================
|
||||
|
||||
func get_display_name() -> String:
|
||||
return profile.get("display_name", "Guest")
|
||||
|
||||
func get_avatar_url() -> String:
|
||||
var index: int = profile.get("avatar_index", 0)
|
||||
if index >= 0 and index < AVATARS.size():
|
||||
return AVATARS[index]
|
||||
return AVATARS[0]
|
||||
|
||||
func get_stats() -> Dictionary:
|
||||
return stats
|
||||
|
||||
func get_win_rate() -> float:
|
||||
var played: int = stats.get("games_played", 0)
|
||||
if played == 0:
|
||||
return 0.0
|
||||
return float(stats.get("games_won", 0)) / float(played) * 100.0
|
||||
Reference in New Issue
Block a user