diff --git a/browser.lua b/browser.lua new file mode 100644 index 0000000..8c9d8d2 --- /dev/null +++ b/browser.lua @@ -0,0 +1,337 @@ +local maps +local mapIndex = 1 +local modes = nil +local mode = 1 + +local function mapIndexOffset(n) + return (mapIndex + n + #maps - 1) % #maps + 1 +end + +local function updateMapIndex(n) + mapIndex = mapIndexOffset(n) + + -- TODO: keep mode while scrolling through levels + mode = 1 + modes = nil +end + +local function getMap(offset) + return maps[mapIndexOffset(offset or 0)] +end + +local scalar = 2 +local hlfScrnWdth = 320 / 2 +local mappY = 26 +local ttlY = mappY + FixedMul(30, FRACUNIT / scalar) +local scoresY = ttlY + 16 + +local function drawMapPatch(v, offset) + local scale = FRACUNIT / (abs(offset) + scalar) + local mapName = G_BuildMapName(getMap(offset)) + local mapp = v.cachePatch(mapName.."P") + + 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 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 zoneAct(map) + local z = "" + if map.zonttl != "" then + z = " " + map.zonttl + elseif not(map.levelflags & LF_NOZONE) then + z = " Zone" + end + if map.actnum != "" then + z = $ + " " + map.actnum + end + + return z +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" + ) +end + +local F_SPBATK = 0x1 +local F_SPBJUS = 0x2 +local F_SPBBIG = 0x4 +local F_SPBEXP = 0x8 + +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 / 6 % 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 + +-- draw in columns +-- pos, facerank, name, time, flags +-- ______________________________________________ +-- | 3|[O]|InsertNameHere | 01:02:03 | EXB | +-- ---------------------------------------------- +local column = { + [0] = 18, -- pos, drawNum is right aligned + [1] = 18, -- facerank + [2] = 46, -- name + [3] = 216, -- time + [4] = 250 -- flags +} +local function drawScore(v, i, score) + local y = scoresY + i * 18 + + -- position + v.drawNum(column[0], y, i) + + 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"])) + v.drawString(column[2], y, score["name"], V_ALLOWLOWERCASE) + v.drawString(column[3], y, rawget(_G, "TicsToTime")(score["time"])) +end + +local function drawBrowser(v, leaderboard) + -- set available modes for this map + -- TODO: set this elsewhere + if modes == nil then + modes = {} + for mode, scoreTable in pairs(leaderboard) do + if scoreTable[getMap()] then + table.insert(modes, mode) + end + end + table.sort(modes) + 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) + drawMapStrings(v) + drawGamemode(v) + + if not modes then return end + + local gamemode = leaderboard[modes[mode]] + if not gamemode then return end + + local scoreTable = gamemode[getMap()] + if not scoreTable then return end + + for i, score in ipairs(scoreTable) do + if i > 10 then + break + end + drawScore(v, i, score) + end +end +rawset(_G, "DrawBrowser", drawBrowser) + +local function initBrowser(leaderboard) + -- set mapIndex to current map + for i, m in ipairs(maps) do + if m == gamemap then + mapIndex = i + break + end + end + + modes = nil + mode = 1 +end +rawset(_G, "InitBrowser", initBrowser) + +-- initialize maps with racemaps only +local function loadMaps() + maps = {} + for i = 0, #mapheaderinfo do + local map = mapheaderinfo[i] + if map and map.typeoflevel & TOL_RACE then + table.insert(maps, i) + end + 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 + +-- 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 + + if not keyRepeat then + if player.cmd.driftturn > 0 then + updateMapIndex(-1) + updateKeyRepeat() + elseif player.cmd.driftturn < 0 then + updateMapIndex(1) + updateKeyRepeat() + elseif player.cmd.buttons & BT_DRIFT then + if #modes then + mode = $ % #modes + 1 + end + updateKeyRepeat() + elseif player.cmd.buttons & BT_BRAKE then + S_StartSound(nil, 115) + return true + elseif player.cmd.buttons & BT_ACCELERATE then + S_StartSound(nil, 143) + COM_BufInsertText(player, "changelevel "..G_BuildMapName(maps[mapIndex])) + return true + end + end +end +rawset(_G, "BrowserController", controller) + + +local function netvars(net) + maps = net($) + mapIndex = net($) + modes = net($) + mode = net($) +end +addHook("NetVars", netvars) diff --git a/leaderboard.lua b/leaderboard.lua index 87b8696..cd49c1a 100644 --- a/leaderboard.lua +++ b/leaderboard.lua @@ -33,7 +33,7 @@ local RedFlash = { local StatTrack = false local UNCLAIMED = "Unclaimed Record" -local HELP_MESSAGE = "\x89Leaderboard Commands:\nretry exit findmap changelevel spba_clearcheats lb_gui rival scroll encore records" +local HELP_MESSAGE = "\x89Leaderboard Commands:\nretry exit findmap changelevel spba_clearcheats lb_gui rival scroll encore records levelselect" local FILENAME = "leaderboard.txt" -- Retry / changelevel map @@ -68,6 +68,7 @@ local DS_DEFAULT = 0x0 local DS_SCROLL = 0x1 local DS_AUTO = 0x2 local DS_SCRLTO = 0x4 +local DS_BROWSER = 0x8 local drawState = DS_DEFAULT @@ -396,6 +397,18 @@ local function exitlevel(player, ...) end COM_AddCommand("exit", exitlevel) +local function browser(player) + if not doyoudare(player) then return end + + if not rawget(_G, "DrawBrowser") then + print("Browser is not loaded") + return + end + rawget(_G, "InitBrowser")() + drawState = DS_BROWSER +end +COM_AddCommand("levelselect", browser) + local function zoneAct(map) local z = "" if map.zonttl != "" then @@ -835,6 +848,7 @@ function ticsToTime(tics, pure) G_TicsToCentiseconds(tics) ) end +rawset(_G, "TicsToTime", ticsToTime) -- Item patches have the amazing property of being displaced 12x 13y pixels local iXoffset = 13 * FRACUNIT @@ -1116,11 +1130,16 @@ local function drawScrollTo(v, player, scoreTable, gui) drawScroll(v, player, scoreTable, gui) end +local function drawBrowser(v, player) + rawget(_G, "DrawBrowser")(v, lb) +end + local stateFunctions = { [DS_DEFAULT] = drawDefault, [DS_SCROLL] = drawScroll, [DS_AUTO] = drawAuto, - [DS_SCRLTO] = drawScrollTo + [DS_SCRLTO] = drawScrollTo, + [DS_BROWSER] = drawBrowser } -- Draw mode and return pos + 1 if success @@ -1457,6 +1476,10 @@ local function think() scrollAcc = 0 end end + elseif drawState == DS_BROWSER then + if rawget(_G, "BrowserController")(p) then + drawState = DS_DEFAULT + end end if p.lives == 0 then