From 3a61ffcabd96a9e81c248d39734a57c4ac74aabc Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 7 Oct 2022 01:35:24 +0200 Subject: [PATCH 01/21] allow non netvar stored, cold record loading from lua --- browser.lua | 44 ++++---- lb_common.lua | 20 ++++ lb_store.lua | 218 ++++++++++++++++++++++++++++++++++++++ leaderboard.lua | 274 ++++++++++-------------------------------------- 4 files changed, 319 insertions(+), 237 deletions(-) create mode 100644 lb_store.lua diff --git a/browser.lua b/browser.lua index b493e81..c2e1d02 100644 --- a/browser.lua +++ b/browser.lua @@ -1,15 +1,21 @@ -local leaderboard = nil +local MapRecords local maps local mapIndex = 1 local scrollPos = 1 local modes = nil local mode = 1 local prefMode = nil +local ModeSep + +---- Imported functions ---- --- Imported functions ------- -- lb_common.lua local ZoneAct = lb_ZoneAct local TicsToTime = lb_TicsToTime + +-- lb_store.lua +local GetMapRecords = lb_get_map_records + ----------------------------- local cv_kartencore @@ -25,10 +31,8 @@ end local function updateModes() -- set available modes for this map modes = {} - for mode, scoreTable in pairs(leaderboard) do - if scoreTable[getMap()] then - table.insert(modes, mode) - end + for mode, _ in pairs(MapRecords) do + table.insert(modes, mode) end table.sort(modes) @@ -46,6 +50,8 @@ local function updateMapIndex(n) mapIndex = mapIndexOffset(n) scrollPos = 1 + MapRecords = GetMapRecords(maps[mapIndex], ModeSep) + updateModes() end @@ -337,7 +343,7 @@ local function drawScore(v, i, pos, score, highlight) end local function drawBrowser(v, player) - if not leaderboard then return end + if not MapRecords then return end v.fadeScreen(0xFF00, 16) @@ -358,23 +364,20 @@ local function drawBrowser(v, player) if not modes then return end - local gamemode = leaderboard[modes[mode]] - if not gamemode then return end + local records = MapRecords[modes[mode]] + if not records then return end - local scoreTable = gamemode[getMap()] - if not scoreTable then return end - - local scores = #scoreTable - scrollPos = max(min(scrollPos, scores - 3), 1) - local endi = min(scrollPos + 7, scores) + local record_count = #records + scrollPos = max(min(scrollPos, record_count - 3), 1) + local endi = min(scrollPos + 7, record_count) for i = scrollPos, endi do - drawScore(v, i - scrollPos + 1, i, scoreTable[i], scoreTable[i].name == player.name) + drawScore(v, i - scrollPos + 1, i, records[i], records[i].name == player.name) end end rawset(_G, "DrawBrowser", drawBrowser) -local function initBrowser(lb) - leaderboard = lb +local function initBrowser(modeSep) + ModeSep = modeSep -- set mapIndex to current map for i, m in ipairs(maps) do @@ -384,6 +387,9 @@ local function initBrowser(lb) end end + -- initialize MapRecords + MapRecords = GetMapRecords(gamemap, ModeSep) + scrollPos = 1 updateModes() end @@ -485,6 +491,6 @@ local function netvars(net) mode = net($) prefMode = net($) scrollPos = net($) - leaderboard = net($) + MapRecords = net($) end addHook("NetVars", netvars) diff --git a/lb_common.lua b/lb_common.lua index ac7f479..0557e81 100644 --- a/lb_common.lua +++ b/lb_common.lua @@ -24,3 +24,23 @@ rawset(_G, "lb_ZoneAct", function(map) return z end) + +rawset(_G, "lb_stat_t", function(speed, weight) + if speed and weight then + return (speed << 4) | weight + end + return 0 +end) + + +local F_SPBBIG = 0x4 +local F_SPBEXP = 0x8 +-- True if a is better than b +rawset(_G, "lb_comp", function(a, b) + -- Calculates the difficulty, harder has higher priority + -- if s is positive then a is harder + -- if s is negative then b is harder + -- if s is 0 then compare time + local s = (a.flags & (F_SPBEXP | F_SPBBIG)) - (b.flags & (F_SPBEXP | F_SPBBIG)) + return s > 0 or not(s < 0 or a.time >= b.time) +end) diff --git a/lb_store.lua b/lb_store.lua new file mode 100644 index 0000000..d8684b5 --- /dev/null +++ b/lb_store.lua @@ -0,0 +1,218 @@ +-- 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 + +---------------------------- + +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 = {} + + +-- GLOBAL +-- Function for adding records from lua +local function AddColdStore(record) + ColdStore[record.map] = $ or {} + table.insert(ColdStore[record.map], record) +end +rawset(_G, "lb_add_coldstore_record", AddColdStore) + + +-- Insert mode separated records from the flat sourceTable into dest +local function insertRecords(dest, sourceTable, modeSep) + if not sourceTable then return end + + local mode = nil + for _, record in ipairs(sourceTable) 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 +local function GetMapRecords(map, modeSep) + local mapRecords = {} + + -- Insert ColdStore records + insertRecords(mapRecords, ColdStore[map], modeSep) + + -- Insert LiveStore records + insertRecords(mapRecords, LiveStore[map], 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) + LiveStore[map] = $ or {} + + for i, record in ipairs(LiveStore[map]) do + -- Replace the record + if record.name == score.name + and (record.flags & modeSep) == (score.flags & modeSep) then + LiveStore[map][i] = score + return + end + end + + table.insert(LiveStore[map], 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") + + for mapid, records in pairs(LiveStore) do + for _, record in ipairs(records) do + 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), "\n" + ) + end + end + + f:close() +end +rawset(_G, "lb_save_record", SaveRecord) + +local function netvars(net) + LiveStore = net($) +end + +addHook("NetVars", netvars) + +local function score_t(map, name, skin, color, time, splits, flags, stat) + return { + ["map"] = map, + ["name"] = name, + ["skin"] = skin, + ["color"] = color, + ["time"] = time, + ["splits"] = splits, + ["flags"] = flags, + ["stat"] = stat + } +end + +local 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 + + return score_t( + tonumber(t[1]), -- Map + t[2], -- Name + t[3], -- Skin + t[4], -- Color + tonumber(t[5]), -- Time + splits, + flags, + stats + ) +end +rawset(_G, "lb_parse_score", parseScore) + +-- Load the livestore +do + if isserver then + local f = assert( + io.open(LEADERBOARD_FILE, "r"), + "Failed to open file: "..LEADERBOARD_FILE + ) + + for l in f:lines() do + local score = parseScore(l) + print(score.name) + LiveStore[score.map] = $ or {} + table.insert(LiveStore[score.map], score) + end + + f:close() + end +end diff --git a/leaderboard.lua b/leaderboard.lua index fd79d4b..c8abf6b 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -1,9 +1,8 @@ -- Leaderboards written by Not -- Reusable --- Leaderboard Table --- [mode][mapnum][scoreTable] -local lb = {} +-- Holds the current maps records table including all modes +local MapRecords = {} local timeFinished = 0 local disable = false @@ -12,7 +11,7 @@ local splits = {} local PATCH = nil local help = true local EncoreInitial = nil -local scoreTable +local ScoreTable -- Text flash on finish @@ -94,11 +93,17 @@ local allowJoin -- lb_common.lua local ticsToTime = lb_TicsToTime local zoneAct = lb_ZoneAct +local stat_t = lb_stat_t +local lbComp = lb_comp -- browser.lua local InitBrowser = InitBrowser local DrawBrowser = DrawBrowser local BrowserController = BrowserController + +-- lb_store.lua +local GetMapRecords = lb_get_map_records +local SaveRecord = lb_save_record --------------- -- cvars @@ -164,79 +169,17 @@ local cv_interrupt = CV_RegisterVar({ end }) -local function setST(t, map, flags, scoreTable) - local mode = flags & ST_SEP - t[mode] = t[mode] or {} - t[mode][map] = scoreTable -end - -local function getST(t, map, flags) - local mode = flags & ST_SEP - return t[mode] and t[mode][map] or nil -end - -local function setScoreTable(map, flags, scoreTable) - setST(lb, map, flags, scoreTable) -end - -local function getScoreTable(map, flags) - return getST(lb, map, flags) -end - --- True if a is better than b -local function lbComp(a, b) - -- Calculates the difficulty, harder has higher priority - -- if s is positive then a is harder - -- if s is negative then b is harder - -- if s is 0 then compare time - local s = (a["flags"] & (F_SPBEXP | F_SPBBIG)) - (b["flags"] & (F_SPBEXP | F_SPBBIG)) - return s > 0 or not(s < 0 or a["time"] >= b["time"]) -end - -local function sortScores() - for mode, t in pairs(lb) do - for map, scoreTable in pairs(t) do - table.sort(scoreTable, lbComp) - setScoreTable(map, mode, scoreTable) - end - end -end - --- Reinitialize lb based on new ST_SEP value -local function reinit_lb() - local nlb = {} - - for mode, t in pairs(lb) do - for map, scoreTable in pairs(t) do - for i, score in ipairs(scoreTable) do - local st = getST(nlb, map, score["flags"]) or {} - table.insert(st, score) - setST(nlb, map, score["flags"], st) - end - end - end - - lb = nlb - sortScores() -end - local cv_spb_separate = CV_RegisterVar({ name = "lb_spb_combined", defaultvalue = 1, flags = CV_NETVAR | CV_CALL | CV_NOINIT, PossibleValue = CV_YesNo, func = function(v) - local curSep = ST_SEP - if v.value then ST_SEP = F_SPBATK else ST_SEP = F_SPBATK | F_SPBBIG | F_SPBEXP end - - if curSep != ST_SEP then - reinit_lb() - end end }) @@ -256,80 +199,6 @@ end local MSK_SPEED = 0xF0 local MSK_WEIGHT = 0xF -local function stat_t(speed, weight) - if speed and weight then - return (speed << 4) | weight - end - return 0 -end - -local function stat_str(stat) - if stat then - return string.format("%d%d", (stat & MSK_SPEED) >> 4, stat & MSK_WEIGHT) - end - - return "0" -end - --- Read the leaderboard -if isserver then - local f = io.open(FILENAME, "r") - if f then - for l in f:lines() do - -- Leaderboard is stored in the following tab separated format - -- mapnum, name, skin, color, time, splits, flags, stat - local t = {} - for word in (l+"\t"):gmatch("(.-)\t") do - table.insert(t, word) - end - - local flags = 0 - if t[7] != nil then - flags = tonumber(t[7]) - end - - scoreTable = getScoreTable(tonumber(t[1]), flags) or {} - - local spl = {} - if t[6] != nil then - for str in t[6]:gmatch("([^ ]+)") do - table.insert(spl, tonumber(str)) - end - 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 - - table.insert( - scoreTable, - score_t( - tonumber(t[1]), - t[2], - t[3], - t[4], - tonumber(t[5]), - spl, - flags, - stats - ) - ) - - setScoreTable(tonumber(t[1]), flags, scoreTable) - end - - sortScores() - f:close() - else - print("Failed to open file: ", FILENAME) - end -end - function allowJoin(v) if not cv_interrupt.value then local y @@ -423,7 +292,7 @@ local function initBrowser(player) return end - InitBrowser(lb) + InitBrowser(ST_SEP) drawState = DS_BROWSER player.afkTime = leveltime @@ -840,6 +709,8 @@ addHook("MapLoad", function() allowJoin(true) --printTable(lb) + + MapRecords = GetMapRecords(gamemap, ST_SEP) end ) @@ -1150,12 +1021,14 @@ local function drawScoreboard(v, player) cachePatches(v) local gui = cv_gui.value + + -- Force enable gui at start and end of the race if leveltime < START_TIME or player.exiting or player.lives == 0 then gui = GUI_ON end if gui then - stateFunctions[drawState](v, player, scoreTable, gui) + stateFunctions[drawState](v, player, ScoreTable, gui) end local pos = 0 @@ -1195,7 +1068,7 @@ end -- Find location of player and scroll to it function scroll_to(player) - local m = scoreTable or {} + local m = ScoreTable or {} scrollToPos = 2 for pos, score in ipairs(m) do @@ -1209,19 +1082,19 @@ function scroll_to(player) end -- Write skin stats to each score where there are none -local function writeStats() - for _, t in pairs(lb) do - for _, scoreTable in pairs(t) do - for _, score in ipairs(scoreTable) do - local skin = skins[score["skin"]] - if skin and not score["stat"] then - local stats = stat_t(skin.kartspeed, skin.kartweight) - score["stat"] = stats - end - end - end - end -end +--local function writeStats() +-- for _, t in pairs(lb) do +-- for _, scoreTable in pairs(t) do +-- for _, score in ipairs(scoreTable) do +-- local skin = skins[score["skin"]] +-- if skin and not score["stat"] then +-- local stats = stat_t(skin.kartspeed, skin.kartweight) +-- score["stat"] = stats +-- end +-- end +-- end +-- end +--end local function checkFlags(p) local flags = 0 @@ -1261,7 +1134,7 @@ local function saveTime(player) return end - scoreTable = $ or {} + ScoreTable = $ or {} local pskin = skins[player.mo.skin] local newscore = score_t( @@ -1276,16 +1149,9 @@ local function saveTime(player) ) -- Check if you beat your previous best - for i = 1, #scoreTable do - if scoreTable[i]["name"] == player.name then - if lbComp(newscore, scoreTable[i]) then - table.remove(scoreTable, i) - S_StartSound(nil, 130) - FlashTics = leveltime + TICRATE * 3 - FlashRate = 1 - FlashVFlags = YellowFlash - break - else + for i = 1, #ScoreTable do + if ScoreTable[i].name == player.name then + if not lbComp(newscore, ScoreTable[i]) then -- You suck lol S_StartSound(nil, 201) FlashTics = leveltime + TICRATE * 3 @@ -1297,61 +1163,33 @@ local function saveTime(player) end end - print("Saving score") - table.insert( - scoreTable, - newscore - ) - table.sort(scoreTable, lbComp) - while #scoreTable > cv_saves.value do - table.remove(scoreTable) - end + -- Save the record + SaveRecord(newscore, gamemap, ST_SEP) + -- Set players text flash and play chime sfx + S_StartSound(nil, 130) + FlashTics = leveltime + TICRATE * 3 + FlashRate = 1 + FlashVFlags = YellowFlash + + -- Reload the MapRecords + MapRecords = GetMapRecords(gamemap, ST_SEP) + + -- Set the updated ScoreTable + ScoreTable = MapRecords[Flags] + + -- Scroll the gui to the player entry scroll_to(player) - - setScoreTable(gamemap, Flags, scoreTable) - - if not StatTrack then - writeStats() - StatTrack = true - end - - if isserver then - local f = assert(io.open(FILENAME, "w")) - if f == nil then - print("Failed to open file for writing: " + FILENAME) - return - end - - for _, tbl in pairs(lb) do - for _, scoreTable in pairs(tbl) do - for _, score in ipairs(scoreTable) do - f:write( - score["map"], "\t", - score["name"], "\t", - score["skin"], "\t", - score["color"], "\t", - score["time"], "\t", - table.concat(score["splits"], " "), "\t", - score["flags"], "\t", - stat_str(score["stat"]), "\n" - ) - end - end - end - - f:close() - end end -- DEBUGGING ---local function saveLeaderboard(player, ...) --- timeFinished = tonumber(... or player.realtime) --- splits = {1000, 2000, 3000} --- saveTime(player) ---end ---COM_AddCommand("save", saveLeaderboard) +local function saveLeaderboard(player, ...) + timeFinished = tonumber(... or player.realtime) + splits = {1000, 2000, 3000} + saveTime(player) +end +COM_AddCommand("save", saveLeaderboard) local function regLap(player) if player.laps > prevLap and timeFinished == 0 then @@ -1461,7 +1299,7 @@ local function think() end end - scoreTable = getScoreTable(gamemap, Flags) + ScoreTable = MapRecords[ST_SEP & Flags] if not cv_teamchange then cv_teamchange = CV_FindVar("allowteamchange") @@ -1562,6 +1400,6 @@ local function netvars(net) drawState = net($) StatTrack = net($) EncoreInitial = net($) - lb = net($) + MapRecords = net($) end addHook("NetVars", netvars) From 5d59f6c6fcdf6aa26ae24e9bd70644ed30dec890 Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 7 Oct 2022 01:50:30 +0200 Subject: [PATCH 02/21] disable debugging command --- leaderboard.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/leaderboard.lua b/leaderboard.lua index c8abf6b..e2c9513 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -1184,12 +1184,12 @@ local function saveTime(player) end -- DEBUGGING -local function saveLeaderboard(player, ...) - timeFinished = tonumber(... or player.realtime) - splits = {1000, 2000, 3000} - saveTime(player) -end -COM_AddCommand("save", saveLeaderboard) +--local function saveLeaderboard(player, ...) +-- timeFinished = tonumber(... or player.realtime) +-- splits = {1000, 2000, 3000} +-- saveTime(player) +--end +--COM_AddCommand("save", saveLeaderboard) local function regLap(player) if player.laps > prevLap and timeFinished == 0 then From 7bd8a13b145c9db11e309f9be1db8a6ef491f023 Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 7 Oct 2022 17:16:37 +0200 Subject: [PATCH 03/21] add state netvars --- browser.lua | 1 + leaderboard.lua | 15 ++++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/browser.lua b/browser.lua index c2e1d02..c2fd4da 100644 --- a/browser.lua +++ b/browser.lua @@ -492,5 +492,6 @@ local function netvars(net) prefMode = net($) scrollPos = net($) MapRecords = net($) + ModeSep = net($) end addHook("NetVars", netvars) diff --git a/leaderboard.lua b/leaderboard.lua index e2c9513..f0b48c5 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -4,7 +4,7 @@ -- Holds the current maps records table including all modes local MapRecords = {} -local timeFinished = 0 +local TimeFinished = 0 local disable = false local prevLap = 0 local splits = {} @@ -699,7 +699,7 @@ COM_AddCommand("rival", findRival) --end addHook("MapLoad", function() - timeFinished = 0 + TimeFinished = 0 splits = {} prevLap = 0 drawState = DS_DEFAULT @@ -1142,7 +1142,7 @@ local function saveTime(player) player.name, player.mo.skin, player.skincolor, - timeFinished, + TimeFinished, splits, Flags, stat_t(player.HMRs or pskin.kartspeed, player.HMRw or pskin.kartweight) @@ -1185,14 +1185,14 @@ end -- DEBUGGING --local function saveLeaderboard(player, ...) --- timeFinished = tonumber(... or player.realtime) +-- TimeFinished = tonumber(... or player.realtime) -- splits = {1000, 2000, 3000} -- saveTime(player) --end --COM_AddCommand("save", saveLeaderboard) local function regLap(player) - if player.laps > prevLap and timeFinished == 0 then + if player.laps > prevLap and TimeFinished == 0 then prevLap = player.laps table.insert(splits, player.realtime) showSplit = 5 * TICRATE @@ -1307,8 +1307,8 @@ local function think() if p then -- must be done before browser control - if p.laps >= mapheaderinfo[gamemap].numlaps and timeFinished == 0 then - timeFinished = p.realtime + if p.laps >= mapheaderinfo[gamemap].numlaps and TimeFinished == 0 then + TimeFinished = p.realtime saveTime(p) end @@ -1401,5 +1401,6 @@ local function netvars(net) StatTrack = net($) EncoreInitial = net($) MapRecords = net($) + TimeFinished = net($) end addHook("NetVars", netvars) From 107a81f67c60a8818d54cdfd76cc58fbc25e154c Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 7 Oct 2022 17:59:27 +0200 Subject: [PATCH 04/21] fix 'rival' command --- lb_store.lua | 21 ++++++++++++++++++++ leaderboard.lua | 51 ++++++++++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/lb_store.lua b/lb_store.lua index d8684b5..db85c19 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -18,6 +18,27 @@ local ColdStore = {} local LiveStore = {} +-- GLOBAL +-- Returns a list of all maps with records +local function MapList() + local maps = {} + for map in pairs(ColdStore) do + maps[map] = true + end + for map in pairs(LiveStore) do + maps[map] = true + end + + local maplist = {} + for map in pairs(maps) do + table.insert(maplist, map) + end + table.sort(maplist) + + return maplist +end +rawset(_G, "lb_map_list", MapList) + -- GLOBAL -- Function for adding records from lua local function AddColdStore(record) diff --git a/leaderboard.lua b/leaderboard.lua index f0b48c5..cbbdbad 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -104,6 +104,7 @@ local BrowserController = BrowserController -- lb_store.lua local GetMapRecords = lb_get_map_records local SaveRecord = lb_save_record +local MapList = lb_map_list --------------- -- cvars @@ -396,6 +397,7 @@ end local function records(player, ...) local mapid = ... local mapnum = gamemap + local mapRecords = MapRecords if mapid then mapnum = mapnumFromExtended(mapid) @@ -403,6 +405,8 @@ local function records(player, ...) CONS_Printf(player, string.format("Invalid map name: %s", mapid)) return end + + mapRecords = GetMapRecords(mapnum, ST_SEP) end local map = mapheaderinfo[mapnum] @@ -430,35 +434,33 @@ local function records(player, ...) CONS_Printf(player, "\x85UNKNOWN MAP") end - for mode, maps in pairs(lb) do - local maptbl = maps[mapnum] - if not maptbl then continue end + for mode, records in pairs(mapRecords) do CONS_Printf(player, "") CONS_Printf(player, modeToString(mode)) -- don't print flags for time attack if mode then - for i, tbl in ipairs(maptbl) do + for i, score in ipairs(records) do CONS_Printf( player, string.format( "%2d %-21s \x89%8s \x80%s", i, - tbl["name"], - ticsToTime(tbl["time"]), - modeToString(tbl["flags"]) + score["name"], + ticsToTime(score["time"]), + modeToString(score["flags"]) ) ) end else - for i, tbl in ipairs(maptbl) do + for i, score in ipairs(records) do CONS_Printf( player, string.format( "%2d %-21s \x89%8s", i, - tbl["name"], - ticsToTime(tbl["time"]) + score["name"], + ticsToTime(score["time"]) ) ) end @@ -562,21 +564,26 @@ local function findRival(player, ...) local totalScores = 0 local totalDiff = 0 - CONS_Printf(player, string.format("\x89%s's times:", rival)) CONS_Printf(player, "MAP Time Diff Mode") - for mode, tbl in pairs(lb) do - scores[mode] = {} + local maplist = MapList() + local mapRecords + local rivalScore + local yourScore + for i = 1, #maplist do + mapRecords = GetMapRecords(maplist[i], ST_SEP) - for map, scoreTable in pairs(tbl) do - local rivalScore = nil - local yourScore = nil + for mode, records in pairs(mapRecords) do + scores[mode] = $ or {} - for _, score in pairs(scoreTable) do - if score["name"] == player.name then + rivalScore = nil + yourScore = nil + + for _, score in ipairs(records) do + if score.name == player.name then yourScore = score - elseif score["name"] == rival then + elseif score.name == rival then rivalScore = score end @@ -586,7 +593,7 @@ local function findRival(player, ...) end if rivalScore and yourScore then - totalDiff = totalDiff + yourScore["time"] - rivalScore["time"] + totalDiff = totalDiff + yourScore.time - rivalScore.time end if rivalScore then @@ -594,8 +601,8 @@ local function findRival(player, ...) table.insert( scores[mode], { - ["rival"] = rivalScore, - ["your"] = yourScore + rival = rivalScore, + your = yourScore } ) end From f824e760e3519d9cf3b0bd5a61d9dcc307e47ab5 Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 8 Oct 2022 02:11:55 +0200 Subject: [PATCH 05/21] remove StatTrack --- leaderboard.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/leaderboard.lua b/leaderboard.lua index cbbdbad..04aea58 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -28,9 +28,6 @@ local RedFlash = { [1] = 0 } --- Tracks if stats have been written or not -local StatTrack = false - local UNCLAIMED = "Unclaimed Record" local HELP_MESSAGE = "\x89Leaderboard Commands:\nretry exit findmap changelevel spba_clearcheats lb_gui rival scroll encore records levelselect" local FILENAME = "leaderboard.txt" @@ -1405,7 +1402,6 @@ local function netvars(net) splits = net($) prevLap = net($) drawState = net($) - StatTrack = net($) EncoreInitial = net($) MapRecords = net($) TimeFinished = net($) From 4a1e42c5cb8563d91833a3c2e7a7fc5b09ea9ebd Mon Sep 17 00:00:00 2001 From: Not Date: Mon, 14 Nov 2022 17:53:21 +0100 Subject: [PATCH 06/21] remove print --- lb_store.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lb_store.lua b/lb_store.lua index db85c19..5ca9f2f 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -229,7 +229,6 @@ do for l in f:lines() do local score = parseScore(l) - print(score.name) LiveStore[score.map] = $ or {} table.insert(LiveStore[score.map], score) end From 52316347dabd330aa9d64f6881f7cea33bbed445 Mon Sep 17 00:00:00 2001 From: Not Date: Mon, 14 Nov 2022 17:53:40 +0100 Subject: [PATCH 07/21] add coldstore tool --- tools/coldstore.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 tools/coldstore.py diff --git a/tools/coldstore.py b/tools/coldstore.py new file mode 100755 index 0000000..b4ee2c0 --- /dev/null +++ b/tools/coldstore.py @@ -0,0 +1,94 @@ +#!/bin/python +import sys +from os import linesep + +if len(sys.argv) != 4 or not sys.argv[1] or not sys.argv[2] or not sys.argv[3]: + print("Usage: coldstore.py ") + quit() + +leaderboard_txt = sys.argv[1] +coldstore_txt = sys.argv[2] +records_lua = sys.argv[3] + +def ParseScore(score): + # Map Name Skin Color Time Splits Flags Stat + split = score.split("\t") + return { + "map": split[0], + "name": split[1], + "skin": split[2], + "color": split[3], + "time": int(split[4]), + "splits": split[5], + "flags": int(split[6]), + "stat": split[7] + } + +# Compare scores +def CompareScore(a, b): + return a["time"] < b["time"] + +F_SEP = 0xF +def SameScore(a, b): + return a["name"] == b["name"] and (a["flags"] & F_SEP) == (b["flags"] & F_SEP) + +def LoadRecordsFromFile(path): + records = [] + with open(path, "r") as f: + for line in f.readlines(): + records.append(ParseScore(line.strip())) + return records + +def AddScore(records, score): + mapid = score["map"] + mapTable = records.get(mapid) or [] + + for i in range(len(mapTable)): + scoreb = mapTable[i] + if SameScore(score, scoreb): + if CompareScore(score, scoreb): + mapTable[i] = score + records[mapid] = mapTable + return + mapTable.append(score) + records[mapid] = mapTable + +# load leaderboard.txt and coldstore.txt +recordsList = LoadRecordsFromFile(leaderboard_txt) +recordsList.extend(LoadRecordsFromFile(coldstore_txt)) + + +# construct the map tables +records = {} +for score in recordsList: + AddScore(records, score) + +# convert records to flat list +recordsList = [] +for mapTable in records.values(): + for score in mapTable: + recordsList.append("\t".join([str(v) for v in list(score.values())])) + +# truncate and write records to coldstore +with open(coldstore_txt, "w") as f: + for score in recordsList: + f.write(score + linesep) + +luaA = """local ParseScore = lb_parse_score +local AddColdStore = lb_add_coldstore_record +local records = { +""" +luaB = """} +for _, str in ipairs(records) do + AddColdStore(ParseScore(str)) +end +""" +# pack the records.lua file +with open(records_lua, "w") as f: + f.write(luaA) + for score in recordsList: + f.write("\"{}\",{}".format(score, linesep)) + f.write(luaB) + +# truncate leaderboard.txt +with open(leaderboard_txt, "w"): pass From 08c1af30738fac4e78a2ef4d94a63ef79550aa7d Mon Sep 17 00:00:00 2001 From: Not Date: Mon, 14 Nov 2022 22:17:30 +0100 Subject: [PATCH 08/21] insert checksums --- lb_store.lua | 62 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/lb_store.lua b/lb_store.lua index 5ca9f2f..4352d97 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -122,6 +122,26 @@ local function stat_str(stat) return "0" end +local function djb2(message) + local digest = 5381 + for c in message:gmatch(".") do + digest = (($ << 5) + $) + string.byte(c) + end + + return digest +end + +-- Produce a checksum by using the maps title, subtitle and zone +local function mapChecksum(mapnum) + local mh = mapheaderinfo[mapnum] + if not mh then + return nil + end + + local digest = string.format("%04x", djb2(mh.lvlttl..mh.subttl..mh.zonttl)) + return string.sub(digest, #digest - 3) +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 @@ -141,6 +161,11 @@ local function SaveRecord(score, map, modeSep) for mapid, records in pairs(LiveStore) 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", @@ -149,7 +174,8 @@ local function SaveRecord(score, map, modeSep) record.time, "\t", table.concat(record.splits, " "), "\t", record.flags, "\t", - stat_str(record.stat), "\n" + stat_str(record.stat), "\t", + record.checksum or "", "\n" ) end end @@ -164,7 +190,7 @@ end addHook("NetVars", netvars) -local function score_t(map, name, skin, color, time, splits, flags, stat) +local function score_t(map, name, skin, color, time, splits, flags, stat, checksum) return { ["map"] = map, ["name"] = name, @@ -173,7 +199,8 @@ local function score_t(map, name, skin, color, time, splits, flags, stat) ["time"] = time, ["splits"] = splits, ["flags"] = flags, - ["stat"] = stat + ["stat"] = stat, + ["checksum"] = checksum } end @@ -206,6 +233,8 @@ local function parseScore(str) end end + local checksum = t[9] + return score_t( tonumber(t[1]), -- Map t[2], -- Name @@ -214,25 +243,24 @@ local function parseScore(str) tonumber(t[5]), -- Time splits, flags, - stats + stats, + checksum ) end rawset(_G, "lb_parse_score", parseScore) -- Load the livestore -do - if isserver then - local f = assert( - io.open(LEADERBOARD_FILE, "r"), - "Failed to open file: "..LEADERBOARD_FILE - ) +if isserver then + local f = assert( + io.open(LEADERBOARD_FILE, "r"), + "Failed to open file: "..LEADERBOARD_FILE + ) - for l in f:lines() do - local score = parseScore(l) - LiveStore[score.map] = $ or {} - table.insert(LiveStore[score.map], score) - end - - f:close() + for l in f:lines() do + local score = parseScore(l) + LiveStore[score.map] = $ or {} + table.insert(LiveStore[score.map], score) end + + f:close() end From 34da7e292915f202a17954365aecbf3e960f3b71 Mon Sep 17 00:00:00 2001 From: Not Date: Mon, 14 Nov 2022 22:18:01 +0100 Subject: [PATCH 09/21] reject records missing checksum --- tools/coldstore.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tools/coldstore.py b/tools/coldstore.py index b4ee2c0..062cc4d 100755 --- a/tools/coldstore.py +++ b/tools/coldstore.py @@ -13,6 +13,11 @@ records_lua = sys.argv[3] def ParseScore(score): # Map Name Skin Color Time Splits Flags Stat split = score.split("\t") + + checksum = "" + if len(split) > 8: + checksum = split[8] + return { "map": split[0], "name": split[1], @@ -21,7 +26,8 @@ def ParseScore(score): "time": int(split[4]), "splits": split[5], "flags": int(split[6]), - "stat": split[7] + "stat": split[7], + "checksum": checksum } # Compare scores @@ -65,14 +71,20 @@ for score in recordsList: # convert records to flat list recordsList = [] +rejected = [] for mapTable in records.values(): for score in mapTable: - recordsList.append("\t".join([str(v) for v in list(score.values())])) + scoreStr = "\t".join([str(v) for v in list(score.values())]) + # only allow records with checksums + if score["checksum"] != "": + recordsList.append(scoreStr) + else: + rejected.append(scoreStr) # truncate and write records to coldstore with open(coldstore_txt, "w") as f: for score in recordsList: - f.write(score + linesep) + f.write(score + linesep) luaA = """local ParseScore = lb_parse_score local AddColdStore = lb_add_coldstore_record @@ -90,5 +102,7 @@ with open(records_lua, "w") as f: f.write("\"{}\",{}".format(score, linesep)) f.write(luaB) -# truncate leaderboard.txt -with open(leaderboard_txt, "w"): pass +# truncate and rewrite rejected scores to leaderboard.txt +with open(leaderboard_txt, "w") as f: + for score in rejected: + f.write(score + linesep) From 0a21799e60a7a4bc408db3161a6719eed7507ed5 Mon Sep 17 00:00:00 2001 From: Not Date: Wed, 16 Nov 2022 16:09:53 +0100 Subject: [PATCH 10/21] add coldstore string loading --- lb_store.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lb_store.lua b/lb_store.lua index 4352d97..d22e4ec 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -17,6 +17,8 @@ local ColdStore = {} -- Livestore are new records nad records loaded from leaderboard.txt file local LiveStore = {} +-- parse score function +local parseScore -- GLOBAL -- Returns a list of all maps with records @@ -40,13 +42,19 @@ end rawset(_G, "lb_map_list", MapList) -- GLOBAL --- Function for adding records from lua +-- Function for adding a single record from lua local function AddColdStore(record) ColdStore[record.map] = $ or {} table.insert(ColdStore[record.map], record) end rawset(_G, "lb_add_coldstore_record", AddColdStore) +-- 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 local function insertRecords(dest, sourceTable, modeSep) @@ -204,7 +212,7 @@ local function score_t(map, name, skin, color, time, splits, flags, stat, checks } end -local function parseScore(str) +function parseScore(str) -- Leaderboard is stored in the following tab separated format -- mapnum, name, skin, color, time, splits, flags, stat local t = {} From 68e44e534b6d61dea343eab97eef0501a80b38a2 Mon Sep 17 00:00:00 2001 From: Not Date: Wed, 16 Nov 2022 16:10:14 +0100 Subject: [PATCH 11/21] esacpe special characters --- tools/coldstore.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/coldstore.py b/tools/coldstore.py index 062cc4d..c5e94fd 100755 --- a/tools/coldstore.py +++ b/tools/coldstore.py @@ -1,4 +1,4 @@ -#!/bin/python +#!/usr/bin/env python3 import sys from os import linesep @@ -86,20 +86,23 @@ with open(coldstore_txt, "w") as f: for score in recordsList: f.write(score + linesep) -luaA = """local ParseScore = lb_parse_score -local AddColdStore = lb_add_coldstore_record -local records = { +luaA = """do + local AddColdStore = lb_add_coldstore_record_string + local records = { """ -luaB = """} -for _, str in ipairs(records) do - AddColdStore(ParseScore(str)) +luaB = """ } + for _, str in ipairs(records) do + AddColdStore(str) + end end """ # pack the records.lua file with open(records_lua, "w") as f: f.write(luaA) for score in recordsList: - f.write("\"{}\",{}".format(score, linesep)) + score = score.replace("\\", "\\\\") + score = score.replace("\"", "\\\"") + f.write("\t\t\"{}\",{}".format(score, linesep)) f.write(luaB) # truncate and rewrite rejected scores to leaderboard.txt From cf74af89457ecbeab366fa55732938dec954dac5 Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 18 Nov 2022 01:35:59 +0100 Subject: [PATCH 12/21] map identity by checksum --- browser.lua | 5 ++- lb_common.lua | 20 +++++++++ lb_store.lua | 105 ++++++++++++++++++++++----------------------- leaderboard.lua | 28 ++++++------ tools/coldstore.py | 2 +- 5 files changed, 91 insertions(+), 69 deletions(-) diff --git a/browser.lua b/browser.lua index c2fd4da..d7c7bb0 100644 --- a/browser.lua +++ b/browser.lua @@ -12,6 +12,7 @@ local ModeSep -- lb_common.lua local ZoneAct = lb_ZoneAct local TicsToTime = lb_TicsToTime +local mapChecksum = lb_map_checksum -- lb_store.lua local GetMapRecords = lb_get_map_records @@ -50,7 +51,7 @@ local function updateMapIndex(n) mapIndex = mapIndexOffset(n) scrollPos = 1 - MapRecords = GetMapRecords(maps[mapIndex], ModeSep) + MapRecords = GetMapRecords(maps[mapIndex], mapChecksum(maps[mapIndex]), ModeSep) updateModes() end @@ -388,7 +389,7 @@ local function initBrowser(modeSep) end -- initialize MapRecords - MapRecords = GetMapRecords(gamemap, ModeSep) + MapRecords = GetMapRecords(gamemap, mapChecksum(gamemap), ModeSep) scrollPos = 1 updateModes() diff --git a/lb_common.lua b/lb_common.lua index 0557e81..5ca198e 100644 --- a/lb_common.lua +++ b/lb_common.lua @@ -44,3 +44,23 @@ rawset(_G, "lb_comp", function(a, b) local s = (a.flags & (F_SPBEXP | F_SPBBIG)) - (b.flags & (F_SPBEXP | F_SPBBIG)) return s > 0 or not(s < 0 or a.time >= b.time) end) + +local function djb2(message) + local digest = 5381 + for c in message:gmatch(".") do + digest = (($ << 5) + $) + string.byte(c) + end + + return digest +end + +-- Produce a checksum by using the maps title, subtitle and zone +rawset(_G, "lb_map_checksum", function(mapnum) + local mh = mapheaderinfo[mapnum] + if not mh then + return nil + end + + local digest = string.format("%04x", djb2(mh.lvlttl..mh.subttl..mh.zonttl)) + return string.sub(digest, #digest - 3) +end) diff --git a/lb_store.lua b/lb_store.lua index d22e4ec..dfe867f 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -5,6 +5,7 @@ -- lb_common.lua local stat_t = lb_stat_t local lbComp = lb_comp +local mapChecksum = lb_map_checksum ---------------------------- @@ -24,18 +25,26 @@ local parseScore -- Returns a list of all maps with records local function MapList() local maps = {} - for map in pairs(ColdStore) do - maps[map] = true + for mapid, checksums in pairs(ColdStore) do + maps[mapid] = $ or {} + for checksum in pairs(checksums) do + maps[mapid][checksum] = true + end end - for map in pairs(LiveStore) do - maps[map] = true + for mapid, checksums in pairs(LiveStore) do + maps[mapid] = $ or {} + for checksum in pairs(checksums) do + maps[mapid][checksum] = true + end end local maplist = {} - for map in pairs(maps) do - table.insert(maplist, map) + for mapid, checksums in pairs(maps) do + for checksum in pairs(checksums) do + table.insert(maplist, {["id"] = mapid, ["checksum"] = checksum}) + end end - table.sort(maplist) + table.sort(maplist, function(a, b) return a.id < b.id end) return maplist end @@ -45,7 +54,9 @@ rawset(_G, "lb_map_list", MapList) -- Function for adding a single record from lua local function AddColdStore(record) ColdStore[record.map] = $ or {} - table.insert(ColdStore[record.map], record) + ColdStore[record.map][record.checksum] = $ or {} + + table.insert(ColdStore[record.map][record.checksum], record) end rawset(_G, "lb_add_coldstore_record", AddColdStore) @@ -57,11 +68,12 @@ end rawset(_G, "lb_add_coldstore_record_string", AddColdStoreString) -- Insert mode separated records from the flat sourceTable into dest -local function insertRecords(dest, sourceTable, modeSep) +local function insertRecords(dest, sourceTable, checksum, modeSep) if not sourceTable then return end + if not sourceTable[checksum] then return end local mode = nil - for _, record in ipairs(sourceTable) do + for _, record in ipairs(sourceTable[checksum]) do mode = record.flags & modeSep dest[mode] = $ or {} table.insert(dest[mode], record) @@ -71,14 +83,14 @@ end -- GLOBAL -- Construct the leaderboard table of the supplied mapid -- combines the ColdStore and LiveStore records -local function GetMapRecords(map, modeSep) +local function GetMapRecords(map, checksum, modeSep) local mapRecords = {} -- Insert ColdStore records - insertRecords(mapRecords, ColdStore[map], modeSep) + insertRecords(mapRecords, ColdStore[map], checksum, modeSep) -- Insert LiveStore records - insertRecords(mapRecords, LiveStore[map], modeSep) + insertRecords(mapRecords, LiveStore[map], checksum, modeSep) -- Sort records for _, records in pairs(mapRecords) do @@ -104,18 +116,20 @@ end rawset(_G, "lb_get_map_records", GetMapRecords) local function insertOrReplaceRecord(map, score, modeSep) + local checksum = mapChecksum(map) LiveStore[map] = $ or {} + LiveStore[map][checksum] = $ or {} - for i, record in ipairs(LiveStore[map]) do + 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 - LiveStore[map][i] = score + LiveStore[map][checksum][i] = score return end end - table.insert(LiveStore[map], score) + table.insert(LiveStore[map][checksum], score) -- TODO: remove excess records end @@ -130,26 +144,6 @@ local function stat_str(stat) return "0" end -local function djb2(message) - local digest = 5381 - for c in message:gmatch(".") do - digest = (($ << 5) + $) + string.byte(c) - end - - return digest -end - --- Produce a checksum by using the maps title, subtitle and zone -local function mapChecksum(mapnum) - local mh = mapheaderinfo[mapnum] - if not mh then - return nil - end - - local digest = string.format("%04x", djb2(mh.lvlttl..mh.subttl..mh.zonttl)) - return string.sub(digest, #digest - 3) -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 @@ -167,24 +161,26 @@ local function SaveRecord(score, map, modeSep) f:setvbuf("line") - for mapid, records in pairs(LiveStore) do - for _, record in ipairs(records) do - -- Insert checksum if missing - if (not record.checksum) or record.checksum == "" then - record.checksum = mapChecksum(mapid) - end + 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" - ) + 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" + ) + end end end @@ -267,7 +263,8 @@ if isserver then for l in f:lines() do local score = parseScore(l) LiveStore[score.map] = $ or {} - table.insert(LiveStore[score.map], score) + LiveStore[score.map][score.checksum] = $ or {} + table.insert(LiveStore[score.map][score.checksum], score) end f:close() diff --git a/leaderboard.lua b/leaderboard.lua index 04aea58..e08cb70 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -92,6 +92,7 @@ local ticsToTime = lb_TicsToTime local zoneAct = lb_ZoneAct local stat_t = lb_stat_t local lbComp = lb_comp +local mapChecksum = lb_map_checksum -- browser.lua local InitBrowser = InitBrowser @@ -403,7 +404,7 @@ local function records(player, ...) return end - mapRecords = GetMapRecords(mapnum, ST_SEP) + mapRecords = GetMapRecords(mapnum, mapChecksum(mapnum), ST_SEP) end local map = mapheaderinfo[mapnum] @@ -562,14 +563,14 @@ local function findRival(player, ...) local totalDiff = 0 CONS_Printf(player, string.format("\x89%s's times:", rival)) - CONS_Printf(player, "MAP Time Diff Mode") + CONS_Printf(player, "MAP CHCK Time Diff Mode") local maplist = MapList() local mapRecords local rivalScore local yourScore for i = 1, #maplist do - mapRecords = GetMapRecords(maplist[i], ST_SEP) + mapRecords = GetMapRecords(maplist[i].id, maplist[i].checksum, ST_SEP) for mode, records in pairs(mapRecords) do scores[mode] = $ or {} @@ -636,9 +637,10 @@ local function findRival(player, ...) CONS_Printf( player, string.format( - "%s %8s %s%9s \x80%s", - G_BuildMapName(score["rival"]["map"]), - ticsToTime(score["rival"]["time"]), + "%s %4s %8s %s%9s \x80%s", + G_BuildMapName(score.rival.map), + score.rival.checksum, + ticsToTime(score.rival.time), color, sym[diff<0] + ticsToTime(abs(diff)), modestr @@ -648,9 +650,10 @@ local function findRival(player, ...) CONS_Printf( player, string.format( - "%s %8s %9s %s", - G_BuildMapName(score["rival"]["map"]), - ticsToTime(score["rival"]["time"]), + "%s %4s %8s %9s %s", + G_BuildMapName(score.rival.map), + score.rival.checksum, + ticsToTime(score.rival.time), ticsToTime(0, true), modestr ) @@ -714,7 +717,7 @@ addHook("MapLoad", function() allowJoin(true) --printTable(lb) - MapRecords = GetMapRecords(gamemap, ST_SEP) + MapRecords = GetMapRecords(gamemap, mapChecksum(gamemap), ST_SEP) end ) @@ -1149,7 +1152,8 @@ local function saveTime(player) TimeFinished, splits, Flags, - stat_t(player.HMRs or pskin.kartspeed, player.HMRw or pskin.kartweight) + stat_t(player.HMRs or pskin.kartspeed, player.HMRw or pskin.kartweight), + mapChecksum(gamemap) ) -- Check if you beat your previous best @@ -1178,7 +1182,7 @@ local function saveTime(player) FlashVFlags = YellowFlash -- Reload the MapRecords - MapRecords = GetMapRecords(gamemap, ST_SEP) + MapRecords = GetMapRecords(gamemap, mapChecksum(gamemap), ST_SEP) -- Set the updated ScoreTable ScoreTable = MapRecords[Flags] diff --git a/tools/coldstore.py b/tools/coldstore.py index c5e94fd..fb1f57e 100755 --- a/tools/coldstore.py +++ b/tools/coldstore.py @@ -36,7 +36,7 @@ def CompareScore(a, b): F_SEP = 0xF def SameScore(a, b): - return a["name"] == b["name"] and (a["flags"] & F_SEP) == (b["flags"] & F_SEP) + return a["name"] == b["name"] and a["checksum"] == b["checksum"] and (a["flags"] & F_SEP) == (b["flags"] & F_SEP) def LoadRecordsFromFile(path): records = [] From 57120e257adc403b87aead6efbc7b1ae4e10b7e7 Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 01:26:46 +0100 Subject: [PATCH 13/21] add commands for moving and checksumming records --- lb_common.lua | 43 +++++++ lb_store.lua | 291 ++++++++++++++++++++++++++++++++++-------------- leaderboard.lua | 139 ++++++++++++----------- 3 files changed, 320 insertions(+), 153 deletions(-) diff --git a/lb_common.lua b/lb_common.lua index 5ca198e..9a11c3e 100644 --- a/lb_common.lua +++ b/lb_common.lua @@ -1,3 +1,17 @@ +rawset(_G, "lb_score_t", function(map, name, skin, color, time, splits, flags, stat, checksum) + return { + ["map"] = map, + ["name"] = name, + ["skin"] = skin, + ["color"] = color, + ["time"] = time, + ["splits"] = splits, + ["flags"] = flags, + ["stat"] = stat, + ["checksum"] = checksum + } +end) + rawset(_G, "lb_TicsToTime", function(tics, pure) if tics == 0 and pure then return "-:--:--" @@ -64,3 +78,32 @@ rawset(_G, "lb_map_checksum", function(mapnum) local digest = string.format("%04x", djb2(mh.lvlttl..mh.subttl..mh.zonttl)) return string.sub(digest, #digest - 3) end) + +rawset(_G, "lb_mapnum_from_extended", function(map) + local p, q = map:upper():match("MAP(%w)(%w)$", 1) + if not (p and q) then + return nil + end + + local mapnum = 0 + local A = string.byte("A") + + if tonumber(p) != nil then + -- Non extended map numbers + if tonumber(q) == nil then + return nil + end + mapnum = tonumber(p) * 10 + tonumber(q) + else + --Extended map numbers + p = string.byte(p) - A + local qn = tonumber(q) + if qn == nil then + qn = string.byte(q) - A + 10 + end + + mapnum = 36 * p + qn + 100 + end + + return mapnum +end) diff --git a/lb_store.lua b/lb_store.lua index dfe867f..4dc80d2 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -5,11 +5,14 @@ -- lb_common.lua local stat_t = lb_stat_t local lbComp = lb_comp +local score_t = lb_score_t local mapChecksum = lb_map_checksum +local mapnumFromExtended = lb_mapnum_from_extended ---------------------------- local LEADERBOARD_FILE = "leaderboard.txt" +local COLDSTORE_FILE = "leaderboard.coldstore.txt" -- ColdStore are records loaded from lua addons -- this table should never be modified outside of the AddColdStore function @@ -21,6 +24,67 @@ local LiveStore = {} -- parse score function local parseScore +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 + +local function isSameRecord(a, b, modeSep) + return a.name == b.name and (a.flags & modeSep) == (b.flags & modeSep) +end + +-- insert or replace the score in dest +local function insertOrReplace(dest, score, modeSep) + for i, record in ipairs(dest) do + if isSameRecord(record, score, modeSep) then + if lbComp(score, record) then + dest[i] = score + end + return + end + end + + table.insert(dest, score) +end + +local function dumpStoreToFile(filename, store) + local f = assert( + io.open(filename, "w"), + "Failed to open file for writing: "..filename + ) + + f:setvbuf("line") + + for mapid, checksums in pairs(store) do + for checksum, records in pairs(checksums) do + for _, record in ipairs(records) do + if not record.checksum or record.checksum == "" then + record.checksum = mapChecksum(record.map) or "" + 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, "\n" + ) + end + end + end + + f:close() +end + -- GLOBAL -- Returns a list of all maps with records local function MapList() @@ -115,76 +179,19 @@ local function GetMapRecords(map, checksum, modeSep) end rawset(_G, "lb_get_map_records", GetMapRecords) -local function insertOrReplaceRecord(map, score, modeSep) +-- GLOBAL +-- Save a record to the LiveStore and write to disk +-- SaveRecord will replace the record holders previous record +local function SaveRecord(score, map, modeSep) local checksum = mapChecksum(map) LiveStore[map] = $ or {} LiveStore[map][checksum] = $ or {} - - 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 - LiveStore[map][checksum][i] = score - return - end - end - - 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) + insertOrReplace(LiveStore[map][checksum], 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") - - 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" - ) - end - end + if isserver then + dumpStoreToFile(LEADERBOARD_FILE, LiveStore) end - - f:close() end rawset(_G, "lb_save_record", SaveRecord) @@ -194,20 +201,6 @@ end addHook("NetVars", netvars) -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, - ["stat"] = stat, - ["checksum"] = checksum - } -end - function parseScore(str) -- Leaderboard is stored in the following tab separated format -- mapnum, name, skin, color, time, splits, flags, stat @@ -237,7 +230,7 @@ function parseScore(str) end end - local checksum = t[9] + local checksum = t[9] or "" return score_t( tonumber(t[1]), -- Map @@ -253,19 +246,145 @@ function parseScore(str) end rawset(_G, "lb_parse_score", parseScore) --- Load the livestore -if isserver then +-- Read and parse a store file +local function loadStoreFile(filename) local f = assert( - io.open(LEADERBOARD_FILE, "r"), - "Failed to open file: "..LEADERBOARD_FILE + io.open(filename, "r"), + "Failed to open file for reading: "..filename ) + local store = {} + for l in f:lines() do local score = parseScore(l) - LiveStore[score.map] = $ or {} - LiveStore[score.map][score.checksum] = $ or {} - table.insert(LiveStore[score.map][score.checksum], score) + store[score.map] = $ or {} + store[score.map][score.checksum] = $ or {} + table.insert(store[score.map][score.checksum], score) end f:close() + + return store +end + +-- GLOBAL +-- Command for moving records from one map to another +local function moveRecords(from, to, modeSep) + local function moveRecordsInStore(store) + if not (store[from.id] and store[from.id][from.checksum]) then + return 0 + end + + local moveCount = #store[from.id][from.checksum] + + store[to.id] = $ or {} + store[to.id][to.checksum] = $ or {} + for i, score in ipairs(store[from.id][from.checksum]) do + score.map = to.id + score.checksum = to.checksum + insertOrReplace(store[to.id][to.checksum], score, modeSep) + end + + -- Destroy the original table + store[from.id][from.checksum] = nil + + return moveCount + end + + -- move livestore records and write to disk + local moveCount = moveRecordsInStore(LiveStore) + dumpStoreToFile(LEADERBOARD_FILE, LiveStore) + + -- move coldstore records + if isserver then + local ok, coldstore = pcall(loadStoreFile, COLDSTORE_FILE) + if ok and coldstore then + moveRecordsInStore(coldstore) + dumpStoreToFile(COLDSTORE_FILE, coldstore) + end + end + + return moveCount +end +rawset(_G, "lb_move_records", moveRecords) + +-- Helper function for those upgrading from 1.2 to 1.3 +COM_AddCommand("lb_write_checksums", function(player) + local count = 0 + local moved = {} + + -- Gather movable records (no checksum, map loaded) + for map, checksums in pairs(LiveStore) do + for checksum, records in pairs(checksums) do + if checksum == "" then + local sum = mapChecksum(map) + + if not sum then continue end + + moved[map] = {} + moved[map][sum] = {} + + for i, record in ipairs(records) do + record.checksum = sum + table.insert(moved[map][sum], record) + end + end + end + end + + -- Write moved to livestore + for map, checksums in pairs(moved) do + LiveStore[map] = $ or {} + for checksum, records in pairs(checksums) do + LiveStore[map][checksum] = $ or {} + for i, score in ipairs(records) do + table.insert(LiveStore[map][checksum], score) + end + count = $ + #records + end + LiveStore[map][""] = nil + end + + if isserver then + dumpStoreToFile(LEADERBOARD_FILE, LiveStore) + end + + CONS_Printf(player, string.format("Successful operation on %d records", count)) +end, COM_ADMIN) + +COM_AddCommand("lb_known_maps", function(player, map) + if not map then + CONS_Printf(player, "Usage: ") + CONS_Printf(player, "Print all known checksums under ") + return + end + + local mapnum = mapnumFromExtended(map) + if not mapnum then + CONS_Printf(player, string.format("invalid map '%s'", map)) + return + end + + local known = {} + + if LiveStore[mapnum] then + for checksum, records in pairs(LiveStore[mapnum]) do + known[checksum] = #records + end + end + if ColdStore[mapnum] then + for checksum, records in pairs(ColdStore[mapnum]) do + known[checksum] = $ or 0 + #records + end + end + + CONS_Printf(player, "Map Chck Records") + for checksum, count in pairs(known) do + CONS_Printf(player, string.format("%s %s %d", map, checksum, count)) + end +end) + +-- Load the livestore +if isserver then + LiveStore = loadStoreFile(LEADERBOARD_FILE) end diff --git a/leaderboard.lua b/leaderboard.lua index e08cb70..efdd173 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -1,6 +1,28 @@ -- Leaderboards written by Not -- Reusable +---------- Imported functions ------------- +-- lb_common.lua +local ticsToTime = lb_TicsToTime +local zoneAct = lb_ZoneAct +local stat_t = lb_stat_t +local lbComp = lb_comp +local mapChecksum = lb_map_checksum +local score_t = lb_score_t +local mapnumFromExtended = lb_mapnum_from_extended + +-- browser.lua +local InitBrowser = InitBrowser +local DrawBrowser = DrawBrowser +local BrowserController = BrowserController + +-- lb_store.lua +local GetMapRecords = lb_get_map_records +local SaveRecord = lb_save_record +local MapList = lb_map_list +local MoveRecords = lb_move_records +-------------------------------------------- + -- Holds the current maps records table including all modes local MapRecords = {} @@ -85,25 +107,6 @@ local scroll_to local allowJoin --- Imported functions -- - --- lb_common.lua -local ticsToTime = lb_TicsToTime -local zoneAct = lb_ZoneAct -local stat_t = lb_stat_t -local lbComp = lb_comp -local mapChecksum = lb_map_checksum - --- browser.lua -local InitBrowser = InitBrowser -local DrawBrowser = DrawBrowser -local BrowserController = BrowserController - --- lb_store.lua -local GetMapRecords = lb_get_map_records -local SaveRecord = lb_save_record -local MapList = lb_map_list ---------------- -- cvars local cv_teamchange @@ -182,19 +185,6 @@ local cv_spb_separate = CV_RegisterVar({ end }) -local function score_t(map, name, skin, color, time, splits, flags, stat) - return { - ["map"] = map, - ["name"] = name, - ["skin"] = skin, - ["color"] = color, - ["time"] = time, - ["splits"] = splits, - ["flags"] = flags, - ["stat"] = stat - } -end - local MSK_SPEED = 0xF0 local MSK_WEIGHT = 0xF @@ -343,35 +333,6 @@ local function findMap(player, ...) end COM_AddCommand("findmap", findMap) -local function mapnumFromExtended(map) - local p, q = map:upper():match("MAP(%w)(%w)$", 1) - if not (p and q) then - return nil - end - - local mapnum = 0 - local A = string.byte("A") - - if tonumber(p) != nil then - -- Non extended map numbers - if tonumber(q) == nil then - return nil - end - mapnum = tonumber(p) * 10 + tonumber(q) - else - --Extended map numbers - p = string.byte(p) - A - local qn = tonumber(q) - if qn == nil then - qn = string.byte(q) - A + 10 - end - - mapnum = 36 * p + qn + 100 - end - - return mapnum -end - local SPBModeSym = { [F_SPBEXP] = "X", [F_SPBBIG] = "B", @@ -683,6 +644,50 @@ local function findRival(player, ...) end COM_AddCommand("rival", findRival) +local function moveRecords(player, from_map, from_checksum, to_map, to_checksum) + if not(from_map and from_checksum and to_map) then + CONS_Printf(player, "Usage: lb_move_records []") + CONS_Printf( + player, + string.format( + "Summary: Move records from one map to another.\n".. + "If no is supplied then the checksum of the current loaded map %s is used.\n".. + "Hint: Use lb_known_maps to find checksums", + to_map or "" + ) + ) + return + end + + local from = { + ["id"] = mapnumFromExtended(from_map), + ["checksum"] = from_checksum + } + + local to = { + ["id"] = mapnumFromExtended(to_map), + } + to.checksum = to_checksum or mapChecksum(to.id) + + if not to.checksum then + CONS_Printf(player, string.format("error: '%s' is missing; provide to_checksum to continue", to.id)) + return + end + + CONS_Printf( + player, + string.format( + "%d records have been moved from %s %s to %s %s", + MoveRecords(from, to, ST_SEP), + from_map, from.checksum, + to_map, to.checksum + ) + ) + + CONS_Printf(player, "Please repack coldstore and restart the server for changes to take effect.") +end +COM_AddCommand("lb_move_records", moveRecords) + --DEBUGGING --local function printTable(tb) -- for mode, tbl in pairs(tb) do @@ -1192,12 +1197,12 @@ local function saveTime(player) end -- DEBUGGING ---local function saveLeaderboard(player, ...) --- TimeFinished = tonumber(... or player.realtime) --- splits = {1000, 2000, 3000} --- saveTime(player) ---end ---COM_AddCommand("save", saveLeaderboard) +local function saveLeaderboard(player, ...) + TimeFinished = tonumber(... or player.realtime) + splits = {1000, 2000, 3000} + saveTime(player) +end +COM_AddCommand("save", saveLeaderboard) local function regLap(player) if player.laps > prevLap and TimeFinished == 0 then From 172b9d3633bb801f1aed431dbd639897d418236f Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 01:49:32 +0100 Subject: [PATCH 14/21] lb_known_maps print current map by default --- lb_store.lua | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lb_store.lua b/lb_store.lua index 4dc80d2..488a297 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -353,16 +353,13 @@ COM_AddCommand("lb_write_checksums", function(player) end, COM_ADMIN) COM_AddCommand("lb_known_maps", function(player, map) - if not map then - CONS_Printf(player, "Usage: ") - CONS_Printf(player, "Print all known checksums under ") - return - end - - local mapnum = mapnumFromExtended(map) - if not mapnum then - CONS_Printf(player, string.format("invalid map '%s'", map)) - return + local mapnum = gamemap + if map then + mapnum = mapnumFromExtended(map) + if not mapnum then + CONS_Printf(player, string.format("invalid map '%s'", map)) + return + end end local known = {} @@ -380,7 +377,7 @@ COM_AddCommand("lb_known_maps", function(player, map) CONS_Printf(player, "Map Chck Records") for checksum, count in pairs(known) do - CONS_Printf(player, string.format("%s %s %d", map, checksum, count)) + CONS_Printf(player, string.format("%s %s %d", G_BuildMapName(mapnum), checksum, count)) end end) From 935d119e4d155f80a765a2b9a647838fb546706d Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 01:49:52 +0100 Subject: [PATCH 15/21] comment out debug command --- leaderboard.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/leaderboard.lua b/leaderboard.lua index efdd173..599991d 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -1197,12 +1197,12 @@ local function saveTime(player) end -- DEBUGGING -local function saveLeaderboard(player, ...) - TimeFinished = tonumber(... or player.realtime) - splits = {1000, 2000, 3000} - saveTime(player) -end -COM_AddCommand("save", saveLeaderboard) +--local function saveLeaderboard(player, ...) +-- TimeFinished = tonumber(... or player.realtime) +-- splits = {1000, 2000, 3000} +-- saveTime(player) +--end +--COM_AddCommand("save", saveLeaderboard) local function regLap(player) if player.laps > prevLap and TimeFinished == 0 then From 9d50705d9a2948b33dcaddcca723d76e618a1ddb Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 01:50:08 +0100 Subject: [PATCH 16/21] make lb_move_records admin only --- leaderboard.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaderboard.lua b/leaderboard.lua index 599991d..e2a3968 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -686,7 +686,7 @@ local function moveRecords(player, from_map, from_checksum, to_map, to_checksum) CONS_Printf(player, "Please repack coldstore and restart the server for changes to take effect.") end -COM_AddCommand("lb_move_records", moveRecords) +COM_AddCommand("lb_move_records", moveRecords, COM_ADMIN) --DEBUGGING --local function printTable(tb) From 36204b579f614b9d1c5444c5774384c4176366b5 Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 16:38:03 +0100 Subject: [PATCH 17/21] simplify coldstore.py usage --- tools/coldstore.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/coldstore.py b/tools/coldstore.py index fb1f57e..499bfbd 100755 --- a/tools/coldstore.py +++ b/tools/coldstore.py @@ -1,14 +1,21 @@ #!/usr/bin/env python3 import sys -from os import linesep +from os import linesep, path -if len(sys.argv) != 4 or not sys.argv[1] or not sys.argv[2] or not sys.argv[3]: - print("Usage: coldstore.py ") +if len(sys.argv) != 3 or not sys.argv[1] or not sys.argv[2]: + print("Usage: coldstore.py ") + print("\t\t\tthe game directory where wads and luafiles reside. Usually at '$HOME/.srb2kart'.") + print("\t\tthe output name for the records packed lua file. It will be saved within .") quit() -leaderboard_txt = sys.argv[1] -coldstore_txt = sys.argv[2] -records_lua = sys.argv[3] +if not sys.argv[2].endswith(".lua"): + print("{} must end with .lua".format(sys.argv[2])) + quit() + +game_dir = sys.argv[1] +leaderboard_txt = path.join(game_dir, "luafiles", "leaderboard.txt") +coldstore_txt = path.join(game_dir, "luafiles", "leaderboard.coldstore.txt") +records_lua = path.join(game_dir, sys.argv[2]) def ParseScore(score): # Map Name Skin Color Time Splits Flags Stat From d4bbf6294528b8a536d352de1ce1e4184a6ef90d Mon Sep 17 00:00:00 2001 From: Not Date: Sat, 19 Nov 2022 16:47:23 +0100 Subject: [PATCH 18/21] add download command --- lb_store.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lb_store.lua b/lb_store.lua index 488a297..9de7551 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -381,6 +381,18 @@ COM_AddCommand("lb_known_maps", function(player, map) end end) +COM_AddCommand("lb_download_live_records", function(player, filename) + if not filename then + CONS_Printf(player, "Usage: lb_download_live_records ") + return + end + + if filename:sub(#filename-3) != ".txt" then + filename = $..".txt" + end + dumpStoreToFile(filename, LiveStore) +end, COM_LOCAL) + -- Load the livestore if isserver then LiveStore = loadStoreFile(LEADERBOARD_FILE) From 3c5234f7b26d49bb2f522bb91c45215729dfc549 Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 25 Nov 2022 02:48:29 +0100 Subject: [PATCH 19/21] remove movecount, write LiveStore on servers only --- lb_store.lua | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lb_store.lua b/lb_store.lua index 9de7551..2a6953b 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -275,8 +275,6 @@ local function moveRecords(from, to, modeSep) return 0 end - local moveCount = #store[from.id][from.checksum] - store[to.id] = $ or {} store[to.id][to.checksum] = $ or {} for i, score in ipairs(store[from.id][from.checksum]) do @@ -287,24 +285,21 @@ local function moveRecords(from, to, modeSep) -- Destroy the original table store[from.id][from.checksum] = nil - - return moveCount end -- move livestore records and write to disk - local moveCount = moveRecordsInStore(LiveStore) - dumpStoreToFile(LEADERBOARD_FILE, LiveStore) + moveRecordsInStore(LiveStore) - -- move coldstore records if isserver then + dumpStoreToFile(LEADERBOARD_FILE, LiveStore) + + -- move coldstore records local ok, coldstore = pcall(loadStoreFile, COLDSTORE_FILE) if ok and coldstore then moveRecordsInStore(coldstore) dumpStoreToFile(COLDSTORE_FILE, coldstore) end end - - return moveCount end rawset(_G, "lb_move_records", moveRecords) From 00de269bb3b2e237b613735edb4d59175df6802d Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 25 Nov 2022 03:24:21 +0100 Subject: [PATCH 20/21] ensure lowercase on checksum parsing --- lb_store.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lb_store.lua b/lb_store.lua index 2a6953b..896ce65 100644 --- a/lb_store.lua +++ b/lb_store.lua @@ -241,7 +241,7 @@ function parseScore(str) splits, flags, stats, - checksum + checksum:lower() ) end rawset(_G, "lb_parse_score", parseScore) From beb19c81a3c5a3d367498d12f19f094b145926b3 Mon Sep 17 00:00:00 2001 From: Not Date: Fri, 25 Nov 2022 03:54:59 +0100 Subject: [PATCH 21/21] improve output and checking of lb_move_records command --- leaderboard.lua | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/leaderboard.lua b/leaderboard.lua index e2a3968..e5a060f 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -661,7 +661,7 @@ local function moveRecords(player, from_map, from_checksum, to_map, to_checksum) local from = { ["id"] = mapnumFromExtended(from_map), - ["checksum"] = from_checksum + ["checksum"] = from_checksum:lower() } local to = { @@ -670,15 +670,29 @@ local function moveRecords(player, from_map, from_checksum, to_map, to_checksum) to.checksum = to_checksum or mapChecksum(to.id) if not to.checksum then - CONS_Printf(player, string.format("error: '%s' is missing; provide to_checksum to continue", to.id)) + CONS_Printf(player, string.format("error: %s is not loaded; provide to_checksum to continue", to_map:upper())) return end + if #to.checksum != 4 or to.checksum:match("[^a-f0-9]") then + CONS_Printf(player, string.format("error: %s is an invalid checksum; checksums are of length 4 and can contain only 0-9a-f", to.checksum)) + return + end + + to.checksum = $:lower() + + local mapRecords = GetMapRecords(from.id, from.checksum, F_SPBATK | F_SPBBIG | F_SPBEXP) + local recordCount = 0 + for mode, records in pairs(mapRecords) do + recordCount = $ + #records + end + + MoveRecords(from, to, ST_SEP) CONS_Printf( player, string.format( - "%d records have been moved from %s %s to %s %s", - MoveRecords(from, to, ST_SEP), + "%d records have been moved from\x82 %s %s\x80 to\x88 %s %s", + recordCount, from_map, from.checksum, to_map, to.checksum )