srb2k-leaderboard/lb_store.lua

272 lines
6.2 KiB
Lua
Raw Normal View History

-- This file handles the storage and related netvars of the leaderboard
---- Imported functions ----
-- lb_common.lua
local stat_t = lb_stat_t
local lbComp = lb_comp
2022-11-18 01:35:59 +01:00
local mapChecksum = lb_map_checksum
----------------------------
local LEADERBOARD_FILE = "leaderboard.txt"
-- ColdStore are records loaded from lua addons
-- this table should never be modified outside of the AddColdStore function
local ColdStore = {}
-- Livestore are new records nad records loaded from leaderboard.txt file
local LiveStore = {}
2022-11-16 16:09:53 +01:00
-- parse score function
local parseScore
2022-10-07 17:59:27 +02:00
-- GLOBAL
-- Returns a list of all maps with records
local function MapList()
local maps = {}
2022-11-18 01:35:59 +01:00
for mapid, checksums in pairs(ColdStore) do
maps[mapid] = $ or {}
for checksum in pairs(checksums) do
maps[mapid][checksum] = true
end
2022-10-07 17:59:27 +02:00
end
2022-11-18 01:35:59 +01:00
for mapid, checksums in pairs(LiveStore) do
maps[mapid] = $ or {}
for checksum in pairs(checksums) do
maps[mapid][checksum] = true
end
2022-10-07 17:59:27 +02:00
end
local maplist = {}
2022-11-18 01:35:59 +01:00
for mapid, checksums in pairs(maps) do
for checksum in pairs(checksums) do
table.insert(maplist, {["id"] = mapid, ["checksum"] = checksum})
end
2022-10-07 17:59:27 +02:00
end
2022-11-18 01:35:59 +01:00
table.sort(maplist, function(a, b) return a.id < b.id end)
2022-10-07 17:59:27 +02:00
return maplist
end
rawset(_G, "lb_map_list", MapList)
-- GLOBAL
2022-11-16 16:09:53 +01:00
-- Function for adding a single record from lua
local function AddColdStore(record)
ColdStore[record.map] = $ or {}
2022-11-18 01:35:59 +01:00
ColdStore[record.map][record.checksum] = $ or {}
table.insert(ColdStore[record.map][record.checksum], record)
end
rawset(_G, "lb_add_coldstore_record", AddColdStore)
2022-11-16 16:09:53 +01:00
-- GLOBAL
-- Function for adding a single record in string form from lua
local function AddColdStoreString(record)
AddColdStore(parseScore(record))
end
rawset(_G, "lb_add_coldstore_record_string", AddColdStoreString)
-- Insert mode separated records from the flat sourceTable into dest
2022-11-18 01:35:59 +01:00
local function insertRecords(dest, sourceTable, checksum, modeSep)
if not sourceTable then return end
2022-11-18 01:35:59 +01:00
if not sourceTable[checksum] then return end
local mode = nil
2022-11-18 01:35:59 +01:00
for _, record in ipairs(sourceTable[checksum]) do
mode = record.flags & modeSep
dest[mode] = $ or {}
table.insert(dest[mode], record)
end
end
-- GLOBAL
-- Construct the leaderboard table of the supplied mapid
-- combines the ColdStore and LiveStore records
2022-11-18 01:35:59 +01:00
local function GetMapRecords(map, checksum, modeSep)
local mapRecords = {}
-- Insert ColdStore records
2022-11-18 01:35:59 +01:00
insertRecords(mapRecords, ColdStore[map], checksum, modeSep)
-- Insert LiveStore records
2022-11-18 01:35:59 +01:00
insertRecords(mapRecords, LiveStore[map], checksum, modeSep)
-- Sort records
for _, records in pairs(mapRecords) do
table.sort(records, lbComp)
end
-- Remove duplicate entries
for _, records in pairs(mapRecords) do
local players = {}
local i = 1
while i <= #records do
if players[records[i].name] then
table.remove(records, i)
else
players[records[i].name] = true
i = i + 1
end
end
end
return mapRecords
end
rawset(_G, "lb_get_map_records", GetMapRecords)
local function insertOrReplaceRecord(map, score, modeSep)
2022-11-18 01:35:59 +01:00
local checksum = mapChecksum(map)
LiveStore[map] = $ or {}
2022-11-18 01:35:59 +01:00
LiveStore[map][checksum] = $ or {}
2022-11-18 01:35:59 +01:00
for i, record in ipairs(LiveStore[map][checksum]) do
-- Replace the record
if record.name == score.name
and (record.flags & modeSep) == (score.flags & modeSep) then
2022-11-18 01:35:59 +01:00
LiveStore[map][checksum][i] = score
return
end
end
2022-11-18 01:35:59 +01:00
table.insert(LiveStore[map][checksum], score)
-- TODO: remove excess records
end
local MSK_SPEED = 0xF0
local MSK_WEIGHT = 0xF
local function stat_str(stat)
if stat then
return string.format("%d%d", (stat & MSK_SPEED) >> 4, stat & MSK_WEIGHT)
end
return "0"
end
-- GLOBAL
-- Save a record to the LiveStore and write to disk
-- SaveRecord will replace the record holders previous record but it will not compare any record times
local function SaveRecord(score, map, modeSep)
insertOrReplaceRecord(map, score, modeSep)
print("Saving score")
if not isserver then return end
local f = assert(
io.open(LEADERBOARD_FILE, "w"),
"Failed to open file for writing: "..LEADERBOARD_FILE
)
f:setvbuf("line")
2022-11-18 01:35:59 +01:00
for mapid, checksums in pairs(LiveStore) do
for checksum, records in pairs(checksums) do
for _, record in ipairs(records) do
-- Insert checksum if missing
if (not record.checksum) or record.checksum == "" then
record.checksum = mapChecksum(mapid)
end
f:write(
mapid, "\t",
record.name, "\t",
record.skin, "\t",
record.color, "\t",
record.time, "\t",
table.concat(record.splits, " "), "\t",
record.flags, "\t",
stat_str(record.stat), "\t",
record.checksum or "", "\n"
)
2022-11-14 22:17:30 +01:00
end
end
end
f:close()
end
rawset(_G, "lb_save_record", SaveRecord)
local function netvars(net)
LiveStore = net($)
end
addHook("NetVars", netvars)
2022-11-14 22:17:30 +01:00
local function score_t(map, name, skin, color, time, splits, flags, stat, checksum)
return {
["map"] = map,
["name"] = name,
["skin"] = skin,
["color"] = color,
["time"] = time,
["splits"] = splits,
["flags"] = flags,
2022-11-14 22:17:30 +01:00
["stat"] = stat,
["checksum"] = checksum
}
end
2022-11-16 16:09:53 +01:00
function parseScore(str)
-- Leaderboard is stored in the following tab separated format
-- mapnum, name, skin, color, time, splits, flags, stat
local t = {}
for word in (str.."\t"):gmatch("(.-)\t") do
table.insert(t, word)
end
local splits = {}
if t[6] != nil then
for str in t[6]:gmatch("([^ ]+)") do
table.insert(splits, tonumber(str))
end
end
local flags = 0
if t[7] != nil then
flags = tonumber(t[7])
end
local stats = nil
if t[8] != nil then
if #t[8] >= 2 then
local speed = tonumber(string.sub(t[8], 1, 1))
local weight = tonumber(string.sub(t[8], 2, 2))
stats = stat_t(speed, weight)
end
end
2022-11-14 22:17:30 +01:00
local checksum = t[9]
return score_t(
tonumber(t[1]), -- Map
t[2], -- Name
t[3], -- Skin
t[4], -- Color
tonumber(t[5]), -- Time
splits,
flags,
2022-11-14 22:17:30 +01:00
stats,
checksum
)
end
rawset(_G, "lb_parse_score", parseScore)
-- Load the livestore
2022-11-14 22:17:30 +01:00
if isserver then
local f = assert(
io.open(LEADERBOARD_FILE, "r"),
"Failed to open file: "..LEADERBOARD_FILE
)
2022-11-14 22:17:30 +01:00
for l in f:lines() do
local score = parseScore(l)
LiveStore[score.map] = $ or {}
2022-11-18 01:35:59 +01:00
LiveStore[score.map][score.checksum] = $ or {}
table.insert(LiveStore[score.map][score.checksum], score)
end
2022-11-14 22:17:30 +01:00
f:close()
end