1230 lines
41 KiB
Lua
1230 lines
41 KiB
Lua
local Charting = require('common.charting')
|
|
local Easing = require('common.easing')
|
|
local Background = require('components.background')
|
|
local Dim = require("common.dimensions")
|
|
local Wallpaper = require("components.wallpaper")
|
|
local common = require('common.util')
|
|
local Sound = require("common.sound")
|
|
local Numbers = require('components.numbers')
|
|
local Footer = require('components.footer')
|
|
|
|
local VolforceCalc = require('components.volforceCalc')
|
|
local VolforceWindow = require("components.volforceWindow")
|
|
local VolforceAmount = game.GetSkinSetting('_volforce');
|
|
|
|
require("api.point2d")
|
|
require("components.radar")
|
|
|
|
local dataPanelImage = gfx.CreateSkinImage("song_select/data_bg_overlay.png", 1)
|
|
local dataGlowOverlayImage = gfx.CreateSkinImage("song_select/data_panel/data_glow_overlay.png", 1)
|
|
local gradeBgImage = gfx.CreateSkinImage("song_select/data_panel/grade_bg.png", 1)
|
|
local badgeBgImage = gfx.CreateSkinImage("song_select/data_panel/clear_badge_bg.png", 1)
|
|
local effectedBgImage = gfx.CreateSkinImage("song_select/data_panel/effected_bg.png", 1)
|
|
local illustratedBgImage = gfx.CreateSkinImage("song_select/data_panel/illust_bg.png", 1)
|
|
local songPlateBg = gfx.CreateSkinImage("song_select/plate/bg.png", 1)
|
|
local songPlateBottomBarOverlayImage = gfx.CreateSkinImage("song_select/plate/bottom_bar_overlay.png", 1)
|
|
local scoreBoardBarBgImage = gfx.CreateSkinImage("song_select/textboard.png", 1)
|
|
local crownImage = gfx.CreateSkinImage("song_select/crown.png", 1)
|
|
|
|
local laserAnimBaseImage = gfx.CreateSkinImage("song_select/laser_anim.png", 1)
|
|
|
|
local top50OverlayImage = gfx.CreateSkinImage("song_select/top50.png", 1)
|
|
local top50JacketOverlayImage = gfx.CreateSkinImage("song_select/top50_jacket.png", 1)
|
|
|
|
local diffCursorImage = gfx.CreateSkinImage("song_select/level_cursor.png", 1)
|
|
|
|
local scrollBarBackgroundImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1)
|
|
local scrollBarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1)
|
|
|
|
local filterInfoBgImage = gfx.CreateSkinImage("song_select/filter_info_bg.png", 1)
|
|
local sortInfoBgImage = gfx.CreateSkinImage("song_select/sort_info_bg.png", 1)
|
|
|
|
local searchBgImage = gfx.CreateSkinImage("song_select/search_bg.png", 1)
|
|
local searchActiveImage = gfx.CreateSkinImage("song_select/search_active.png", 1)
|
|
local searchInfoPanelImage = gfx.CreateSkinImage("song_select/search_info_panel.png", 1)
|
|
|
|
local defaultJacketImage = gfx.CreateSkinImage("song_select/loading.png", 0)
|
|
|
|
local difficultyLabelImages = {
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/novice.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/advanced.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/exhaust.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/maximum.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/infinite.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/gravity.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/heavenly.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/vivid.png", 1),
|
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/exceed.png", 1),
|
|
}
|
|
|
|
local badgeImages = {
|
|
gfx.CreateSkinImage("song_select/medal/nomedal.png", 1),
|
|
gfx.CreateSkinImage("song_select/medal/played.png", 1),
|
|
gfx.CreateSkinImage("song_select/medal/clear.png", 1),
|
|
gfx.CreateSkinImage("song_select/medal/hard.png", 1),
|
|
gfx.CreateSkinImage("song_select/medal/uc.png", 1),
|
|
gfx.CreateSkinImage("song_select/medal/puc.png", 1),
|
|
}
|
|
|
|
local cursorImages = {
|
|
gfx.CreateSkinImage("song_select/cursor.png", 1), -- Effective rate or fallback
|
|
gfx.CreateSkinImage("song_select/cursor_exc.png", 1), -- Excessive rate
|
|
gfx.CreateSkinImage("song_select/cursor_perm.png", 1), -- Premissive rate
|
|
gfx.CreateSkinImage("song_select/cursor_blast.png", 1), -- Blastive rate
|
|
}
|
|
local gradeCutoffs = {
|
|
D = 0000000,
|
|
C = 7000000,
|
|
B = 8000000,
|
|
A = 8700000,
|
|
A_P = 9000000,
|
|
AA = 9300000,
|
|
AA_P = 9500000,
|
|
AAA = 9700000,
|
|
AAA_P = 9800000,
|
|
S = 9900000,
|
|
}
|
|
|
|
local gradeImages = {
|
|
S = gfx.CreateSkinImage("common/grades/S.png", 0),
|
|
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
|
|
AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0),
|
|
AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0),
|
|
AA = gfx.CreateSkinImage("common/grades/AA.png", 0),
|
|
A_P = gfx.CreateSkinImage("common/grades/A+.png", 0),
|
|
A = gfx.CreateSkinImage("common/grades/A.png", 0),
|
|
B = gfx.CreateSkinImage("common/grades/B.png", 0),
|
|
C = gfx.CreateSkinImage("common/grades/C.png", 0),
|
|
D = gfx.CreateSkinImage("common/grades/D.png", 0),
|
|
none = gfx.CreateSkinImage("common/grades/none.png", 0),
|
|
}
|
|
|
|
local difficultyLabelUnderImages = {
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/nov.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/adv.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/exh.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/mxm.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/inf.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
|
|
gfx.CreateSkinImage("songtransition/difficulty_labels/xcd.png", 0),
|
|
}
|
|
|
|
game.LoadSkinSample('song_wheel/cursor_change.wav')
|
|
game.LoadSkinSample('song_wheel/diff_change.wav')
|
|
|
|
local scoreNumbers = Numbers.load_number_image("score_num")
|
|
local difficultyNumbers = Numbers.load_number_image("diff_num")
|
|
|
|
local showEffectRadar = game.GetSkinSetting("songselect_showEffectRadar") or false
|
|
|
|
local LEADERBOARD_PLACE_NAMES = {
|
|
'1st',
|
|
'2nd',
|
|
'3rd',
|
|
'4th',
|
|
}
|
|
|
|
local songPlateHeight = 172
|
|
|
|
local selectedIndex = 1
|
|
local selectedDifficulty = 1
|
|
|
|
local radar = Radar.new(Point2D.new(0, 0))
|
|
local updateRadar = true
|
|
|
|
local jacketCache = {}
|
|
|
|
local top50diffs = {}
|
|
|
|
local irRequestStatus = 1 -- 0=unused, 1=not requested, 2=loading, others are status codes
|
|
local irRequestTimeout = 2
|
|
local irLeaderboard = {} ---@type ServerScore[]|{}
|
|
local irLeaderboardsCache = {}
|
|
|
|
local transitionScrollScale = 0
|
|
local transitionScrollOffsetY = 0
|
|
local scrollingUp = false
|
|
|
|
local transitionAfterscrollScale = 0
|
|
local transitionAfterscrollDataOverlayAlpha = 0
|
|
local transitionAfterscrollGradeAlpha = 0
|
|
local transitionAfterscrollBadgeAlpha = 0
|
|
local transitionAfterscrollTextSongTitle = 0
|
|
local transitionAfterscrollTextSongArtist = 0
|
|
local transitionAfterscrollDifficultiesAlpha = 0
|
|
|
|
local transitionJacketBgScrollScale = 0
|
|
local transitionJacketBgScrollAlpha = 0
|
|
local transitionJacketBgScrollPosX = 0
|
|
|
|
--search
|
|
local searchPreviousActiveState = false
|
|
local searchInfoPreviousActiveState = false
|
|
local transitionSearchEnterScale = 0
|
|
local transitionSearchInfoEnterScale = 0
|
|
local transitionSearchBackgroundAlpha = 0
|
|
local transitionSearchbarOffsetY = 0
|
|
local transitionSearchInfoOffsetY = 0
|
|
local transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
|
|
|
|
local transitionLaserScale = 0
|
|
local transitionLaserY = 0
|
|
|
|
-- Flash transition (animation)
|
|
-- Used for flashing the badges
|
|
-- 0 = minimum brightness; 0.5 = maximum brightness; 1 = minimum brightness again
|
|
local transitionFlashScale = 0
|
|
local transitionFlashAlpha = 1
|
|
|
|
local isFilterWheelActive = false
|
|
local transitionLeaveScale = 0
|
|
local transitionLeaveReappearTimer = 0
|
|
local TRANSITION_LEAVE_DURATION = 0.1
|
|
|
|
-- Window variables
|
|
local resX, resY
|
|
|
|
-- Aspect Ratios
|
|
local landscapeWidescreenRatio = 16 / 9
|
|
local landscapeStandardRatio = 4 / 3
|
|
local portraitWidescreenRatio = 9 / 16
|
|
|
|
-- Portrait sizes
|
|
local fullX, fullY
|
|
local desw = 1080
|
|
local desh = 1920
|
|
|
|
local resolutionChange = function(x, y)
|
|
resX = x
|
|
resY = y
|
|
fullX = portraitWidescreenRatio * y
|
|
fullY = y
|
|
|
|
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR)
|
|
end
|
|
|
|
local function getCorrectedIndex(from, offset)
|
|
local total = #songwheel.songs
|
|
|
|
if (math.abs(offset) > total) then
|
|
if (offset < 0) then
|
|
offset = offset + total*math.floor(math.abs(offset)/total)
|
|
else
|
|
offset = offset - total*math.floor(math.abs(offset)/total)
|
|
end
|
|
end
|
|
|
|
local index = from + offset
|
|
|
|
if index < 1 then
|
|
index = total + (from+offset) -- this only happens if the offset is negative
|
|
end
|
|
|
|
if index > total then
|
|
local indexesUntilEnd = total - from
|
|
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
|
end
|
|
|
|
return index
|
|
end
|
|
|
|
local function getJacketImage(song)
|
|
if not jacketCache[song.id] or jacketCache[song.id]==defaultJacketImage then
|
|
jacketCache[song.id] = gfx.LoadImageJob(song.difficulties[
|
|
math.min(selectedDifficulty, #song.difficulties)
|
|
].jacketPath, defaultJacketImage, 500, 500)
|
|
end
|
|
|
|
return jacketCache[song.id]
|
|
end
|
|
|
|
local function getGradeImageForScore(score)
|
|
local gradeImage = gradeImages.none
|
|
local bestGradeCutoff = 0
|
|
for gradeName, scoreCutoff in pairs(gradeCutoffs) do
|
|
if scoreCutoff <= score then
|
|
if scoreCutoff > bestGradeCutoff then
|
|
gradeImage = gradeImages[gradeName]
|
|
bestGradeCutoff = scoreCutoff
|
|
end
|
|
end
|
|
end
|
|
|
|
return gradeImage
|
|
end
|
|
|
|
local function drawLaserAnim()
|
|
gfx.Save()
|
|
gfx.BeginPath()
|
|
|
|
gfx.Scissor(0, transitionLaserY, desw, 100)
|
|
|
|
gfx.ImageRect(0, 0, desw, desh, laserAnimBaseImage, 1, 0)
|
|
|
|
gfx.Restore()
|
|
end
|
|
|
|
local function drawBackground(deltaTime)
|
|
Background.draw(deltaTime)
|
|
|
|
local song = songwheel.songs[selectedIndex]
|
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
|
|
|
if (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then
|
|
-- If the score for song exists
|
|
if song and diff then
|
|
local jacketImage = getJacketImage(song)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(transitionJacketBgScrollPosX, 0, 900, 900, jacketImage or defaultJacketImage, transitionJacketBgScrollAlpha, 0)
|
|
|
|
gfx.BeginPath()
|
|
gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64))
|
|
gfx.Rect(0,0,900,900)
|
|
gfx.Fill()
|
|
gfx.ClosePath()
|
|
end
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(0, 0, desw, desh, dataPanelImage, 1, 0)
|
|
|
|
drawLaserAnim()
|
|
|
|
if song and diff and (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(0, 0, desw, desh, dataGlowOverlayImage, transitionAfterscrollDataOverlayAlpha, 0)
|
|
gfx.BeginPath()
|
|
|
|
gfx.ImageRect(341, 754, 85, 85, gradeBgImage, transitionAfterscrollDataOverlayAlpha, 0)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(391, 687, 180*0.85, 226*0.85, badgeBgImage, transitionAfterscrollDataOverlayAlpha, 0)
|
|
gfx.BeginPath()
|
|
|
|
gfx.ImageRect(95, 1165, 433, 30, effectedBgImage, transitionAfterscrollDataOverlayAlpha, 0)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(95, 1195, 433, 30, illustratedBgImage, transitionAfterscrollDataOverlayAlpha, 0)
|
|
end
|
|
|
|
end
|
|
|
|
---@param song SongWheelSong
|
|
---@param y number
|
|
local function drawSong(song, y)
|
|
if (not song) then return end
|
|
|
|
local songX = desw/2+28
|
|
local selectedSongDifficulty = song.difficulties[math.min(selectedDifficulty, #song.difficulties)] -- Limit selecting difficulty that is above the number that the song has
|
|
|
|
if not selectedSongDifficulty then
|
|
return
|
|
end
|
|
|
|
local bestScore
|
|
if selectedSongDifficulty.scores then
|
|
bestScore = selectedSongDifficulty.scores[1]
|
|
end
|
|
|
|
-- Draw the bg for the song plate
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX, y, 515, 172, songPlateBg, 1, 0)
|
|
|
|
-- Draw jacket
|
|
local jacketImage = getJacketImage(song)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX+4, y+4, 163, 163, jacketImage or defaultJacketImage, 1, 0)
|
|
|
|
-- Draw the overlay for the song plate (that bottom black bar)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX, y, 515, 172, songPlateBottomBarOverlayImage, 1, 0)
|
|
|
|
-- Draw the difficulty notch background
|
|
gfx.BeginPath()
|
|
local diffIndex = Charting.GetDisplayDifficulty(selectedSongDifficulty.jacketPath, selectedSongDifficulty.difficulty)
|
|
gfx.ImageRect(songX, y+95, 83, 74, difficultyLabelImages[diffIndex], 1, 0)
|
|
|
|
-- Draw the difficulty level number
|
|
gfx.BeginPath()
|
|
Numbers.draw_number(songX+30, y+125, 1.0, selectedSongDifficulty.level, 2, difficultyNumbers, false, 0.65, 1)
|
|
|
|
-- Draw song title
|
|
gfx.FontSize(24)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.Text(song.title, songX+90, y+155)
|
|
|
|
-- Draw score badge
|
|
local badgeImage = badgeImages[1]
|
|
if selectedSongDifficulty.topBadge then
|
|
badgeImage = badgeImages[selectedSongDifficulty.topBadge+1]
|
|
end
|
|
|
|
local badgeAlpha = 1
|
|
if (selectedSongDifficulty.topBadge >= 3) then
|
|
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX+282, y+44, 79, 69, badgeImage, badgeAlpha, 0)
|
|
|
|
-- Draw grade
|
|
local gradeImage = gradeImages.none
|
|
local gradeAlpha = 1
|
|
|
|
if bestScore then
|
|
gradeImage = getGradeImageForScore(bestScore.score)
|
|
|
|
if (bestScore.score >= gradeCutoffs.S) then
|
|
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
|
end
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0)
|
|
|
|
-- Draw top 50 label if applicable
|
|
if (top50diffs[selectedSongDifficulty.hash]) then
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0)
|
|
VolforceWindow.render(0,songX+95, y+105,24*1.5,VolforceAmount,false,true,false)
|
|
end
|
|
end
|
|
|
|
local function drawSongList()
|
|
gfx.GlobalAlpha(1-transitionLeaveScale)
|
|
|
|
local numOfSongsAround = 7 -- How many songs should be up and how many should be down of the selected one
|
|
|
|
local yOffset = transitionScrollOffsetY
|
|
|
|
local i=1
|
|
while (i <= numOfSongsAround) do
|
|
local songIndex = getCorrectedIndex(selectedIndex, -i)
|
|
drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2-songPlateHeight*i + yOffset)
|
|
i=i+1
|
|
end
|
|
|
|
-- Draw the selected song
|
|
drawSong(songwheel.songs[selectedIndex], desh/2-songPlateHeight/2 + yOffset)
|
|
|
|
i=1
|
|
while (i <= numOfSongsAround) do
|
|
local songIndex = getCorrectedIndex(selectedIndex, i)
|
|
drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2+songPlateHeight*i + yOffset)
|
|
i=i+1
|
|
end
|
|
|
|
gfx.GlobalAlpha(1)
|
|
end
|
|
|
|
---@param diff SongWheelDifficulty
|
|
local function drawLocalLeaderboard(diff)
|
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
gfx.FontSize(26)
|
|
|
|
local scoreBoardX = 75
|
|
local scoreBoardY = 1250
|
|
|
|
local sbBarWidth = 336*1.2
|
|
local sbBarHeight = 33
|
|
|
|
local sbBarContentLeftX = scoreBoardX + 80
|
|
local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30
|
|
|
|
-- Draw the header
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(205, 1252.5, 800*0.045, 600*0.045, crownImage, 1, 0)
|
|
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
gfx.Text("LOCAL TOP", sbBarContentRightX, scoreBoardY + sbBarHeight/2)
|
|
|
|
for i = 1, 5, 1 do
|
|
local scoreTable = diff.scores[i]
|
|
local score = scoreTable and scoreTable.score
|
|
local username = scoreTable and scoreTable.playerName
|
|
|
|
-- if for some reason there's a score but no associated username, fall back to skin setting
|
|
if score and username == "" then
|
|
username = string.upper(game.GetSkinSetting("username"))
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
|
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
gfx.Text(string.upper(username or "-"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
|
|
|
gfx.BeginPath()
|
|
local scoreText = score and tostring(score) or "- - - - - - - -"
|
|
gfx.Text(scoreText, sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
|
end
|
|
end
|
|
|
|
local function drawIrLeaderboard()
|
|
if not IRData.Active then
|
|
return
|
|
end
|
|
|
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
gfx.FontSize(26)
|
|
|
|
local scoreBoardX = 75
|
|
local scoreBoardY = 1500
|
|
|
|
local sbBarWidth = 336*1.2
|
|
local sbBarHeight = 33
|
|
|
|
local sbBarContentLeftX = scoreBoardX + 80
|
|
local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30
|
|
|
|
-- Draw the header
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
|
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
|
|
if irRequestStatus == 1 or irRequestStatus == 2 then
|
|
gfx.Text("Loading ranking...", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2)
|
|
return
|
|
end
|
|
|
|
if irRequestStatus == IRData.States.ChartRefused then
|
|
gfx.Text("This chart is blacklisted", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2)
|
|
return
|
|
end
|
|
|
|
if irRequestStatus == IRData.States.NotFound then
|
|
gfx.Text("This chart is not tracked", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2)
|
|
return
|
|
end
|
|
|
|
if #irLeaderboard == 0 then
|
|
gfx.Text("This chart has no scores", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2)
|
|
return
|
|
end
|
|
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
gfx.Text("IR TOP", scoreBoardX + (sbBarWidth / 2), scoreBoardY + sbBarHeight/2)
|
|
|
|
for i = 1, 4, 1 do
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
|
end
|
|
|
|
-- Becuase the scores are in "random order", we have to do this
|
|
for index, irScore in ipairs(irLeaderboard) do
|
|
-- local irScore = irLeaderboard[i]
|
|
|
|
if irScore then
|
|
local rank = index
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
gfx.Text(LEADERBOARD_PLACE_NAMES[rank], sbBarContentLeftX-40, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight)
|
|
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.BeginPath()
|
|
gfx.Text(string.upper(irScore.username), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight)
|
|
|
|
gfx.BeginPath()
|
|
gfx.Text(string.format("%d", irScore.score), sbBarContentRightX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight)
|
|
|
|
local badgeImage = badgeImages[irScore.lamp+1]
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(scoreBoardX + sbBarWidth - 50, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight - 12.5, 31.6, 27.6, badgeImage, 1, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function drawData(deltaTime) -- Draws the song data on the left panel
|
|
|
|
if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end
|
|
|
|
local song = songwheel.songs[selectedIndex]
|
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
|
local bestScore = diff and diff.scores[1]
|
|
|
|
if not song then return false end
|
|
|
|
local jacketImage = getJacketImage(song)
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0)
|
|
|
|
if (top50diffs[diff.hash]) then
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0)
|
|
VolforceWindow.render(deltaTime,85, 600,24*3,VolforceAmount,false,true,false)
|
|
end
|
|
|
|
-- Draw best score
|
|
gfx.Save()
|
|
gfx.BeginPath()
|
|
|
|
local scoreNumber = 0
|
|
if bestScore then
|
|
scoreNumber = bestScore.score
|
|
end
|
|
|
|
Numbers.draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12)
|
|
Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12)
|
|
|
|
-- Draw grade
|
|
local gradeImage = gradeImages.none
|
|
local gradeAlpha = transitionAfterscrollGradeAlpha
|
|
if bestScore then
|
|
gradeImage = getGradeImageForScore(bestScore.score)
|
|
|
|
if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then
|
|
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
|
end
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0)
|
|
|
|
-- Draw badge
|
|
local badgeImage = badgeImages[diff.topBadge+1]
|
|
|
|
local badgeAlpha = transitionAfterscrollBadgeAlpha
|
|
if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then
|
|
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, badgeAlpha, 0)
|
|
|
|
gfx.Restore()
|
|
|
|
-- Draw BPM
|
|
gfx.Save()
|
|
gfx.BeginPath()
|
|
gfx.FontSize(24)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out
|
|
gfx.Text(song.bpm, 85, 920)
|
|
gfx.Restore()
|
|
|
|
-- Draw song title
|
|
gfx.Save()
|
|
gfx.FontSize(28)
|
|
gfx.GlobalAlpha(transitionAfterscrollTextSongTitle)
|
|
gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955)
|
|
gfx.Restore()
|
|
|
|
-- Draw artist
|
|
gfx.Save()
|
|
gfx.GlobalAlpha(transitionAfterscrollTextSongArtist)
|
|
gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997)
|
|
gfx.Restore()
|
|
|
|
-- Draw difficulties
|
|
local DIFF_X_START = 98.5
|
|
local DIFF_GAP = 114.8
|
|
gfx.Save()
|
|
gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha)
|
|
for i, diff in ipairs(song.difficulties) do
|
|
gfx.BeginPath()
|
|
|
|
local index = diff.difficulty+1
|
|
|
|
if i == selectedDifficulty then
|
|
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-(163*0.8)/2, 1028, 163*0.8, 163*0.8, diffCursorImage, 1, 0)
|
|
end
|
|
|
|
Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1)
|
|
|
|
local diffLabelImage = difficultyLabelUnderImages[
|
|
Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty)
|
|
]
|
|
local tw, th = gfx.ImageSize(diffLabelImage)
|
|
tw=tw*0.9
|
|
th=th*0.9
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0)
|
|
end
|
|
gfx.Restore()
|
|
|
|
-- Scoreboard
|
|
|
|
drawLocalLeaderboard(diff)
|
|
drawIrLeaderboard()
|
|
|
|
gfx.Save()
|
|
gfx.FontSize(22)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha)
|
|
gfx.Text(diff.effector, 270, 1180) -- effected by
|
|
gfx.Text(diff.illustrator, 270, 1210) -- illustrated by
|
|
gfx.Restore()
|
|
|
|
end
|
|
|
|
local function drawFilterInfo(deltatime)
|
|
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
|
|
|
if (songwheel.searchInputActive) then
|
|
--return
|
|
end
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(5, 95, 417*0.85, 163*0.85, filterInfoBgImage, 1, 0)
|
|
|
|
local folderLabel = game.GetSkinSetting('_songWheelActiveFolderLabel')
|
|
local subFolderLabel = game.GetSkinSetting('_songWheelActiveSubFolderLabel')
|
|
local sortOptionLabel = game.GetSkinSetting('_songWheelActiveSortOptionLabel')
|
|
|
|
gfx.FontSize(24)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
|
|
|
gfx.BeginPath()
|
|
gfx.Text(folderLabel or '', 167, 131)
|
|
|
|
gfx.BeginPath()
|
|
gfx.Text(subFolderLabel or '', 195, 166)
|
|
|
|
gfx.BeginPath()
|
|
gfx.ImageRect(desw - 310 - 5, 108, 310, 75, sortInfoBgImage, 1, 0)
|
|
|
|
gfx.BeginPath()
|
|
gfx.Text(sortOptionLabel or '', desw-150, 130)
|
|
end
|
|
|
|
local function drawCursor()
|
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return false end
|
|
|
|
gfx.BeginPath()
|
|
|
|
local cursorImageIndex = game.GetSkinSetting('_gaugeType')
|
|
local cursorImage = cursorImages[cursorImageIndex or 1]
|
|
|
|
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
|
|
end
|
|
|
|
local function drawSearch()
|
|
if (not songwheel.searchInputActive and searchPreviousActiveState) then
|
|
searchPreviousActiveState = false
|
|
game.PlaySample('sort_wheel/enter.wav')
|
|
elseif (songwheel.searchInputActive and not searchPreviousActiveState) then
|
|
searchPreviousActiveState = true
|
|
game.PlaySample('sort_wheel/leave.wav')
|
|
end
|
|
|
|
if (songwheel.searchText ~= '' and searchInfoPreviousActiveState == true) then
|
|
searchInfoPreviousActiveState = false
|
|
elseif (songwheel.searchText == '' and searchInfoPreviousActiveState == false) then
|
|
searchInfoPreviousActiveState = true
|
|
end
|
|
|
|
if (transitionSearchEnterScale == 0) then
|
|
return
|
|
end
|
|
|
|
-- Draw dark overlay over Songwheel
|
|
gfx.BeginPath()
|
|
gfx.FillColor(0, 0, 0, math.floor(transitionSearchBackgroundAlpha * 192))
|
|
gfx.Rect(0, 0, 1080, 1920)
|
|
gfx.Fill()
|
|
|
|
-- Draw search info panel
|
|
gfx.BeginPath()
|
|
local infoResize = 0.855
|
|
local sw, sh = gfx.ImageSize(searchInfoPanelImage)
|
|
sw = sw * infoResize
|
|
sh = sh * infoResize
|
|
local infoXPos = 0
|
|
local infoYStartPos = desh - sh - 772 + 242
|
|
local infoYPos = infoYStartPos + transitionSearchInfoOffsetY
|
|
|
|
if (game.GetSkinSetting('gameplay_showSearchControls')) then
|
|
gfx.ImageRect(infoXPos, infoYPos, sw, sh, searchInfoPanelImage, transitionSearchBackgroundInfoAlpha, 0)
|
|
end
|
|
|
|
-- Draw Search is Active text
|
|
gfx.BeginPath()
|
|
local activeResize = 0.855
|
|
local activew, activeh = gfx.ImageSize(searchActiveImage)
|
|
activew = activew * activeResize
|
|
activeh = activeh * activeResize
|
|
local activeXPos = 0
|
|
local activeYStartPos = desh - sh - 722
|
|
|
|
local activeYPos = activeYStartPos + transitionSearchInfoOffsetY
|
|
gfx.ImageRect(activeXPos, activeYPos, activew, activeh, searchActiveImage, 1, 0)
|
|
|
|
-- Draw Searchbox
|
|
gfx.BeginPath()
|
|
local searchResize = 0.8
|
|
local tw, th = gfx.ImageSize(searchBgImage)
|
|
tw = tw * searchResize
|
|
th = th * searchResize
|
|
local xPos = (desw-tw)/2
|
|
local yStartPos = 170
|
|
|
|
local yPos = yStartPos - transitionSearchbarOffsetY
|
|
|
|
gfx.ImageRect(xPos, yPos, tw, th, searchBgImage, 1, 0)
|
|
|
|
gfx.FontSize(48)
|
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2)
|
|
end
|
|
|
|
local function drawScrollbar()
|
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
|
|
|
-- Scrollbar Background
|
|
gfx.BeginPath()
|
|
local resize = 0.85
|
|
local lw, lh = gfx.ImageSize(scrollBarBackgroundImage)
|
|
local lw = lw * resize
|
|
local lh = lh * resize
|
|
local xPos = desw-20
|
|
local backgroundYPos = desh/2 - lh/2
|
|
gfx.ImageRect(xPos, backgroundYPos, lw, lh, scrollBarBackgroundImage, 1, 0)
|
|
|
|
-- Scrollbar Fill
|
|
gfx.BeginPath()
|
|
local sw, sh = gfx.ImageSize(scrollBarFillImage)
|
|
local sw = sw * resize
|
|
local sh = sh * resize
|
|
local fillXPos = xPos - 6
|
|
|
|
local minScrollYPos = backgroundYPos
|
|
local maxScrollYPos = backgroundYPos + lh - sh
|
|
local scrollStep = (maxScrollYPos - minScrollYPos) / (#songwheel.songs - 1)
|
|
local scrollbarYOffset = (selectedIndex - 1) * scrollStep
|
|
local scrollbarYPos = minScrollYPos + scrollbarYOffset
|
|
gfx.ImageRect(fillXPos, scrollbarYPos, sw, sh, scrollBarFillImage, 1, 0)
|
|
|
|
-- 1st letter of song title on scroll
|
|
gfx.BeginPath()
|
|
gfx.FontSize(16)
|
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
gfx.Rect(fillXPos-18, scrollbarYPos - 5, 16, 16)
|
|
gfx.FillColor(0,0,0,170)
|
|
gfx.Fill()
|
|
gfx.FillColor(255,255,255)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
|
|
if (songwheel.songs[selectedIndex] ~= nil) then
|
|
local title = songwheel.songs[selectedIndex].title;
|
|
local letter = string.upper(common.firstAlphaNum(title))
|
|
gfx.Text(letter, fillXPos-10, scrollbarYPos + 5)
|
|
end
|
|
end
|
|
|
|
---Called on IR Leaderboard fetch complete
|
|
---@param res IRLeaderboardResponse
|
|
local function onIrLeaderboardFetched(res)
|
|
irRequestStatus = res.statusCode
|
|
|
|
local song = songwheel.songs[selectedIndex]
|
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
|
|
|
if res.statusCode == IRData.States.Success then
|
|
local tempIrLB = res.body
|
|
|
|
table.sort(tempIrLB, function (a,b)
|
|
return a.score > b.score
|
|
end)
|
|
|
|
irLeaderboard = tempIrLB
|
|
irLeaderboardsCache[diff.hash] = irLeaderboard
|
|
else
|
|
local httpStatus = (res.statusCode // 10) * 100 + res.statusCode % 10 -- convert to 100 range
|
|
game.Log("IR error (" .. httpStatus .. "): " .. res.description, game.LOGGER_ERROR)
|
|
if res.body then
|
|
game.Log(common.dump(res.body), game.LOGGER_ERROR)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function refreshIrLeaderboard(deltaTime)
|
|
if not IRData.Active then
|
|
return
|
|
end
|
|
|
|
if irRequestStatus ~= 1 then -- Only continue if the leaderboard is requesteded, but not loading or loaded.
|
|
return
|
|
end
|
|
irLeaderboard = {}
|
|
|
|
local song = songwheel.songs[selectedIndex]
|
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
|
|
|
if (not diff) then
|
|
return
|
|
end
|
|
|
|
if (irLeaderboardsCache[diff.hash]) then
|
|
irLeaderboard = irLeaderboardsCache[diff.hash]
|
|
irRequestStatus = 20
|
|
return
|
|
end
|
|
|
|
if (irRequestTimeout > 0) then
|
|
irRequestTimeout = irRequestTimeout - deltaTime
|
|
return
|
|
end
|
|
|
|
irRequestStatus = 2 -- Loading
|
|
IR.Leaderboard(diff.hash, 'best', 4, onIrLeaderboardFetched)
|
|
end
|
|
|
|
local function tickTransitions(deltaTime)
|
|
if transitionScrollScale < 1 then
|
|
transitionScrollScale = transitionScrollScale + deltaTime / 0.1 -- transition should last for that time in seconds
|
|
else
|
|
transitionScrollScale = 1
|
|
end
|
|
|
|
if transitionAfterscrollScale < 1 then
|
|
if transitionScrollScale == 1 then
|
|
-- Only start the after scroll transition when the scroll transition is finished
|
|
transitionAfterscrollScale = transitionAfterscrollScale + deltaTime / 15
|
|
end
|
|
else
|
|
transitionAfterscrollScale = 1
|
|
end
|
|
|
|
if scrollingUp then
|
|
transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * songPlateHeight
|
|
else
|
|
transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * -songPlateHeight
|
|
end
|
|
|
|
if transitionAfterscrollScale < 0.02 then
|
|
transitionAfterscrollDataOverlayAlpha = math.min(1, transitionAfterscrollScale / 0.02)
|
|
else
|
|
transitionAfterscrollDataOverlayAlpha = 1
|
|
end
|
|
|
|
-- Searchbar offsets and alpha
|
|
if not searchPreviousActiveState then
|
|
if transitionSearchEnterScale > 0 then
|
|
transitionSearchEnterScale = transitionSearchEnterScale - deltaTime / 0.5 -- transition should last for that time in seconds
|
|
else
|
|
transitionSearchEnterScale = 0
|
|
end
|
|
else
|
|
if transitionSearchEnterScale < 1 then
|
|
transitionSearchEnterScale = transitionSearchEnterScale + deltaTime / 0.5 -- transition should last for that time in seconds
|
|
else
|
|
transitionSearchEnterScale = 1
|
|
end
|
|
end
|
|
|
|
transitionSearchInfoOffsetY = Easing.inOutQuad(1 - transitionSearchEnterScale) * 1680
|
|
transitionSearchbarOffsetY = Easing.inOutQuad(1 - transitionSearchEnterScale) * 300
|
|
transitionSearchBackgroundAlpha = Easing.inOutQuad(transitionSearchEnterScale)
|
|
|
|
if not searchInfoPreviousActiveState then
|
|
if transitionSearchInfoEnterScale > 0 then
|
|
transitionSearchInfoEnterScale = transitionSearchInfoEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds
|
|
else
|
|
transitionSearchInfoEnterScale = 0
|
|
end
|
|
else
|
|
if transitionSearchInfoEnterScale < 1 then
|
|
transitionSearchInfoEnterScale = transitionSearchInfoEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds
|
|
else
|
|
transitionSearchInfoEnterScale = 1
|
|
end
|
|
end
|
|
|
|
-- Grade alpha
|
|
if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then
|
|
transitionAfterscrollGradeAlpha = 0.5
|
|
elseif transitionAfterscrollScale >= 0.04 then
|
|
transitionAfterscrollGradeAlpha = 1
|
|
else
|
|
transitionAfterscrollGradeAlpha = 0
|
|
end
|
|
|
|
-- Badge alpha
|
|
if transitionAfterscrollScale >= 0.032 and transitionAfterscrollScale < 0.035 then
|
|
transitionAfterscrollBadgeAlpha = 0.5
|
|
elseif transitionAfterscrollScale >= 0.042 then
|
|
transitionAfterscrollBadgeAlpha = 1
|
|
else
|
|
transitionAfterscrollBadgeAlpha = 0
|
|
end
|
|
|
|
-- Song title alpha and pos
|
|
if transitionAfterscrollScale < 0.025 then
|
|
transitionAfterscrollTextSongTitle = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025))
|
|
else
|
|
transitionAfterscrollTextSongTitle = 1
|
|
end
|
|
-- Song artist alpha and pos
|
|
if transitionAfterscrollScale < 0.025 then
|
|
transitionAfterscrollTextSongArtist = Easing.outQuad(math.min(1, (transitionAfterscrollScale) / 0.025))
|
|
else
|
|
transitionAfterscrollTextSongArtist = 1
|
|
end
|
|
|
|
-- Difficulties alpha
|
|
if transitionAfterscrollScale < 0.025 then
|
|
transitionAfterscrollDifficultiesAlpha = math.min(1, transitionAfterscrollScale / 0.025)
|
|
else
|
|
transitionAfterscrollDifficultiesAlpha = 1
|
|
end
|
|
|
|
-- Jacket bg animation
|
|
if transitionJacketBgScrollScale < 1 then
|
|
transitionJacketBgScrollScale = transitionJacketBgScrollScale + deltaTime / 20 -- transition should last for that time in seconds
|
|
else
|
|
transitionJacketBgScrollScale = 0
|
|
end
|
|
|
|
if transitionJacketBgScrollScale < 0.05 or transitionJacketBgScrollScale >= 1 then
|
|
transitionJacketBgScrollAlpha = 0
|
|
elseif transitionJacketBgScrollScale >= 0.05 and transitionJacketBgScrollScale < 0.1 then
|
|
transitionJacketBgScrollAlpha = math.min(1, (transitionJacketBgScrollScale-0.05) / 0.05)
|
|
elseif transitionJacketBgScrollScale >= 0.8 and transitionJacketBgScrollScale < 1 then
|
|
transitionJacketBgScrollAlpha = math.max(0,
|
|
math.min(1, 1-((transitionJacketBgScrollScale-0.8) / 0.05))
|
|
)
|
|
else
|
|
transitionJacketBgScrollAlpha = 1
|
|
end
|
|
|
|
transitionJacketBgScrollPosX = 0+(transitionJacketBgScrollScale*(0.8/1))*-300
|
|
|
|
-- Laser anim
|
|
if transitionLaserScale < 1 then
|
|
transitionLaserScale = transitionLaserScale + deltaTime / 2 -- transition should last for that time in seconds
|
|
else
|
|
transitionLaserScale = 0
|
|
end
|
|
|
|
transitionLaserY = desh - math.min(transitionLaserScale * 2 * desh, desh)
|
|
|
|
-- Flash transition
|
|
if transitionFlashScale < 1 then
|
|
---@type number|string
|
|
local songBpm = 120
|
|
|
|
if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then
|
|
local songBpmStr = songwheel.songs[selectedIndex].bpm
|
|
local songBpmStrs = common.split(songBpmStr, '-')
|
|
local minBpm = tonumber(songBpmStrs[1]) -- Lowest bpm value
|
|
songBpm = minBpm or songBpm
|
|
end
|
|
|
|
transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds
|
|
else
|
|
transitionFlashScale = 0
|
|
end
|
|
|
|
if transitionFlashScale < 0.5 then
|
|
transitionFlashAlpha = transitionFlashScale * 2
|
|
else
|
|
transitionFlashAlpha = 1-((transitionFlashScale-0.5) * 2)
|
|
end
|
|
transitionFlashAlpha = 1+transitionFlashAlpha*0.5
|
|
|
|
-- Leave transition
|
|
if (isFilterWheelActive) then
|
|
if transitionLeaveScale < 1 then
|
|
transitionLeaveScale = transitionLeaveScale + deltaTime / TRANSITION_LEAVE_DURATION -- transition should last for that time in seconds
|
|
else
|
|
transitionLeaveScale = 1
|
|
end
|
|
transitionLeaveReappearTimer = 1
|
|
transitionAfterscrollScale = 0 -- Keep songwheel in the "afterscroll" state while the filterwheel is active
|
|
transitionJacketBgScrollScale = 0 -- Same thing here, just with the jacket bg
|
|
else
|
|
if (transitionLeaveReappearTimer ~= 0) then
|
|
transitionAfterscrollScale = 0 -- Keep songwheel in the "afterscroll" state while we're waiting on filter wheel to fade out
|
|
transitionJacketBgScrollScale = 0 -- Same thing here, just with the jacket bg
|
|
end
|
|
|
|
transitionLeaveReappearTimer = transitionLeaveReappearTimer - deltaTime / (TRANSITION_LEAVE_DURATION + 0.05) -- 0.05s is a few frames between the completetion of the fade out and songs reappearing in the AC
|
|
|
|
if (transitionLeaveReappearTimer <= 0) then
|
|
transitionLeaveScale = 0
|
|
transitionLeaveReappearTimer = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
---This function is basically a workaround for the ForceRender call
|
|
local function drawRadar()
|
|
if not showEffectRadar then return end
|
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
|
|
|
local x, y = 375, 650
|
|
local scale = 0.666
|
|
|
|
gfx.FontSize(28)
|
|
gfx.Translate(x, y)
|
|
gfx.Scale(scale, scale)
|
|
|
|
local strokeColor = ColorRGBA.new(255, 255, 255, 128)
|
|
local fillColor = ColorRGBA.new(0, 0, 0, 191)
|
|
|
|
gfx.ResetScissor()
|
|
radar:drawBackground(fillColor)
|
|
radar:drawOutline(3, strokeColor)
|
|
|
|
--Bug: ForceRender resets every transformation, need to re-setup view transform afterwards
|
|
--ForceRender also resets gfx stack, USC will crash if you try to call gfx.Restore(),
|
|
--make sure the gfx stack is clean before calling radar:drawRadarMesh()
|
|
radar:drawRadarMesh()
|
|
|
|
Dim.transformToScreenSpace()
|
|
|
|
gfx.Save()
|
|
gfx.Translate(x, y)
|
|
gfx.Scale(scale, scale)
|
|
radar:drawRadialTicks(strokeColor)
|
|
radar:drawAttributes()
|
|
gfx.Restore()
|
|
end
|
|
|
|
local draw_songwheel = function(deltaTime)
|
|
drawBackground(deltaTime)
|
|
|
|
drawSongList()
|
|
|
|
isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1
|
|
|
|
drawData()
|
|
|
|
drawRadar()
|
|
|
|
drawCursor()
|
|
|
|
drawFilterInfo(deltaTime)
|
|
|
|
drawSearch()
|
|
|
|
drawScrollbar()
|
|
|
|
gfx.BeginPath()
|
|
gfx.FontSize(18)
|
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
|
local debugScrollingUp= "FALSE"
|
|
if scrollingUp then debugScrollingUp = "TRUE" end
|
|
|
|
if game.GetSkinSetting('debug_showInformation') then
|
|
gfx.Text('S_I: ' .. selectedIndex .. ' // S_D: ' .. selectedDifficulty .. ' // S_UP: ' .. debugScrollingUp .. ' // AC_TS: ' .. transitionAfterscrollScale .. ' // L_TS: ' .. transitionLeaveScale .. ' // IR_CODE: ' .. irRequestStatus .. ' // IR_T: ' .. irRequestTimeout, 8, 8)
|
|
end
|
|
|
|
Footer.draw(deltaTime)
|
|
|
|
gfx.ResetTransform()
|
|
end
|
|
|
|
---@diagnostic disable-next-line:lowercase-global
|
|
render = function (deltaTime)
|
|
tickTransitions(deltaTime)
|
|
|
|
game.SetSkinSetting('_currentScreen', 'songwheel')
|
|
|
|
Sound.stopMusic()
|
|
|
|
if showEffectRadar and updateRadar then
|
|
local difficultyNames = {"nov","adv","exh","mxm","inf","grv","hvn","vvd","xcd"}
|
|
local diff = songwheel.songs[selectedIndex].difficulties[selectedDifficulty].difficulty + 1
|
|
radar:updateGraph(songwheel.songs[selectedIndex].path, difficultyNames[diff])
|
|
updateRadar = false
|
|
end
|
|
|
|
Dim.updateResolution()
|
|
|
|
Wallpaper.render()
|
|
|
|
Dim.transformToScreenSpace()
|
|
|
|
draw_songwheel(deltaTime)
|
|
|
|
refreshIrLeaderboard(deltaTime)
|
|
end
|
|
|
|
---@diagnostic disable-next-line:lowercase-global
|
|
songs_changed = function (withAll)
|
|
|
|
irLeaderboardsCache = {} -- Reset LB cache
|
|
|
|
if not withAll then return end
|
|
|
|
game.SetSkinSetting('_songWheelScrollbarTotal', #songwheel.songs)
|
|
game.SetSkinSetting('_songWheelScrollbarIndex', selectedIndex)
|
|
|
|
local diffs = {}
|
|
for i = 1, #songwheel.allSongs do
|
|
local song = songwheel.allSongs[i]
|
|
for j = 1, #song.difficulties do
|
|
local diff = song.difficulties[j]
|
|
table.insert(diffs, {hash = diff.hash, force = VolforceCalc.calc(diff)})
|
|
end
|
|
end
|
|
|
|
table.sort(diffs, function (l, r)
|
|
return l.force > r.force
|
|
end)
|
|
|
|
local totalForce = 0
|
|
for i = 1, 50 do
|
|
local diff = diffs[i]
|
|
if not diff then
|
|
break
|
|
end
|
|
top50diffs[diff.hash] = true
|
|
totalForce = totalForce + diff.force
|
|
end
|
|
|
|
game.SetSkinSetting('_volforce', totalForce)
|
|
end
|
|
|
|
---@diagnostic disable-next-line:lowercase-global
|
|
set_index = function(newIndex)
|
|
transitionScrollScale = 0
|
|
transitionAfterscrollScale = 0
|
|
transitionJacketBgScrollScale = 0
|
|
|
|
Footer.resetTimer()
|
|
|
|
game.SetSkinSetting('_songWheelScrollbarTotal', #songwheel.songs)
|
|
game.SetSkinSetting('_songWheelScrollbarIndex', newIndex)
|
|
|
|
scrollingUp = false
|
|
if ((newIndex > selectedIndex and not (newIndex == #songwheel.songs and selectedIndex == 1)) or (newIndex == 1 and selectedIndex == #songwheel.songs)) then
|
|
scrollingUp = true
|
|
end
|
|
|
|
updateRadar = true
|
|
|
|
game.PlaySample('song_wheel/cursor_change.wav')
|
|
|
|
selectedIndex = newIndex
|
|
end
|
|
|
|
local json = require("common.json")
|
|
|
|
---@diagnostic disable-next-line:lowercase-global
|
|
set_diff = function(newDiff)
|
|
if newDiff ~= selectedDifficulty then
|
|
jacketCache = {} -- Clear the jacket cache for the new diff jackets
|
|
|
|
game.PlaySample('song_wheel/diff_change.wav')
|
|
end
|
|
|
|
Footer.resetTimer()
|
|
updateRadar = true
|
|
|
|
selectedDifficulty = newDiff
|
|
irLeaderboard = {}
|
|
irRequestStatus = 1
|
|
irRequestTimeout = 2
|
|
end
|