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