feat: Implement core multiplayer features including user authentication, profile management, lobby, game mode managers, and leaderboard.

This commit is contained in:
2026-03-12 03:55:20 +08:00
parent 650d241a72
commit 4f6783b468
13 changed files with 1151 additions and 31 deletions
+59
View File
@@ -29,6 +29,9 @@ function InitModule(
// User management RPCs
initializer.registerRpc("get_user_profile", rpcGetUserProfile);
initializer.registerRpc("update_user_profile", rpcUpdateUserProfile);
// Leaderboard RPCs
initializer.registerRpc("get_leaderboard_stats", rpcGetLeaderboardStats);
logger.info("Tekton admin module loaded");
}
@@ -538,6 +541,62 @@ function rpcUpdateUserProfile(
}
}
// =============================================================================
// Leaderboard RPCs
// =============================================================================
function rpcGetLeaderboardStats(
ctx: nkruntime.Context,
logger: nkruntime.Logger,
nk: nkruntime.Nakama,
payload: string
): string {
try {
// Query the "stats" collection to get all user stats
// Warning: For a large production game this should be indexed using Nakama's actual Leaderboard system,
// but this works for listing all users' custom storage stats.
const limit = 100;
const result = nk.storageList(null, "stats", limit, "");
const statsObjects = result.objects || [];
const leaderboardData: any[] = [];
for (const obj of statsObjects) {
try {
const stats = JSON.parse(obj.value);
const userId = obj.userId;
// Get the user's profile to retrieve their display name
let displayName = "Unknown";
let avatarUrl = "";
try {
const account = nk.accountGetId(userId);
displayName = account.user.displayName || account.user.username;
avatarUrl = account.user.avatarUrl;
} catch (e) {
// Ignore if account fetch fails
}
leaderboardData.push({
user_id: userId,
display_name: displayName,
avatar_url: avatarUrl,
games_played: stats.games_played || 0,
games_won: stats.games_won || 0,
high_score: stats.high_score || 0
});
} catch (e) {
logger.error(`Error parsing stats for object: ${obj.key}`);
}
}
return JSON.stringify({ leaderboard: leaderboardData });
} catch (e) {
logger.error(`Failed to get leaderboard stats: ${e}`);
throw new Error("Failed to get leaderboard stats");
}
}
// Before login hook to check ban status
function beforeAuthenticateEmail(
ctx: nkruntime.Context,