🏆 Nakama Leaderboards + Activity

Operators

OpUseSortExample
incrCumulativedescWins, kills, points
bestRecordsdesc/ascHigh score, best time
setCurrentdescElo rating, level
decrDrainascLives left

Sort Selection

  • desc = higher better (wins, score, streak)
  • asc = lower better (time, moves, deaths)

Lua Create Pattern

nk.leaderboard_create(id, authoritative, sort, operator, reset_schedule, metadata)
-- Examples:
nk.leaderboard_create("game_wins", false, "desc", "incr", "", {game="mygame"})
nk.leaderboard_create("game_elo", false, "desc", "set", "", {type="elo"})
nk.leaderboard_create("game_best", false, "asc", "best", "", {type="efficiency"})

Lua Write Pattern

-- Get username for metadata
local users = nk.users_get_id({owner_id})
local username = users and users[1] and users[1].username or "unknown"
local metadata = {mode = "normal", username = username}
 
-- Write with pcall for safety
local ok, err = pcall(function()
  nk.leaderboard_record_write(leaderboard_id, owner_id, username, score, subscore, metadata)
end)
if ok then print("✅ Leaderboard:", leaderboard_id) else print("❌", err) end

Streak Tracking Pattern

-- Read current streak from storage
local key = "game_streak"
local streak = 1
local read = nk.storage_read({{collection="game", key=key, user_id=winner_id}})
if read and read[1] and read[1].value then
  streak = (read[1].value.streak or 0) + 1
end
-- Persist streak
nk.storage_write({{collection="game", key=key, user_id=winner_id, value={streak=streak}, permission_read=1, permission_write=0}})
-- Write to leaderboard
nk.leaderboard_record_write("game_streak", winner_id, username, streak, 0, metadata)
-- Reset loser
nk.storage_write({{collection="game", key=key, user_id=loser_id, value={streak=0}, permission_read=1, permission_write=0}})

Elo Calculation

local K, default = 32, 1200
local expected = 1 / (1 + math.pow(10, (opponent_elo - player_elo) / 400))
local new_elo = math.floor(player_elo + K * (actual - expected))
-- actual: 1=win, 0.5=draw, 0=loss

Plugin JSON Config

{
  "leaderboards": {
    "default": "game_wins",
    "ids": ["game_wins", "game_elo", "game_streak", "game_efficiency"],
    "configs": {
      "game_wins": { "type": "incr", "label": "Wins", "unit": "Wins" },
      "game_elo": { "type": "set", "default": 1200, "label": "Elo", "unit": "Rating" },
      "game_streak": { "type": "best", "sortOrder": "desc", "label": "Streak", "unit": "Streak" },
      "game_efficiency": { "type": "best", "sortOrder": "asc", "label": "Best", "unit": "Moves" }
    }
  }
}

Activity Recording

local activity = require("activity_utils")
-- On match end:
activity.record_match_completion(players, "game_id", winner_id, {moves=N, durationSeconds=T})
-- Activity types: match, leaderboard, achievement, friend, level, social

Match Handler Template

if state.winner and not state.resultNotified then
  state.resultNotified = true
  local winner, loser = state.winner, nil
  for _, p in ipairs(state.players) do if p ~= winner then loser = p break end end
 
  -- 1. Activity
  activity.record_match_completion(state.players, "game", winner, {moves=state.moves})
 
  -- 2. Wins (incr)
  nk.leaderboard_record_write("game_wins", winner, username, 1, 0, meta)
 
  -- 3. Efficiency (best asc)
  nk.leaderboard_record_write("game_efficiency", winner, username, state.moves, 0, meta)
 
  -- 4. Streak (storage + best)
  -- [see streak pattern above]
 
  -- 5. Elo (set)
  -- [see elo pattern above]
end

Data Seed (nakama-modules/data-seed.lua)

local boards = {"game_wins", "game_elo", "game_streak", "game_efficiency"}
for _, id in ipairs(boards) do
  pcall(function()
    if id:match("_wins$") then
      nk.leaderboard_create(id, false, "desc", "incr", "", {})
    elseif id:match("_elo$") then
      nk.leaderboard_create(id, false, "desc", "set", "", {})
    elseif id:match("_streak$") then
      nk.leaderboard_create(id, false, "desc", "best", "", {})
    elseif id:match("_efficiency$") then
      nk.leaderboard_create(id, false, "asc", "best", "", {})
    end
  end)
end

File Locations

WhatPath
Plugin configgames/{game}/funday-plugin.json
Match handlergames/{game}/server/match_handler.lua
Data seednakama-modules/data-seed.lua
Activity utilsnakama-modules/activity_utils.lua
Frontend tablefrontend/src/lib/components/leaderboards/LeaderboardTable.svelte

Deploy After Changes

npm run build && sudo systemctl restart funday-frontend
sudo k3s kubectl rollout restart deployment/nakama -n funday-platform

Reference

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send