feat : update backend
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
extends Control
|
||||
## Leaderboard panel — full-scene layout.
|
||||
## Leaderboard panel — reads from Nakama native leaderboard (global_high_score).
|
||||
## Left: sortable leaderboard list.
|
||||
## Right: 3D SubViewport character preview of the selected/top-ranked player.
|
||||
|
||||
@@ -10,6 +10,7 @@ signal closed
|
||||
# -------------------------------------------------------------------------
|
||||
@onready var back_btn := %BackBtn as Button
|
||||
@onready var refresh_btn := %RefreshBtn as Button
|
||||
@onready var sync_btn := %SyncBtn as Button
|
||||
@onready var sort_score_btn := %SortScoreBtn as Button
|
||||
@onready var sort_win_rate_btn := %SortWinRateBtn as Button
|
||||
@onready var sort_games_btn := %SortGamesBtn as Button
|
||||
@@ -41,6 +42,7 @@ const AVATAR_TO_CHAR: Array[String] = ["Pip", "Gatot", "Dabro", "Copper"]
|
||||
func _ready() -> void:
|
||||
back_btn.pressed.connect(_on_close_pressed)
|
||||
refresh_btn.pressed.connect(_fetch_leaderboard_data)
|
||||
sync_btn.pressed.connect(_on_sync_pressed)
|
||||
sort_score_btn.pressed.connect(func(): _sort_by("high_score"))
|
||||
sort_win_rate_btn.pressed.connect(func(): _sort_by("win_rate"))
|
||||
sort_games_btn.pressed.connect(func(): _sort_by("games_played"))
|
||||
@@ -52,12 +54,31 @@ func _ready() -> void:
|
||||
# -------------------------------------------------------------------------
|
||||
func show_panel() -> void:
|
||||
show()
|
||||
status_label.text = "Syncing scores..."
|
||||
# Bulk-sync all users' storage stats to native leaderboard (server-side operation)
|
||||
if NakamaManager.session:
|
||||
var sync_result = await NakamaManager.client.rpc_async(NakamaManager.session, "sync_leaderboard", "{}")
|
||||
if sync_result.is_exception():
|
||||
push_error("[Leaderboard] sync_leaderboard RPC failed: ", sync_result.get_exception().message)
|
||||
else:
|
||||
print("[Leaderboard] Server sync finished: ", sync_result.payload)
|
||||
_fetch_leaderboard_data()
|
||||
|
||||
func _on_close_pressed() -> void:
|
||||
hide()
|
||||
emit_signal("closed")
|
||||
|
||||
func _on_sync_pressed() -> void:
|
||||
"""Push the current player's stored stats up to the native Nakama leaderboard."""
|
||||
if not NakamaManager.session or AuthManager.is_guest:
|
||||
status_label.text = "Must be logged in to sync"
|
||||
return
|
||||
status_label.text = "Syncing your score..."
|
||||
await UserProfileManager._submit_to_leaderboard()
|
||||
status_label.text = "Synced! Refreshing..."
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
_fetch_leaderboard_data()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Data
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -66,11 +87,59 @@ func _fetch_leaderboard_data() -> void:
|
||||
status_label.text = "Not connected to Nakama"
|
||||
return
|
||||
|
||||
status_label.text = "Fetching Global Records..."
|
||||
status_label.text = "Fetching Leaderboard..."
|
||||
for child in leaderboard_list.get_children():
|
||||
child.queue_free()
|
||||
|
||||
# Calls the updated RPC that returns ONLY native global_high_score records
|
||||
# Try native Nakama leaderboard first (fastest, ranked already)
|
||||
var native_data = await _fetch_native_leaderboard()
|
||||
|
||||
if native_data.size() > 0:
|
||||
leaderboard_data = native_data
|
||||
_calculate_win_rates()
|
||||
status_label.text = ""
|
||||
_sort_by(current_sort_key)
|
||||
if leaderboard_data.size() > 0:
|
||||
_show_entry_preview(0)
|
||||
else:
|
||||
# Fallback: try the server RPC (reads same native leaderboard)
|
||||
await _fetch_via_rpc()
|
||||
|
||||
func _fetch_native_leaderboard() -> Array:
|
||||
"""Use the Nakama client API to list native leaderboard records directly."""
|
||||
var result = await NakamaManager.client.list_leaderboard_records_async(
|
||||
NakamaManager.session,
|
||||
"global_high_score",
|
||||
[], # no specific owner filter
|
||||
null, # expiry = null (no filter)
|
||||
100 # limit
|
||||
)
|
||||
|
||||
if result.is_exception():
|
||||
push_warning("[Leaderboard] Native API failed: ", result.get_exception().message)
|
||||
return []
|
||||
|
||||
var data: Array = []
|
||||
for record in result.records:
|
||||
var meta: Dictionary = {}
|
||||
if record.metadata and not record.metadata.is_empty():
|
||||
var parsed = JSON.parse_string(record.metadata)
|
||||
if parsed is Dictionary:
|
||||
meta = parsed
|
||||
|
||||
data.append({
|
||||
"user_id": record.owner_id,
|
||||
"display_name": record.username if (record.username and not record.username.is_empty()) else "Unknown",
|
||||
"avatar_url": meta.get("avatar_url", ""),
|
||||
"high_score": int(record.score),
|
||||
"games_played": int(meta.get("games_played", 0)),
|
||||
"games_won": int(meta.get("games_won", 0)),
|
||||
"rank": int(record.rank)
|
||||
})
|
||||
return data
|
||||
|
||||
func _fetch_via_rpc() -> void:
|
||||
"""Fallback: call server RPC which reads the same native leaderboard."""
|
||||
var result = await NakamaManager.client.rpc_async(NakamaManager.session, "get_leaderboard_stats", "{}")
|
||||
|
||||
if result.is_exception():
|
||||
@@ -78,20 +147,19 @@ func _fetch_leaderboard_data() -> void:
|
||||
push_error("[Leaderboard] RPC failed: ", result.get_exception().message)
|
||||
return
|
||||
|
||||
var json = JSON.new()
|
||||
var error = json.parse(result.payload)
|
||||
if error == OK:
|
||||
var json := JSON.new()
|
||||
if json.parse(result.payload) == OK:
|
||||
var data = json.get_data()
|
||||
if data.has("leaderboard"):
|
||||
if data.has("leaderboard") and data.leaderboard.size() > 0:
|
||||
leaderboard_data = data.leaderboard
|
||||
_calculate_win_rates()
|
||||
status_label.text = ""
|
||||
_sort_by(current_sort_key)
|
||||
# Show top player's character in 3D preview
|
||||
if leaderboard_data.size() > 0:
|
||||
_show_entry_preview(0)
|
||||
else:
|
||||
status_label.text = "No records found"
|
||||
# No records exist yet — show a helpful hint
|
||||
status_label.text = "No scores recorded yet.\nPlay a match to appear here!"
|
||||
else:
|
||||
status_label.text = "Error parsing server data"
|
||||
|
||||
@@ -122,7 +190,7 @@ func _populate_list() -> void:
|
||||
child.queue_free()
|
||||
|
||||
if leaderboard_data.size() == 0:
|
||||
status_label.text = "No players found"
|
||||
status_label.text = "No players found.\nPlay a match to appear here!"
|
||||
return
|
||||
|
||||
for i in range(leaderboard_data.size()):
|
||||
@@ -143,7 +211,7 @@ func _create_leaderboard_item(rank: int, entry: Dictionary, index: int) -> void:
|
||||
item.add_theme_stylebox_override("panel", style)
|
||||
|
||||
var hbox = HBoxContainer.new()
|
||||
hbox.theme_override_constants.separation = 16
|
||||
hbox.add_theme_constant_override("separation", 16)
|
||||
item.add_child(hbox)
|
||||
|
||||
# Rank
|
||||
|
||||
Reference in New Issue
Block a user