-- Leaderboards written by Not local FILENAME = "leaderboard.txt" local lb = {} local timeFinished = 0 local disable = true local prevLap = 0 local splits = {} local PATCH = { ["FACERANK"]={} } -- Read the leaderboard local f = io.open(FILENAME, "r") if f then for l in f:lines() do 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]] = {} 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}) end f:close() else print("Failed to open file: ", FILENAME) end local function initLeaderboard() local ingame = 0 for p in players.iterate do if p.valid and not p.spectator then ingame = ingame + 1 end end disable = ingame > 1 if disable then --print("To many players in game, leaderboard has been disabled") return end end addHook("PlayerSpawn", initLeaderboard) local function retry(player, ...) if disable or player.spectator then print("How dare you") return end G_SetCustomExitVars(gamemap, 2) G_ExitLevel() end COM_AddCommand("retry", retry) --DEBUGGING --local function printTable(tb) -- for k, v in pairs(tb) do -- 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], ",")) -- end -- end -- end --end addHook("MapLoad", function() timeFinished = 0 splits = {} prevLap = 0 end ) local function ticsToTime(tics) return string.format( "%d:%02d:%02d", G_TicsToMinutes(tics), G_TicsToSeconds(tics), G_TicsToCentiseconds(tics) ) end local bodium = {V_YELLOWMAP, V_GRAYMAP, V_BROWNMAP, 0} local splitColor = {[true]=V_SKYMAP, [false]=V_REDMAP} 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 local m = lb[tostring(gamemap)] if m == nil then return end for i = 1, #m do local score = m[i] local name = score[1] local skin = skins[score[2]] if skin == nil then skin = skins["sonic"] end local skinPatch = PATCH["FACERANK"][skin.name] -- | 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])) if player.name == name then v.draw(4, h, PATCH["CHILI"][(leveltime / 4) % 8], V_HUDTRANS) end -- Shorten long names local stralign = "left" local MAXWIDTH = 70 local px = 6 local py = 0 if v.stringWidth(name) > MAXWIDTH then stralign = "thin" py = -1 if v.stringWidth(name, 0, "thin") > MAXWIDTH then stralign = "small" py = 2 if v.stringWidth(name, 0, "small") > MAXWIDTH then name = name:sub(0, 15) + "..." end end end 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] 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)]) end end end hud.add(drawScoreboard, "game") local function lbComp(a, b) return a[4] < b[4] end local function saveTime(player) -- Check if you beat your previous best local m = lb[tostring(gamemap)] if m == nil then m = {} end for i = 1, #m do if m[i][1] == player.name then if m[i][4] > timeFinished then table.remove(m, i) S_StartSound(nil, 130) break else -- You suck lol S_StartSound(nil, 201) return end end end print("Saving score") table.insert(m, {player.name, player.mo.skin, player.mo.color, timeFinished, splits}) table.sort(m, lbComp) while #m > 5 do table.remove(m) end lb[tostring(gamemap)] = m local f = assert(io.open(FILENAME, "w")) if f == nil then print("Failed to open file for writing: " + FILENAME) return end 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") end end f:close() end -- DEBUGGING --local function saveLeaderboard(player, ...) -- timeFinished = 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 prevLap = player.laps table.insert(splits, player.realtime) showSplit = 5 * TICRATE end end local function captureFinish() if disable then return end if showSplit > 0 then showSplit = showSplit - 1 end for p in players.iterate do if p.laps >= mapheaderinfo[gamemap].numlaps and timeFinished == 0 then timeFinished = p.realtime saveTime(p) end regLap(p) end end addHook("ThinkFrame", captureFinish) local function netvars(net) lb = net($) end addHook("NetVars", netvars)