Examples

Leaderboard System

Dynamic rankings and statistics with async operations

This leaderboard uses async aggregation to calculate rankings without blocking the server.

Setup

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")

Update Score

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

Add Score

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

Get Top Players

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

Get Player Rank

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

Display Leaderboard

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)

Record Kill (Example Integration)

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)

Get Statistics

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)

Reset Leaderboard (Admin)

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)

Periodic Leaderboard Update

-- 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)
  • Complex calculations don't block the server
  • Real-time rankings update without lag
  • Statistics computed on-demand efficiently
  • Handles high player counts gracefully