IMIsle Modkit
Pages in this area

Storing data

Server scripts have two ways to keep data across restarts. modkit.storage is a key/value store that saves itself. modkit.resources.save writes a file you format yourself, see the persistence guide. Use the store unless you need a file that people edit by hand.

The store in short

modkit.storage.set("motd", "Welcome back")
local motd = modkit.storage.get("motd", "Welcome")

Each resource has its own store in data\storage\<resource>.json next to server.cfg. Values can be booleans, numbers, strings and tables of those. The file is written two seconds after a change, on a background thread.

A complete resource: playtime and points

resources/stats/manifest.lua

name "stats"
server "server.lua"

resources/stats/server.lua

local function load(steamId)
  return modkit.storage.get("player:" .. steamId, { name = "", seconds = 0, kills = 0, deaths = 0, points = 0 })
end

local function save(steamId, record)
  modkit.storage.set("player:" .. steamId, record)
end

local online = {}

modkit.events.add("playerConnected", function(player)
  online[player.steamId] = os.time()
  local record = load(player.steamId)
  record.name = player.name
  save(player.steamId, record)
end)

local function settle(steamId)
  local since = online[steamId]
  if not since then return end
  local record = load(steamId)
  record.seconds = record.seconds + (os.time() - since)
  save(steamId, record)
  online[steamId] = os.time()
end

modkit.events.add("playerDisconnected", function(player)
  settle(player.steamId)
  online[player.steamId] = nil
end)

modkit.setInterval(5 * 60 * 1000, function()
  for steamId in pairs(online) do settle(steamId) end
end)

modkit.events.add("playerDied", function(player, killer)
  local victim = load(player.steamId)
  victim.deaths = victim.deaths + 1
  save(player.steamId, victim)
  if killer and killer.steamId then
    local record = load(killer.steamId)
    record.kills = record.kills + 1
    record.points = record.points + 10
    save(killer.steamId, record)
  end
end)

modkit.commands.add("stats", function(player)
  settle(player.steamId)
  local r = load(player.steamId)
  player:message("Playtime " .. modkit.util.formatTime(r.seconds) .. ", kills " .. r.kills .. ", deaths " .. r.deaths .. ", points " .. r.points)
end)

modkit.commands.add("top", function(player)
  local list = {}
  for _, key in ipairs(modkit.storage.keys()) do
    if key:sub(1, 7) == "player:" then list[#list + 1] = modkit.storage.get(key) end
  end
  table.sort(list, function(a, b) return a.points > b.points end)
  for i = 1, math.min(5, #list) do player:message(i .. ". " .. list[i].name .. " " .. list[i].points) end
end)

modkit.exports.register("addPoints", function(steamId, amount)
  local record = load(steamId)
  record.points = record.points + amount
  save(steamId, record)
  return record.points
end)

Things to know

get hands you a copy. Change it, then call set. The pattern is always load, change, save, as in the functions above.

One key per player keeps a change small to reason about. The store still writes the whole file each time, so it is made for thousands of small records, not for hundreds of megabytes. For bigger data send it to a web service with modkit.http.

Playtime is settled every five minutes. If the server process is ended, at most those five minutes are missing. There is no shutdown event, so do not wait for one to save.

A change that must not get lost, such as a purchase, gets a modkit.storage.save() right after the set. That skips the two second wait.

Other resources can not read your store. Offer a function through modkit.exports instead, like addPoints above.

A table with entries at 1..n comes back as a list, any other table as an object with string keys. A key like [5] in a table without [1] comes back as the string key "5".