Compare commits

...

7 Commits

Author SHA1 Message Date
Not
bc002667c8 v1.2.7 2022-03-28 13:11:21 +02:00
Not
b0d5b5abeb v1.2.6 2022-03-28 13:11:00 +02:00
Not
18a81fab6d v1.2.5 2022-03-28 13:10:43 +02:00
Not
9a1fb0677a v1.2.4 2022-03-28 13:10:13 +02:00
Not
321ff7737c v1.2.3 2022-03-28 13:09:52 +02:00
Not
190f94b2b9 v1.2.2 2022-03-28 13:09:23 +02:00
Not
8f8dd18c6b v1.2.1 2022-03-28 13:08:08 +02:00

View File

@ -6,64 +6,183 @@ local timeFinished = 0
local disable = true local disable = true
local prevLap = 0 local prevLap = 0
local splits = {} local splits = {}
local PATCH = { local PATCH = nil
["FACERANK"]={} local help = true
}
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 -- Read the leaderboard
local f = io.open(FILENAME, "r") local f = io.open(FILENAME, "r")
if f then if f then
for l in f:lines() do for l in f:lines() do
-- Leaderboard is stored in the following tab separated format
-- name, skin, color, time, splits, flags
local t = {} local t = {}
for word in (l+"\t"):gmatch("(.-)\t") do for word in (l+"\t"):gmatch("(.-)\t") do
table.insert(t, word) table.insert(t, word)
end end
--local level, name, skin, color, time, splts = l:match("(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)")
if lb[t[1]] == nil then local flags = 0
lb[t[1]] = {} if t[7] != nil then
flags = tonumber(t[7])
end end
local lbt = lb[lbID(t[1], flags)]
if lbt == nil then
lbt = {}
end
local spl = {} local spl = {}
if t[6] != nil then if t[6] != nil then
for str in t[6]:gmatch("([^ ]+)") do for str in t[6]:gmatch("([^ ]+)") do
table.insert(spl, tonumber(str)) table.insert(spl, tonumber(str))
end end
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 end
f:close() f:close()
else else
print("Failed to open file: ", FILENAME) print("Failed to open file: ", FILENAME)
end end
local function initLeaderboard() local function ingame()
local ingame = 0 local n = 0
for p in players.iterate do for p in players.iterate do
if p.valid and not p.spectator then if p.valid and not p.spectator then
ingame = ingame + 1 n = $ + 1
end end
end end
return n
end
disable = ingame > 1 local function initLeaderboard(player)
disable = ingame() > 1
if disable then
--print("To many players in game, leaderboard has been disabled")
return
end
end end
addHook("PlayerSpawn", initLeaderboard) addHook("PlayerSpawn", initLeaderboard)
local function retry(player, ...) local function doyoudare(player)
if disable or player.spectator then if disable or player.spectator then
print("How dare you") CONS_Printf(player, "How dare you")
return return false
end end
return true
end
G_SetCustomExitVars(gamemap, 2) local function retry(player, ...)
G_ExitLevel() if doyoudare(player) then
COM_BufInsertText(server, "map " + G_BuildMapName(gamemap))
end
end end
COM_AddCommand("retry", retry) COM_AddCommand("retry", retry)
local function exitlevel(player, ...)
if doyoudare(player) then
G_ExitLevel()
end
end
COM_AddCommand("exit", exitlevel)
local function mapNotExists(player, map)
CONS_Printf(player, string.format("Map doesn't exist: %s", map:upper()))
end
local ALPH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local function changelevel(player, ...)
if not doyoudare(player) then
return
end
local map = ...
if map == nil then
CONS_Printf(player, "Usage: changelevel MAPXX")
return
end
local p, q = map:upper():match("MAP(%w)(%w)$", 1)
if not (p and q) then
CONS_Printf(player, string.format("Invalid map name: %s", map))
return
end
local mapnum = 0
if tonumber(p) != nil then
-- Non extended map numbers
if tonumber(q) == nil then
mapNotExists(player, map)
return
end
mapnum = tonumber(p) * 10 + tonumber(q)
else
--Extended map numbers
p = ALPH:find(p) - 1
q = (tonumber(q) or ALPH:find(q) + 9)
mapnum = 36 * p + q + 100
end
if mapheaderinfo[mapnum] == nil then
mapNotExists(player, map)
return
end
COM_BufInsertText(server, "map " + (G_BuildMapName(mapnum)))
end
COM_AddCommand("changelevel", changelevel)
local function clearcheats(player)
if not player.spectator then
clearcheats = true
CONS_Printf(player, "SPB Attack cheats will be cleared on next round")
end
end
COM_AddCommand("spba_clearcheats", clearcheats)
--DEBUGGING --DEBUGGING
--local function printTable(tb) --local function printTable(tb)
@ -71,7 +190,16 @@ COM_AddCommand("retry", retry)
-- for i = 1, #v do -- for i = 1, #v do
-- print("TABLE: " + k, tb[k]) -- print("TABLE: " + k, tb[k])
-- if v[i] != nil then -- 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 -- end
-- end -- end
@ -81,6 +209,7 @@ addHook("MapLoad", function()
timeFinished = 0 timeFinished = 0
splits = {} splits = {}
prevLap = 0 prevLap = 0
--printTable(lb)
end end
) )
@ -93,6 +222,30 @@ local function ticsToTime(tics)
) )
end 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 function marquee(text, maxwidth)
if #text <= maxwidth then
return text
end
local shift = 16
local pos = ((leveltime / 16) % (#text - maxwidth + shift * 2)) + 1 - shift
pos = min(max(pos, 1), #text - maxwidth + 1)
return text:sub(pos, pos + maxwidth - 1)
end
local bodium = {V_YELLOWMAP, V_GRAYMAP, V_BROWNMAP, 0} local bodium = {V_YELLOWMAP, V_GRAYMAP, V_BROWNMAP, 0}
local splitColor = {[true]=V_SKYMAP, [false]=V_REDMAP} local splitColor = {[true]=V_SKYMAP, [false]=V_REDMAP}
local splitSymbol = {[true]="-", [false]="+"} local splitSymbol = {[true]="-", [false]="+"}
@ -100,33 +253,18 @@ local splitSymbol = {[true]="-", [false]="+"}
local showSplit = 0 local showSplit = 0
local function drawScoreboard(v, player) local function drawScoreboard(v, player)
if disable then return end if disable then return end
if player != displayplayers[0] then return end if player != displayplayers[0] then return end
-- PATCH CACHE cachePatches(v)
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 local m = lb[lbID(gamemap, Flags)]
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 if m == nil then
return return
end end
for i = 1, #m do for i, score in ipairs(m) do
local score = m[i] local name = score["name"]
local name = score[1] local skin = skins[score["skin"]]
local skin = skins[score[2]]
if skin == nil then if skin == nil then
skin = skins["sonic"] skin = skins["sonic"]
end end
@ -134,11 +272,55 @@ local function drawScoreboard(v, player)
-- | OFFSET | + | PADDING | * |INDEX| -- | OFFSET | + | PADDING | * |INDEX|
local h = ((200 / 4) + 4) + (skinPatch.height + 4) * (i - 1) 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 if player.name == name then
v.draw(4, h, PATCH["CHILI"][(leveltime / 4) % 8], V_HUDTRANS) v.draw(4, h, PATCH["CHILI"][(leveltime / 4) % 8], V_HUDTRANS)
end 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 -- Shorten long names
local stralign = "left" local stralign = "left"
local MAXWIDTH = 70 local MAXWIDTH = 70
@ -151,7 +333,7 @@ local function drawScoreboard(v, player)
stralign = "small" stralign = "small"
py = 2 py = 2
if v.stringWidth(name, 0, "small") > MAXWIDTH then if v.stringWidth(name, 0, "small") > MAXWIDTH then
name = name:sub(0, 15) + "..." name = marquee(name, 15)
end end
end end
end end
@ -159,30 +341,69 @@ local function drawScoreboard(v, player)
v.drawString(px + skinPatch.width, h + py, name, V_HUDTRANSHALF | V_ALLOWLOWERCASE, stralign) v.drawString(px + skinPatch.width, h + py, name, V_HUDTRANSHALF | V_ALLOWLOWERCASE, stralign)
-- Draw splits -- Draw splits
if showSplit > 0 and score[5][prevLap] != nil then if showSplit > 0 and score["splits"][prevLap] != nil then
local split = splits[prevLap] - score[5][prevLap] 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]) v.drawString(px + skinPatch.width, h + 8, splitSymbol[split < 0] + ticsToTime(abs(split)), V_HUDTRANSHALF | splitColor[split < 0])
else 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 end
end end
hud.add(drawScoreboard, "game") 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) 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 end
local function saveTime(player) local function saveTime(player)
-- Check if you beat your previous best local m = lb[lbID(gamemap, Flags)]
local m = lb[tostring(gamemap)]
if m == nil then if m == nil then
m = {} m = {}
end end
local newscore = score_t(
gamemap,
player.name,
player.mo.skin,
player.skincolor,
timeFinished,
splits,
Flags
)
-- Check if you beat your previous best
for i = 1, #m do for i = 1, #m do
if m[i][1] == player.name then if m[i]["name"] == player.name then
if m[i][4] > timeFinished then if lbComp(newscore, m[i]) then
table.remove(m, i) table.remove(m, i)
S_StartSound(nil, 130) S_StartSound(nil, 130)
break break
@ -195,14 +416,17 @@ local function saveTime(player)
end end
print("Saving score") print("Saving score")
table.insert(m, {player.name, player.mo.skin, player.mo.color, timeFinished, splits}) table.insert(
m,
newscore
)
table.sort(m, lbComp) table.sort(m, lbComp)
while #m > 5 do while #m > 5 do
table.remove(m) table.remove(m)
end end
lb[tostring(gamemap)] = m lb[lbID(gamemap, Flags)] = m
local f = assert(io.open(FILENAME, "w")) local f = assert(io.open(FILENAME, "w"))
if f == nil then if f == nil then
@ -213,7 +437,15 @@ local function saveTime(player)
for k, v in pairs(lb) do for k, v in pairs(lb) do
for i = 1, #v do for i = 1, #v do
local s = v[i] 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
end end
@ -236,14 +468,55 @@ local function regLap(player)
end end
end end
local function captureFinish() local function think()
if disable then if disable then
help = true
return return
end end
if showSplit > 0 then if showSplit > 0 then
showSplit = showSplit - 1 showSplit = showSplit - 1
end end
if leveltime < startTime then
-- Help message
if leveltime == startTime - TICRATE * 3 then
if ingame() == 1 then
if help then
help = false
chatprint("\x89Leaderboard Commands:\nretry exit findmap changelevel spba_clearcheats", true)
end
else
help = true
end
end
Flags = $ & !(F_SPBATK | F_SPBEXP | F_SPBBIG | F_SPBJUS)
if leveltime > startTime - (3 * TICRATE) / 2 and 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 not p.spectator then
if p.SPBAKARTBIG then
Flags = $ | F_SPBBIG
end
if p.SPBAjustice then
Flags = $ | F_SPBJUS
end
end
end
end
end
for p in players.iterate do for p in players.iterate do
if p.laps >= mapheaderinfo[gamemap].numlaps and timeFinished == 0 then if p.laps >= mapheaderinfo[gamemap].numlaps and timeFinished == 0 then
timeFinished = p.realtime timeFinished = p.realtime
@ -252,7 +525,7 @@ local function captureFinish()
regLap(p) regLap(p)
end end
end end
addHook("ThinkFrame", captureFinish) addHook("ThinkFrame", think)
local function netvars(net) local function netvars(net)
lb = net($) lb = net($)