srb2k-leaderboard/browser.lua

499 lines
9.9 KiB
Lua

local MapRecords
local maps
local mapIndex = 1
local scrollPos = 1
local modes = nil
local mode = 1
local prefMode = nil
local ModeSep
---- Imported functions ----
-- 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
-----------------------------
local cv_kartencore
local function mapIndexOffset(n)
return (mapIndex + n + #maps - 1) % #maps + 1
end
local function getMap(offset)
return maps[mapIndexOffset(offset or 0)]
end
local function updateModes()
-- set available modes for this map
modes = {}
for mode, _ in pairs(MapRecords) do
table.insert(modes, mode)
end
table.sort(modes)
mode = 1
-- select pref mode
for i, m in ipairs(modes) do
if m == prefMode then
mode = i
break
end
end
end
local function updateMapIndex(n)
mapIndex = mapIndexOffset(n)
scrollPos = 1
MapRecords = GetMapRecords(maps[mapIndex], mapChecksum(maps[mapIndex]), ModeSep)
updateModes()
end
local scalar = 2
local hlfScrnWdth = 320 / 2
local mappY = 26
local ttlY = mappY + FixedMul(30, FRACUNIT / scalar)
local scoresY = ttlY + 16
local sin = sin
local function drawMapPatch(v, offset)
local scale = FRACUNIT / (abs(offset) + scalar)
local mapName = G_BuildMapName(getMap(offset))
local patchName = mapName.."P"
local mapp = v.patchExists(patchName) and v.cachePatch(patchName) or v.cachePatch("BLANKLVL")
local scaledWidth = FixedMul(mapp.width, scale)
local scaledHeight = FixedMul(mapp.height, scale)
v.drawScaled(
(hlfScrnWdth + offset * scaledWidth - scaledWidth / 2) * FRACUNIT,
(mappY - scaledHeight / 2) * FRACUNIT,
scale,
mapp
)
end
local function drawEncore(v)
if not cv_kartencore then
cv_kartencore = CV_FindVar("kartencore")
end
if not cv_kartencore.value then
return
end
local rubyp = v.cachePatch("RUBYICON")
local bob = sin(leveltime * ANG10) * 2
v.drawScaled(
hlfScrnWdth * FRACUNIT,
mappY * FRACUNIT + bob,
FRACUNIT,
rubyp
)
end
local colors = {
[0] = 0,
[1] = 215
}
local function drawMapBorder(v)
local mapWidth = FixedMul(160, FRACUNIT / scalar)
local mapHeight = FixedMul(100, FRACUNIT / scalar)
v.drawFill(
hlfScrnWdth - mapWidth / 2 - 1,
mappY - mapHeight / 2 -1,
mapWidth + 2,
mapHeight + 2,
colors[leveltime / 4 % 2]
)
end
local function drawMapStrings(v)
local map = mapheaderinfo[getMap()]
local titleWidth = v.stringWidth(map.lvlttl)
-- title
v.drawString(
hlfScrnWdth,
ttlY,
map.lvlttl,
V_SKYMAP,
"center"
)
-- zone/act
local zone = ZoneAct(map)
local zoneWidth = v.stringWidth(zone)
v.drawString(
hlfScrnWdth + titleWidth / 2,
ttlY + 8,
zone,
V_SKYMAP,
"right"
)
-- subtitle
v.drawString(
hlfScrnWdth + titleWidth / 2 - zoneWidth,
ttlY + 8,
map.subttl,
V_MAGENTAMAP,
"small-right"
)
-- hell
if map.menuflags & LF2_HIDEINMENU then
v.drawString(
300,
ttlY + 16,
"HELL",
V_REDMAP,
"right"
)
end
end
local F_SPBATK = 0x1
local F_SPBJUS = 0x2
local F_SPBBIG = 0x4
local F_SPBEXP = 0x8
local F_ENCORE = 0x80
local function drawGamemode(v)
local m = modes[mode] or 0
local modeX = 20
local modeY = scoresY
local scale = FRACUNIT / 2
if m == 0 then
local clockp = v.cachePatch("K_LAPE02")
v.drawScaled(
modeX * FRACUNIT,
modeY * FRACUNIT,
scale,
clockp
)
v.drawString(
modeX,
modeY,
"Time Attack!"
)
elseif m & F_SPBATK then
local scaledHalf = FixedMul(50 * FRACUNIT, scale) / 2
local xoff = 0
if m & F_SPBBIG then
xoff = $ + scaledHalf
end
if m & F_SPBEXP then
xoff = $ + scaledHalf
end
if m & F_SPBBIG then
local growp = v.cachePatch("K_ITGROW")
v.drawScaled(
modeX * FRACUNIT - scaledHalf + xoff,
modeY * FRACUNIT - scaledHalf,
scale,
growp
)
xoff = $ - scaledHalf
end
if m & F_SPBEXP then
local invp = v.cachePatch("K_ITINV"..(leveltime / 3 % 7 + 1))
v.drawScaled(
modeX * FRACUNIT - scaledHalf + xoff,
modeY * FRACUNIT - scaledHalf,
scale,
invp
)
end
local spbp = v.cachePatch("K_ITSPB")
v.drawScaled(
modeX * FRACUNIT - scaledHalf,
modeY * FRACUNIT - scaledHalf,
scale,
spbp
)
v.drawString(
modeX,
modeY,
"SPB Attack!"
)
end
end
local function drawFlags(v, x, y, flags)
local nx = x * FRACUNIT
local ny = y * FRACUNIT + 2 * FRACUNIT
local margin = 4 * FRACUNIT
if flags & F_ENCORE then
local encp = v.cachePatch("RUBYICON")
v.drawScaled(
nx,
ny + 2 * FRACUNIT,
FRACUNIT / 5,
encp
)
nx = $ + margin
end
if flags & F_SPBATK then
local scale = FRACUNIT / 3
local shift = 6 * FRACUNIT
nx = $ - shift
ny = $ - shift
if flags & F_SPBJUS then
local hyup = v.cachePatch("K_ISHYUD")
v.drawScaled(nx, ny, scale, hyup)
nx = $ + margin
end
if flags & F_SPBBIG then
local growp = v.cachePatch("K_ISGROW")
v.drawScaled(nx - FRACUNIT / 2, ny, scale, growp)
nx = $ + margin
end
if flags & F_SPBEXP then
local invp = v.cachePatch("K_ISINV"..(leveltime / 3 % 6 + 1))
v.drawScaled(nx, ny, scale, invp)
nx = $ + margin
end
end
end
local MSK_SPEED = 0xF0
local MSK_WEIGHT = 0xF
local function drawStats(v, x, y, skin, stats)
local s = skins[skin]
if stats
and not (s
and s.kartspeed == (stats & MSK_SPEED) >> 4
and s.kartweight == stats & MSK_WEIGHT
) then
v.drawString(x-2, y-2, (stats & MSK_SPEED) >> 4, V_ALLOWLOWERCASE, "thin")
v.drawString(x + 13, y + 9, stats & MSK_WEIGHT, V_ALLOWLOWERCASE, "thin")
end
end
-- draw in columns
-- pos, facerank, name, time, flags
-- ______________________________________________
-- | 3|[O]|InsertNameHere | 01:02:03 | EXB |
-- ----------------------------------------------
-- defined are widths of each column, x value is calculated below
local column = {
[1] = 18, -- facerank, pos, drawNum is right aligned
[2] = 170, -- name
[3] = 60, -- time
[4] = 0 -- flags
}
do
local w = 32 -- starting offset
local t
for i = 1, #column do
t = column[i]
column[i] = w
w = $ + t
end
end
local colorFlags = {
[0] = V_SKYMAP,
[1] = 0
}
local function drawScore(v, i, pos, score, highlight)
local y = scoresY + i * 18
local textFlag = colorFlags[pos%2]
-- position
v.drawNum(column[1], y, pos)
-- facerank
local skin = skins[score["skin"]]
local facerank = skin and v.cachePatch(skin.facerank) or v.cachePatch("M_NORANK")
v.draw(column[1], y, facerank, 0, v.getColormap("sonic", score["color"]))
-- chili
if highlight then
local chilip = v.cachePatch("K_CHILI"..leveltime/4%8+1)
v.draw(column[1], y, chilip)
textFlag = V_YELLOWMAP
end
-- stats
drawStats(v, column[1], y, score["skin"], score["stat"])
-- name
v.drawString(column[2], y, score["name"], V_ALLOWLOWERCASE | textFlag)
-- time
v.drawString(column[3], y, TicsToTime(score["time"]), textFlag)
-- flags
drawFlags(v, column[4], y, score["flags"])
end
local function drawBrowser(v, player)
if not MapRecords then return end
v.fadeScreen(0xFF00, 16)
-- previous, next maps
for i = 5, 1, -1 do
drawMapPatch(v, -i)
drawMapPatch(v, i)
end
-- draw map border
drawMapBorder(v)
-- current map
drawMapPatch(v, 0)
drawEncore(v)
drawMapStrings(v)
drawGamemode(v)
if not modes then return end
local records = MapRecords[modes[mode]]
if not records then return end
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, records[i], records[i].name == player.name)
end
end
rawset(_G, "DrawBrowser", drawBrowser)
local function initBrowser(modeSep)
ModeSep = modeSep
-- set mapIndex to current map
for i, m in ipairs(maps) do
if m == gamemap then
mapIndex = i
break
end
end
-- initialize MapRecords
MapRecords = GetMapRecords(gamemap, mapChecksum(gamemap), ModeSep)
scrollPos = 1
updateModes()
end
rawset(_G, "InitBrowser", initBrowser)
-- initialize maps with racemaps only
local function loadMaps()
maps = {}
local hell = {}
for i = 0, #mapheaderinfo do
local map = mapheaderinfo[i]
if map and map.typeoflevel & TOL_RACE then
if map.menuflags & LF2_HIDEINMENU then
table.insert(hell, i)
else
table.insert(maps, i)
end
end
end
-- append hell maps
for _, map in ipairs(hell) do
table.insert(maps, map)
end
end
addHook("MapLoad", loadMaps)
local repeatCount = 0
local keyRepeat = 0
local function updateKeyRepeat()
S_StartSound(nil, 143)
if repeatCount < 1 then
keyRepeat = TICRATE / 4
else
keyRepeat = TICRATE / 15
end
repeatCount = $ + 1
end
local function resetKeyRepeat()
keyRepeat = 0
repeatCount = 0
end
local ValidButtons = BT_ACCELERATE | BT_BRAKE | BT_FORWARD | BT_BACKWARD | BT_DRIFT | BT_ATTACK
-- return value indicates we want to exit the browser
local function controller(player)
keyRepeat = max(0, $ - 1)
if not (player.cmd.driftturn or player.cmd.buttons) then
resetKeyRepeat()
end
local cmd = player.cmd
if not keyRepeat then
if not (cmd.buttons & ValidButtons or cmd.driftturn) then
return
end
updateKeyRepeat()
if cmd.buttons & BT_BRAKE then
S_StartSound(nil, 115)
return true
elseif cmd.buttons & BT_ACCELERATE then
COM_BufInsertText(player, "changelevel "..G_BuildMapName(maps[mapIndex]))
return true
elseif cmd.buttons & BT_ATTACK then
COM_BufInsertText(player, "encore")
elseif cmd.driftturn then
local dir = cmd.driftturn > 0 and -1 or 1
if encoremode then
updateMapIndex(-dir)
else
updateMapIndex(dir)
end
elseif cmd.buttons & BT_FORWARD then
scrollPos = $ - 1
elseif cmd.buttons & BT_BACKWARD then
scrollPos = $ + 1
elseif cmd.buttons & BT_DRIFT then
scrollPos = 1
if modes and #modes then
mode = $ % #modes + 1
prefMode = modes[mode]
end
end
end
end
rawset(_G, "BrowserController", controller)
local function netvars(net)
maps = net($)
mapIndex = net($)
modes = net($)
mode = net($)
prefMode = net($)
scrollPos = net($)
MapRecords = net($)
ModeSep = net($)
end
addHook("NetVars", netvars)