feat: edit some login indicator
This commit is contained in:
+28
-8
@@ -2,6 +2,8 @@ extends Control
|
|||||||
|
|
||||||
# UI References - Main Menu
|
# UI References - Main Menu
|
||||||
@onready var main_menu_panel = $MainMenuPanel
|
@onready var main_menu_panel = $MainMenuPanel
|
||||||
|
@onready var main_title = $MainMenuPanel/VBoxContainer/TitleContainer/Title
|
||||||
|
@onready var main_subtitle = $MainMenuPanel/VBoxContainer/TitleContainer/Subtitle
|
||||||
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
|
@onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn
|
||||||
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
|
@onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn
|
||||||
@onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn
|
@onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn
|
||||||
@@ -116,11 +118,8 @@ func _ready():
|
|||||||
# Setup Game Mode specific UI dynamically
|
# Setup Game Mode specific UI dynamically
|
||||||
_create_custom_settings_ui()
|
_create_custom_settings_ui()
|
||||||
|
|
||||||
# Set player name from profile
|
# Initial UI update
|
||||||
if AuthManager.is_guest:
|
_on_profile_updated()
|
||||||
LobbyManager.local_player_name = NameGenerator.generate_guest_name()
|
|
||||||
else:
|
|
||||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
|
||||||
|
|
||||||
# Connect button signals - Main Menu
|
# Connect button signals - Main Menu
|
||||||
create_room_btn.pressed.connect(_on_create_room_pressed)
|
create_room_btn.pressed.connect(_on_create_room_pressed)
|
||||||
@@ -208,8 +207,12 @@ func _ready():
|
|||||||
NakamaManager.connection_failed.connect(_on_connection_failed)
|
NakamaManager.connection_failed.connect(_on_connection_failed)
|
||||||
|
|
||||||
# Connect UserProfileManager signals
|
# Connect UserProfileManager signals
|
||||||
|
UserProfileManager.profile_loaded.connect(func(_p): _on_profile_updated())
|
||||||
UserProfileManager.profile_updated.connect(_on_profile_updated)
|
UserProfileManager.profile_updated.connect(_on_profile_updated)
|
||||||
|
|
||||||
|
# Set initial title if already loaded
|
||||||
|
_on_profile_updated()
|
||||||
|
|
||||||
# Show main menu initially
|
# Show main menu initially
|
||||||
_show_panel("main_menu")
|
_show_panel("main_menu")
|
||||||
|
|
||||||
@@ -239,6 +242,9 @@ func _load_character_textures() -> void:
|
|||||||
print("[Lobby] Character texture not found: ", tex_path)
|
print("[Lobby] Character texture not found: ", tex_path)
|
||||||
|
|
||||||
func _on_server_option_selected(index: int) -> void:
|
func _on_server_option_selected(index: int) -> void:
|
||||||
|
if main_subtitle and server_option:
|
||||||
|
main_subtitle.text = server_option.get_item_text(index).to_upper()
|
||||||
|
|
||||||
if index == 0:
|
if index == 0:
|
||||||
# Nakama Localhost
|
# Nakama Localhost
|
||||||
if server_ip_input: server_ip_input.visible = false
|
if server_ip_input: server_ip_input.visible = false
|
||||||
@@ -805,10 +811,24 @@ func _on_connection_failed(error_message: String) -> void:
|
|||||||
|
|
||||||
func _on_profile_updated() -> void:
|
func _on_profile_updated() -> void:
|
||||||
"""Handle profile updates (name/avatar change)."""
|
"""Handle profile updates (name/avatar change)."""
|
||||||
var new_name = UserProfileManager.get_display_name()
|
var display_name: String = ""
|
||||||
|
|
||||||
# Sync to LobbyManager if we are in a room or just locally
|
if UserProfileManager.is_profile_loaded:
|
||||||
LobbyManager.set_player_name(new_name)
|
display_name = UserProfileManager.get_display_name()
|
||||||
|
elif not AuthManager.is_guest and AuthManager.is_authenticated:
|
||||||
|
display_name = "LOADING..."
|
||||||
|
else:
|
||||||
|
# Is Guest or not logged in yet
|
||||||
|
if LobbyManager.local_player_name.is_empty() or LobbyManager.local_player_name == "Guest":
|
||||||
|
display_name = NameGenerator.generate_guest_name()
|
||||||
|
else:
|
||||||
|
display_name = LobbyManager.local_player_name
|
||||||
|
|
||||||
|
if main_title:
|
||||||
|
main_title.text = display_name
|
||||||
|
|
||||||
|
# Sync to LobbyManager
|
||||||
|
LobbyManager.set_player_name(display_name)
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Player Slot Updates
|
# Player Slot Updates
|
||||||
|
|||||||
@@ -58,11 +58,18 @@ layout_mode = 2
|
|||||||
|
|
||||||
[node name="BackBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=1330539196]
|
[node name="BackBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=1330539196]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(44, 44)
|
custom_minimum_size = Vector2(100, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_fonts/font = ExtResource("3_font")
|
theme_override_fonts/font = ExtResource("3_font")
|
||||||
text = "← BACK"
|
text = "← BACK"
|
||||||
|
|
||||||
|
[node name="RefreshBtn" type="Button" parent="MainLayout/LeftPanel/LeftVBox/Header"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(44, 44)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Refresh Data"
|
||||||
|
text = "⟳"
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=1037998429]
|
[node name="Title" type="Label" parent="MainLayout/LeftPanel/LeftVBox/Header" unique_id=1037998429]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
@@ -175,22 +182,35 @@ root_node = NodePath("../Oldpop")
|
|||||||
libraries/animation-pack = ExtResource("5_animlib")
|
libraries/animation-pack = ExtResource("5_animlib")
|
||||||
autoplay = &"animation-pack/idle"
|
autoplay = &"animation-pack/idle"
|
||||||
|
|
||||||
[node name="SelectedPlayerInfo" type="VBoxContainer" parent="MainLayout/RightPanel" unique_id=1592883771]
|
[node name="SelectedPlayerInfo" type="PanelContainer" parent="MainLayout/RightPanel" unique_id=1592883771]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 8
|
||||||
|
|
||||||
|
[node name="InfoMargin" type="MarginContainer" parent="MainLayout/RightPanel/SelectedPlayerInfo"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/margin_left = 20
|
||||||
|
theme_override_constants/margin_right = 20
|
||||||
|
theme_override_constants/margin_top = 15
|
||||||
|
theme_override_constants/margin_bottom = 15
|
||||||
|
|
||||||
|
[node name="InfoVBox" type="VBoxContainer" parent="MainLayout/RightPanel/SelectedPlayerInfo/InfoMargin"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 4
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
[node name="SelectedNameLabel" type="Label" parent="MainLayout/RightPanel/SelectedPlayerInfo" unique_id=1940372038]
|
[node name="SelectedNameLabel" type="Label" parent="MainLayout/RightPanel/SelectedPlayerInfo/InfoMargin/InfoVBox" unique_id=1940372038]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||||
theme_override_fonts/font = ExtResource("3_font")
|
theme_override_fonts/font = ExtResource("3_font")
|
||||||
theme_override_font_sizes/font_size = 20
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "PLAYER NAME"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="SelectedRankLabel" type="Label" parent="MainLayout/RightPanel/SelectedPlayerInfo" unique_id=994755781]
|
[node name="SelectedRankLabel" type="Label" parent="MainLayout/RightPanel/SelectedPlayerInfo/InfoMargin/InfoVBox" unique_id=994755781]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
|
theme_override_colors/font_color = Color(0.647, 0.996, 0.224, 1)
|
||||||
theme_override_fonts/font = ExtResource("3_font")
|
theme_override_fonts/font = ExtResource("3_font")
|
||||||
theme_override_font_sizes/font_size = 14
|
theme_override_font_sizes/font_size = 16
|
||||||
|
text = "RANK #1"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ signal avatar_changed(url: String)
|
|||||||
# Profile data
|
# Profile data
|
||||||
var profile: Dictionary = {}
|
var profile: Dictionary = {}
|
||||||
var stats: Dictionary = {}
|
var stats: Dictionary = {}
|
||||||
|
var is_profile_loaded: bool = false
|
||||||
|
|
||||||
# Nakama storage collection names
|
# Nakama storage collection names
|
||||||
const PROFILE_COLLECTION := "profiles"
|
const PROFILE_COLLECTION := "profiles"
|
||||||
@@ -36,6 +37,7 @@ func _on_auth_completed(success: bool, _user_data: Dictionary) -> void:
|
|||||||
func _on_logged_out() -> void:
|
func _on_logged_out() -> void:
|
||||||
profile = {}
|
profile = {}
|
||||||
stats = {}
|
stats = {}
|
||||||
|
is_profile_loaded = false
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Profile Loading
|
# Profile Loading
|
||||||
@@ -81,6 +83,7 @@ func load_profile() -> Dictionary:
|
|||||||
# Load stats
|
# Load stats
|
||||||
await load_stats()
|
await load_stats()
|
||||||
|
|
||||||
|
is_profile_loaded = true
|
||||||
emit_signal("profile_loaded", profile)
|
emit_signal("profile_loaded", profile)
|
||||||
print("[UserProfileManager] Profile loaded: ", profile.display_name)
|
print("[UserProfileManager] Profile loaded: ", profile.display_name)
|
||||||
return profile
|
return profile
|
||||||
@@ -257,8 +260,10 @@ func record_game_result(won: bool, score: int) -> void:
|
|||||||
# Getters
|
# Getters
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
func get_display_name() -> String:
|
func get_display_name(fallback: String = "Guest") -> String:
|
||||||
return profile.get("display_name", "Guest")
|
if not is_profile_loaded:
|
||||||
|
return fallback
|
||||||
|
return profile.get("display_name", fallback)
|
||||||
|
|
||||||
func get_avatar_url() -> String:
|
func get_avatar_url() -> String:
|
||||||
var index: int = profile.get("avatar_index", 0)
|
var index: int = profile.get("avatar_index", 0)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ signal closed
|
|||||||
# UI References
|
# UI References
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@onready var back_btn := %BackBtn as Button
|
@onready var back_btn := %BackBtn as Button
|
||||||
|
@onready var refresh_btn := %RefreshBtn as Button
|
||||||
@onready var sort_score_btn := %SortScoreBtn as Button
|
@onready var sort_score_btn := %SortScoreBtn as Button
|
||||||
@onready var sort_win_rate_btn := %SortWinRateBtn as Button
|
@onready var sort_win_rate_btn := %SortWinRateBtn as Button
|
||||||
@onready var sort_games_btn := %SortGamesBtn as Button
|
@onready var sort_games_btn := %SortGamesBtn as Button
|
||||||
@@ -28,7 +29,6 @@ var current_sort_key: String = "high_score"
|
|||||||
var _anim_player: AnimationPlayer
|
var _anim_player: AnimationPlayer
|
||||||
|
|
||||||
# Maps game character name -> GLB node name in the SubViewport
|
# Maps game character name -> GLB node name in the SubViewport
|
||||||
# Must match the mapping in player.gd's set_character()
|
|
||||||
const CHAR_NODE_MAP: Dictionary = {
|
const CHAR_NODE_MAP: Dictionary = {
|
||||||
"Copper": "Oldpop",
|
"Copper": "Oldpop",
|
||||||
"Dabro": "Masbro",
|
"Dabro": "Masbro",
|
||||||
@@ -40,6 +40,7 @@ const AVATAR_TO_CHAR: Array[String] = ["Pip", "Gatot", "Dabro", "Copper"]
|
|||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
back_btn.pressed.connect(_on_close_pressed)
|
back_btn.pressed.connect(_on_close_pressed)
|
||||||
|
refresh_btn.pressed.connect(_fetch_leaderboard_data)
|
||||||
sort_score_btn.pressed.connect(func(): _sort_by("high_score"))
|
sort_score_btn.pressed.connect(func(): _sort_by("high_score"))
|
||||||
sort_win_rate_btn.pressed.connect(func(): _sort_by("win_rate"))
|
sort_win_rate_btn.pressed.connect(func(): _sort_by("win_rate"))
|
||||||
sort_games_btn.pressed.connect(func(): _sort_by("games_played"))
|
sort_games_btn.pressed.connect(func(): _sort_by("games_played"))
|
||||||
@@ -65,12 +66,12 @@ func _fetch_leaderboard_data() -> void:
|
|||||||
status_label.text = "Not connected to Nakama"
|
status_label.text = "Not connected to Nakama"
|
||||||
return
|
return
|
||||||
|
|
||||||
status_label.text = "Loading data..."
|
status_label.text = "Fetching Global Records..."
|
||||||
for child in leaderboard_list.get_children():
|
for child in leaderboard_list.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
var payload = JSON.stringify({})
|
# Calls the updated RPC that returns ONLY native global_high_score records
|
||||||
var result = await NakamaManager.client.rpc_async(NakamaManager.session, "get_leaderboard_stats", payload)
|
var result = await NakamaManager.client.rpc_async(NakamaManager.session, "get_leaderboard_stats", "{}")
|
||||||
|
|
||||||
if result.is_exception():
|
if result.is_exception():
|
||||||
status_label.text = "Failed to load leaderboard"
|
status_label.text = "Failed to load leaderboard"
|
||||||
@@ -90,9 +91,9 @@ func _fetch_leaderboard_data() -> void:
|
|||||||
if leaderboard_data.size() > 0:
|
if leaderboard_data.size() > 0:
|
||||||
_show_entry_preview(0)
|
_show_entry_preview(0)
|
||||||
else:
|
else:
|
||||||
status_label.text = "No data found"
|
status_label.text = "No records found"
|
||||||
else:
|
else:
|
||||||
status_label.text = "Error parsing data"
|
status_label.text = "Error parsing server data"
|
||||||
|
|
||||||
func _calculate_win_rates() -> void:
|
func _calculate_win_rates() -> void:
|
||||||
for entry in leaderboard_data:
|
for entry in leaderboard_data:
|
||||||
|
|||||||
@@ -564,14 +564,14 @@ function rpcAdminUpdateStats(ctx, logger, nk, payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Update Storage (for legacy support/redundancy)
|
// 1. Update Storage (priority: game_stats)
|
||||||
nk.storageWrite([{
|
nk.storageWrite([{
|
||||||
collection: "stats",
|
collection: "stats",
|
||||||
key: "stats",
|
key: "game_stats",
|
||||||
userId: targetUserId,
|
userId: targetUserId,
|
||||||
value: JSON.stringify(stats),
|
value: JSON.stringify(stats),
|
||||||
permissionRead: 1, // Public read
|
permissionRead: 1, // Public read
|
||||||
permissionWrite: 0 // No one can write (except server)
|
permissionWrite: 0
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
// 2. Update Native Leaderboard
|
// 2. Update Native Leaderboard
|
||||||
@@ -586,7 +586,7 @@ function rpcAdminUpdateStats(ctx, logger, nk, payload) {
|
|||||||
|
|
||||||
nk.leaderboardRecordWrite("global_high_score", targetUserId, account.user.username, score, subscore, JSON.stringify(metadata));
|
nk.leaderboardRecordWrite("global_high_score", targetUserId, account.user.username, score, subscore, JSON.stringify(metadata));
|
||||||
|
|
||||||
logger.info("Stats updated for user " + targetUserId + " by admin " + ctx.userId + " (Storage + Native)");
|
logger.info("Stats updated for user " + targetUserId + " by admin " + ctx.userId + " (game_stats + Native)");
|
||||||
return JSON.stringify({ success: true });
|
return JSON.stringify({ success: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Failed to update stats: " + e);
|
logger.error("Failed to update stats: " + e);
|
||||||
@@ -605,12 +605,11 @@ function rpcAdminDeleteStats(ctx, logger, nk, payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Delete Storage
|
// 1. Delete Storage (Both keys)
|
||||||
nk.storageDelete([{
|
nk.storageDelete([
|
||||||
collection: "stats",
|
{ collection: "stats", key: "stats", userId: targetUserId },
|
||||||
key: "stats",
|
{ collection: "stats", key: "game_stats", userId: targetUserId }
|
||||||
userId: targetUserId
|
]);
|
||||||
}]);
|
|
||||||
|
|
||||||
// 2. Delete Native Leaderboard Record
|
// 2. Delete Native Leaderboard Record
|
||||||
nk.leaderboardRecordDelete("global_high_score", targetUserId);
|
nk.leaderboardRecordDelete("global_high_score", targetUserId);
|
||||||
@@ -629,25 +628,62 @@ function rpcAdminSyncLeaderboard(ctx, logger, nk, payload) {
|
|||||||
try {
|
try {
|
||||||
var result = nk.storageList(null, "stats", 100, "");
|
var result = nk.storageList(null, "stats", 100, "");
|
||||||
var statsObjects = result.objects || [];
|
var statsObjects = result.objects || [];
|
||||||
var count = 0;
|
var userGroup = {}; // [userId] = { highScore, gamesPlayed, gamesWon, avatar }
|
||||||
|
|
||||||
|
// Phase 1: Group and merge
|
||||||
for (var i = 0; i < statsObjects.length; i++) {
|
for (var i = 0; i < statsObjects.length; i++) {
|
||||||
var obj = statsObjects[i];
|
var obj = statsObjects[i];
|
||||||
var userId = obj.userId;
|
var userId = obj.userId;
|
||||||
var stats = JSON.parse(obj.value);
|
var key = obj.key; // "stats" or "game_stats"
|
||||||
|
var value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
value = JSON.parse(obj.value || "{}");
|
||||||
|
} catch (jsonErr) {
|
||||||
|
logger.error("Skipping key " + key + " for user " + userId + " due to corrupt JSON");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userGroup[userId]) {
|
||||||
|
userGroup[userId] = {
|
||||||
|
high_score: value.high_score || 0,
|
||||||
|
games_played: value.games_played || 0,
|
||||||
|
games_won: value.games_won || 0,
|
||||||
|
avatar_url: ""
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Merge logic: sum counts, max high score
|
||||||
|
userGroup[userId].high_score = Math.max(userGroup[userId].high_score, value.high_score || 0);
|
||||||
|
userGroup[userId].games_played += (value.games_played || 0);
|
||||||
|
userGroup[userId].games_won += (value.games_won || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize avatar from game_stats
|
||||||
|
if (key === "game_stats" || !userGroup[userId].avatar_url) {
|
||||||
|
try {
|
||||||
|
// Try to get avatar from storage value if present, else fallback to account later
|
||||||
|
if (value.avatar_url) userGroup[userId].avatar_url = value.avatar_url;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Write to native leaderboard
|
||||||
|
var count = 0;
|
||||||
|
for (var userId in userGroup) {
|
||||||
|
try {
|
||||||
|
var stats = userGroup[userId];
|
||||||
var account = nk.accountGetId(userId);
|
var account = nk.accountGetId(userId);
|
||||||
|
|
||||||
var metadata = {
|
var metadata = {
|
||||||
games_played: stats.games_played || 0,
|
games_played: stats.games_played,
|
||||||
games_won: stats.games_won || 0,
|
games_won: stats.games_won,
|
||||||
avatar_url: account.user.avatarUrl || ""
|
avatar_url: stats.avatar_url || account.user.avatarUrl || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
nk.leaderboardRecordWrite("global_high_score", userId, account.user.username, stats.high_score || 0, 0, JSON.stringify(metadata));
|
nk.leaderboardRecordWrite("global_high_score", userId, account.user.username, stats.high_score, 0, JSON.stringify(metadata));
|
||||||
count++;
|
count++;
|
||||||
} catch (inner) {
|
} catch (inner) {
|
||||||
logger.error("Failed to sync record for " + userId + ": " + inner);
|
logger.error("Failed to sync merged record for " + userId + ": " + inner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user