diff --git a/leaderboard.lua b/leaderboard.lua index 490c957..6f5ed5e 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -6,37 +6,92 @@ local timeFinished = 0 local disable = true local prevLap = 0 local splits = {} -local PATCH = { - ["FACERANK"]={} -} +local PATCH = nil + +local Flags = 0 + +-- SPB flags with the least significance first +local F_SPBATK = 0x1 +local F_SPBJUS = 0x2 +local F_SPBBIG = 0x4 +local F_SPBEXP = 0x8 + +local clearcheats = false + +local startTime = 6 * TICRATE + (3 * TICRATE / 4) + +-- patch caching function +local cachePatches + +local function lbID(map, flags) + local id = tostring(map) + if flags & F_SPBATK then + id = id + "S" + end + + return id +end + +local function score_t(map, name, skin, color, time, splits, flags) + return { + ["map"] = map, + ["name"] = name, + ["skin"] = skin, + ["color"] = color, + ["time"] = time, + ["splits"] = splits, + ["flags"] = flags + } +end -- Read the leaderboard local f = io.open(FILENAME, "r") if f then for l in f:lines() do + -- Leaderboard is stored in the following tab separated format + -- name, skin, color, time, splits, flags local t = {} for word in (l+"\t"):gmatch("(.-)\t") do table.insert(t, word) end - --local level, name, skin, color, time, splts = l:match("(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)") - if lb[t[1]] == nil then - lb[t[1]] = {} + local flags = 0 + if t[7] != nil then + flags = tonumber(t[7]) end + + local lbt = lb[lbID(t[1], flags)] + if lbt == nil then + lbt = {} + end + local spl = {} if t[6] != nil then for str in t[6]:gmatch("([^ ]+)") do table.insert(spl, tonumber(str)) end end - table.insert(lb[t[1]], {t[2], t[3], t[4], tonumber(t[5]), spl}) + + table.insert( + lbt, + score_t( + t[1], + t[2], + t[3], + t[4], + tonumber(t[5]), + spl, + flags + ) + ) + lb[lbID(t[1], flags)] = lbt end f:close() else print("Failed to open file: ", FILENAME) end -local function initLeaderboard() +local function initLeaderboard(player) local ingame = 0 for p in players.iterate do if p.valid and not p.spectator then @@ -55,15 +110,28 @@ addHook("PlayerSpawn", initLeaderboard) local function retry(player, ...) if disable or player.spectator then - print("How dare you") + CONS_Printf(player, "How dare you") return end - G_SetCustomExitVars(gamemap, 2) - G_ExitLevel() + COM_BufInsertText(server, "map " + G_BuildMapName(gamemap)) end COM_AddCommand("retry", retry) +local function exitlevel(player, ...) + if disable or player.spectator then + CONS_Printf(player, "How dare you") + return + end + G_ExitLevel() +end +COM_AddCommand("exit", exitlevel) + +COM_AddCommand("spba_clearcheats", function(player) + clearcheats = true + CONS_Printf(player, "SPB Attack cheats will be cleared on next round") + end +) --DEBUGGING --local function printTable(tb) @@ -71,7 +139,16 @@ COM_AddCommand("retry", retry) -- for i = 1, #v do -- print("TABLE: " + k, tb[k]) -- if v[i] != nil then --- print(v[i][1], v[i][2], v[i][3], v[i][4], table.concat(v[i][5], ",")) +-- print( +-- v[i]["name"], +-- v[i]["skin"], +-- v[i]["color"], +-- v[i]["time"], +-- table.concat(v[i]["splits"]), +-- v[i]["flags"], +-- "," +-- ) +-- -- end -- end -- end @@ -81,6 +158,7 @@ addHook("MapLoad", function() timeFinished = 0 splits = {} prevLap = 0 + --printTable(lb) end ) @@ -93,6 +171,19 @@ local function ticsToTime(tics) ) end +-- Item patches have the amazing property of being displaced 12x 13y pixels +local iXoffset = 13 * FRACUNIT +local iYoffset = 12 * FRACUNIT +local function drawitem(v, x, y, scale, itempatch, vflags) + v.drawScaled( + x * FRACUNIT - FixedMul(iXoffset, scale), + y * FRACUNIT - FixedMul(iYoffset, scale), + scale, + itempatch, + vflags + ) +end + local bodium = {V_YELLOWMAP, V_GRAYMAP, V_BROWNMAP, 0} local splitColor = {[true]=V_SKYMAP, [false]=V_REDMAP} local splitSymbol = {[true]="-", [false]="+"} @@ -100,45 +191,74 @@ local splitSymbol = {[true]="-", [false]="+"} local showSplit = 0 local function drawScoreboard(v, player) if disable then return end - if player != displayplayers[0] then return end - -- PATCH CACHE - if not PATCH["CHILI"] then - PATCH["CHILI"] = {} - for i = 1, 8 do - PATCH["CHILI"][i-1] = v.cachePatch("K_CHILI" + i) - end - end - - for skin in skins.iterate do - if PATCH["FACERANK"][skin.name] then - continue - end - PATCH["FACERANK"][skin.name] = v.cachePatch(skin.facerank) - end + cachePatches(v) - local m = lb[tostring(gamemap)] + local m = lb[lbID(gamemap, Flags)] if m == nil then return end - for i = 1, #m do - local score = m[i] - local name = score[1] - local skin = skins[score[2]] + for i, score in ipairs(m) do + local name = score["name"] + local skin = skins[score["skin"]] if skin == nil then skin = skins["sonic"] end local skinPatch = PATCH["FACERANK"][skin.name] - -- | OFFSET | + | PADDING | * |INDEX| + -- | OFFSET | + | PADDING | * |INDEX| local h = ((200 / 4) + 4) + (skinPatch.height + 4) * (i - 1) - v.draw(4, h, skinPatch, V_HUDTRANS, v.getColormap("sonic", score[3])) + + v.draw(4, h, skinPatch, V_HUDTRANS, v.getColormap("sonic", score["color"])) if player.name == name then v.draw(4, h, PATCH["CHILI"][(leveltime / 4) % 8], V_HUDTRANS) end + -- SPB + if score["flags"] & F_SPBATK then + local scale = FRACUNIT / 4 + drawitem( + v, + 4 - 2, + h - 2, + scale, + PATCH["SPB"], + V_HUDTRANS + ) + if score["flags"] & F_SPBEXP then + drawitem( + v, + skinPatch.width, + h - 2, + scale, + PATCH["INV"][(leveltime / 4) % 6], + V_HUDTRANS + ) + end + if score["flags"] & F_SPBBIG then + drawitem( + v, + 4 - 2, + h + skinPatch.height - 4, + scale, + PATCH["BIG"], + V_HUDTRANS + ) + end + if score["flags"] & F_SPBJUS then + drawitem( + v, + skinPatch.width, + h + skinPatch.height - 4, + scale, + PATCH["HYUD"], + V_HUDTRANS + ) + end + end + -- Shorten long names local stralign = "left" local MAXWIDTH = 70 @@ -159,30 +279,69 @@ local function drawScoreboard(v, player) v.drawString(px + skinPatch.width, h + py, name, V_HUDTRANSHALF | V_ALLOWLOWERCASE, stralign) -- Draw splits - if showSplit > 0 and score[5][prevLap] != nil then - local split = splits[prevLap] - score[5][prevLap] + if showSplit > 0 and score["splits"][prevLap] != nil then + local split = splits[prevLap] - score["splits"][prevLap] v.drawString(px + skinPatch.width, h + 8, splitSymbol[split < 0] + ticsToTime(abs(split)), V_HUDTRANSHALF | splitColor[split < 0]) else - v.drawString(px + skinPatch.width, h + 8, ticsToTime(score[4]), V_HUDTRANSHALF | bodium[min(i, 4)]) + v.drawString(px + skinPatch.width, h + 8, ticsToTime(score["time"]), V_HUDTRANSHALF | bodium[min(i, 4)]) end end end hud.add(drawScoreboard, "game") +function cachePatches(v) + if PATCH == nil then + PATCH = {} + + PATCH["CHILI"] = {} + for i = 1, 8 do + PATCH["CHILI"][i-1] = v.cachePatch("K_CHILI" + i) + end + + PATCH["FACERANK"] = {} + for skin in skins.iterate do + PATCH["FACERANK"][skin.name] = v.cachePatch(skin.facerank) + end + + PATCH["SPB"] = v.cachePatch("K_ISSPB") + PATCH["INV"] = {} + for i = 1, 6 do + PATCH["INV"][i - 1] = v.cachePatch("K_ISINV" + i) + end + PATCH["BIG"] = v.cachePatch("K_ISGROW") + PATCH["HYUD"] = v.cachePatch("K_ISHYUD") + end +end + +-- True if a is better than b local function lbComp(a, b) - return a[4] < b[4] + -- 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 saveTime(player) - -- Check if you beat your previous best - local m = lb[tostring(gamemap)] + local m = lb[lbID(gamemap, Flags)] if m == nil then m = {} end + local newscore = score_t( + gamemap, + player.name, + player.mo.skin, + player.mo.color, + timeFinished, + splits, + Flags + ) + -- Check if you beat your previous best for i = 1, #m do - if m[i][1] == player.name then - if m[i][4] > timeFinished then + if m[i]["name"] == player.name then + if lbComp(newscore, m[i]) then table.remove(m, i) S_StartSound(nil, 130) break @@ -195,14 +354,17 @@ local function saveTime(player) end print("Saving score") - table.insert(m, {player.name, player.mo.skin, player.mo.color, timeFinished, splits}) + table.insert( + m, + newscore + ) table.sort(m, lbComp) while #m > 5 do table.remove(m) end - lb[tostring(gamemap)] = m + lb[lbID(gamemap, Flags)] = m local f = assert(io.open(FILENAME, "w")) if f == nil then @@ -213,7 +375,15 @@ local function saveTime(player) for k, v in pairs(lb) do for i = 1, #v do local s = v[i] - f:write(k, "\t", s[1], "\t", s[2], "\t", s[3], "\t", s[4], "\t", table.concat(s[5], " "), "\n") + f:write( + s["map"], "\t", + s["name"], "\t", + s["skin"], "\t", + s["color"], "\t", + s["time"], "\t", + table.concat(s["splits"], " "), "\t", + s["flags"], "\n" + ) end end @@ -236,7 +406,7 @@ local function regLap(player) end end -local function captureFinish() +local function think() if disable then return end @@ -244,6 +414,32 @@ local function captureFinish() showSplit = showSplit - 1 end + if leveltime < startTime then + Flags = $ & !(F_SPBATK | F_SPBEXP | F_SPBBIG | F_SPBJUS) + if server.SPBArunning then + Flags = $ | F_SPBATK + if server.SPBAexpert then + Flags = $ | F_SPBEXP + end + if clearcheats then + clearcheats = false + for p in players.iterate do + p.SPBAKARTBIG = false + p.SPBAjustice = false + p.SPBAshutup = false + end + end + for p in players.iterate do + if p.SPBAKARTBIG then + Flags = $ | F_SPBBIG + end + if p.SPBAjustice then + Flags = $ | F_SPBJUS + end + end + end + end + for p in players.iterate do if p.laps >= mapheaderinfo[gamemap].numlaps and timeFinished == 0 then timeFinished = p.realtime @@ -252,7 +448,7 @@ local function captureFinish() regLap(p) end end -addHook("ThinkFrame", captureFinish) +addHook("ThinkFrame", think) local function netvars(net) lb = net($)