This leaderboard uses async aggregation to calculate rankings without blocking the server.
local leaderboardDB = db:Collection("leaderboard")
-- Create indexes (sync is fine for initialization)
leaderboardDB:CreateIndex({ score = -1 }, false, "score_desc")
leaderboardDB:CreateIndex({ steamid = 1 }, true, "steamid_unique")
function UpdateScore(ply, score, callback)
if not IsValid(ply) then return end
leaderboardDB:UpdateOneAsync(
{ steamid = ply:SteamID() },
{
["$set"] = {
username = ply:Nick(),
score = score,
updated_at = os.time()
},
["$setOnInsert"] = {
created_at = os.time()
}
},
true, -- upsert
function(err, count)
if err then
ErrorNoHalt("Failed to update score: " .. err .. "\n")
if callback then callback(false) end
else
print("✓ Updated score for " .. ply:Nick() .. ": " .. score)
if callback then callback(true) end
end
end
)
end
function AddScore(ply, points, callback)
if not IsValid(ply) then return end
leaderboardDB:UpdateOneAsync(
{ steamid = ply:SteamID() },
{
["$inc"] = { score = points },
["$set"] = {
username = ply:Nick(),
updated_at = os.time()
}
},
true, -- upsert
function(err, count)
if err then
ErrorNoHalt("Failed to add score: " .. err .. "\n")
if callback then callback(false) end
else
print("✓ Added " .. points .. " points to " .. ply:Nick())
if callback then callback(true) end
end
end
)
end
function GetTopPlayers(limit, callback)
leaderboardDB:AggregateAsync({
{ ["$sort"] = { score = -1 } },
{ ["$limit"] = limit or 10 },
{
["$project"] = {
_id = 0,
username = 1,
score = 1,
rank = 1
}
}
}, function(err, results)
if err then
ErrorNoHalt("Failed to get leaderboard: " .. err .. "\n")
callback({})
return
end
callback(results or {})
end)
end
function GetRank(steamid, callback)
-- First get the player's score
leaderboardDB:FindOneAsync({ steamid = steamid }, function(err, player)
if err or not player then
callback(nil)
return
end
-- Count how many players have a higher score
leaderboardDB:CountAsync({
score = { ["$gt"] = player.score }
}, function(err, higherScores)
if err then
callback(nil)
return
end
local rank = higherScores + 1
callback(rank, player.score)
end)
end)
end
concommand.Add("leaderboard", function(ply)
if not IsValid(ply) then return end
-- Get top 10 players
GetTopPlayers(10, function(top)
ply:ChatPrint("=== LEADERBOARD ===")
if #top == 0 then
ply:ChatPrint("No scores recorded yet!")
return
end
for i, p in ipairs(top) do
ply:ChatPrint(string.format("%d. %s - %d pts",
i, p.username, p.score))
end
-- Show player's own rank if not in top 10
GetRank(ply:SteamID(), function(rank, score)
if rank and rank > 10 then
ply:ChatPrint(string.format("\nYour Rank: #%d - %d pts",
rank, score))
end
end)
end)
end)
hook.Add("PlayerDeath", "RecordKill", function(victim, inflictor, attacker)
if not IsValid(attacker) or not attacker:IsPlayer() then return end
if attacker == victim then return end -- Suicide
-- Add points for kill
AddScore(attacker, 10, function(success)
if success then
attacker:ChatPrint("+10 points!")
-- Optionally notify of rank changes
GetRank(attacker:SteamID(), function(rank)
if rank then
attacker:ChatPrint("Current rank: #" .. rank)
end
end)
end
end)
end)
function GetLeaderboardStats(callback)
leaderboardDB:AggregateAsync({
{
["$group"] = {
_id = nil,
totalPlayers = { ["$sum"] = 1 },
averageScore = { ["$avg"] = "$score" },
highestScore = { ["$max"] = "$score" },
lowestScore = { ["$min"] = "$score" }
}
}
}, function(err, results)
if err or not results or #results == 0 then
callback(nil)
return
end
callback(results[1])
end)
end
concommand.Add("lbstats", function(ply)
if not IsValid(ply) then return end
GetLeaderboardStats(function(stats)
if not stats then
ply:ChatPrint("Error getting statistics!")
return
end
ply:ChatPrint("=== Leaderboard Statistics ===")
ply:ChatPrint("Total Players: " .. stats.totalPlayers)
ply:ChatPrint("Average Score: " .. math.floor(stats.averageScore))
ply:ChatPrint("Highest Score: " .. stats.highestScore)
ply:ChatPrint("Lowest Score: " .. stats.lowestScore)
end)
end)
concommand.Add("resetleaderboard", function(ply)
if IsValid(ply) and not ply:IsAdmin() then
ply:ChatPrint("Admin only!")
return
end
leaderboardDB:DeleteManyAsync({}, function(err, count)
if err then
print("Failed to reset leaderboard: " .. err)
if IsValid(ply) then
ply:ChatPrint("Error resetting leaderboard!")
end
else
print("✓ Leaderboard reset - removed " .. count .. " entries")
if IsValid(ply) then
ply:ChatPrint("Leaderboard reset successfully!")
end
-- Notify all players
for _, p in ipairs(player.GetAll()) do
if IsValid(p) then
p:ChatPrint("Leaderboard has been reset!")
end
end
end
end)
end)
-- Update leaderboard display every 60 seconds
timer.Create("UpdateLeaderboard", 60, 0, function()
GetTopPlayers(3, function(top)
if #top > 0 then
-- Display top 3 to all players
for _, ply in ipairs(player.GetAll()) do
if IsValid(ply) then
ply:ChatPrint("=== Top 3 ===")
for i, p in ipairs(top) do
ply:ChatPrint(string.format("%d. %s - %d", i, p.username, p.score))
end
end
end
end
end)
end)