require('common') local Easing = require('common.easings') local Background = require('components.background'); local VolforceCalc = require('components.volforceCalc'); 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 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 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), } 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 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), } game.LoadSkinSample('song_wheel/cursor_change.wav'); game.LoadSkinSample('song_wheel/diff_change.wav'); local difficultyNumbers; local scoreNumbers; local resx, resy = game.GetResolution() local desw, desh; local scale; local songPlateHeight = 172; local selectedIndex = 1; local selectedDifficulty = 1; local jacketCache = {} local top50diffs = {} 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; 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 transitionLeaveReverse = false; local transitionLeaveScale = 0; local transitionLeaveOffsetY = 0; function resetLayoutInformation() resx, resy = game.GetResolution() desw = 1080 desh = 1920 scale = resx / desw end function load_number_image(path) local images = {} for i = 0, 9 do images[i + 1] = gfx.CreateSkinImage(string.format("%s/%d.png", path, i), 0) end return images end function draw_number(x, y, alpha, num, digits, images, is_dim, scale, kern) scale = scale or 1; kern = kern or 1; local tw, th = gfx.ImageSize(images[1]) tw = tw * scale; th = th * scale; x = x + (tw * (digits - 1)) / 2 y = y - th / 2 for i = 1, digits do local mul = 10 ^ (i - 1) local digit = math.floor(num / mul) % 10 local a = alpha if is_dim and num < mul then a = 0.4 end gfx.BeginPath() gfx.ImageRect(x, y, tw, th, images[digit + 1], a, 0) x = x - (tw * kern) end end function getCorrectedIndex(from, offset) 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 index = from + offset; if index < 1 then index = total + (from+offset) -- this only happens if the offset is negative end; if index > total then indexesUntilEnd = total - from; index = offset - indexesUntilEnd -- this only happens if the offset is positive end; return index; end; 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 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 function drawLaserAnim() gfx.BeginPath() gfx.Scissor(0, transitionLaserY, desw, 100); gfx.ImageRect(0, 0, desw, desh, laserAnimBaseImage, 1, 0) gfx.ResetScissor(); end function drawBackground(deltaTime) Background.draw(deltaTime) -- If the score for song exists local song = songwheel.songs[selectedIndex]; local diff = song and song.difficulties[selectedDifficulty] or false; 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(); gfx.BeginPath(); end gfx.ImageRect(0, 0, desw, desh, dataPanelImage, 1, 0) drawLaserAnim() if song and diff 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 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 = GetDisplayDifficulty(selectedSongDifficulty.jacketPath, selectedSongDifficulty.difficulty) gfx.ImageRect(songX, y+95, 83, 74, difficultyLabelImages[diffIndex], 1, 0) -- Draw the difficulty level number gfx.BeginPath() 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.id]) then gfx.BeginPath(); gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0); end end function drawSongList() local numOfSongsAround = 7; -- How many songs should be up and how many should be down of the selected one local yOffset = transitionLeaveOffsetY + 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; end local scoreNumbers = load_number_image("score_num"); function drawData() -- Draws the song data on the left panel 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.id]) then gfx.BeginPath() gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0) end gfx.Save() -- Draw best score gfx.BeginPath() local scoreNumber = 0; if bestScore then scoreNumber = bestScore.score end draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12) 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 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.BeginPath(); gfx.FontSize(24) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.Save() 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.FontSize(28) gfx.GlobalAlpha(transitionAfterscrollTextSongTitle); gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955); -- Draw artist gfx.GlobalAlpha(transitionAfterscrollTextSongArtist); gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997); gfx.GlobalAlpha(1); -- Draw difficulties local DIFF_X_START = 98.5 local DIFF_GAP = 114.8; 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 draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1) local diffLabelImage = difficultyLabelUnderImages[ 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.GlobalAlpha(1); -- Scoreboard gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.FontSize(32) local scoreBoardX = 75; local scoreBoardY = 1250; local sbBarWidth = 336*1.2; local sbBarHeight = 33; local sbBarContentLeftX = scoreBoardX + sbBarWidth/2 - 30; 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("TOP", sbBarContentRightX, scoreBoardY + sbBarHeight/2); for i = 1, 5, 1 do gfx.BeginPath(); gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0); gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.BeginPath(); gfx.Text(game.GetSkinSetting("username"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.BeginPath(); gfx.Text((diff.scores[i]) and diff.scores[i].score or "00000000", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight); end gfx.FontSize(22) gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha); gfx.Text(diff.effector, 270, 1180); -- effected by gfx.Text(diff.illustrator, 270, 1210); -- illustrated by gfx.GlobalAlpha(1); end function drawFilterInfo(deltatime) 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 function drawSearch() if (not songwheel.searchInputActive) then return; end gfx.BeginPath(); local tw, th = gfx.ImageSize(searchBgImage) gfx.ImageRect(desw-tw/2, 0, tw/2, th/2, searchBgImage, 1, 0) gfx.FontSize(28); gfx.Text(songwheel.searchText, desw-200, 30); end 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 -- 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 local songBpm = 120; if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then songBpm = songwheel.songs[selectedIndex].bpm; -- Is a variable BPM if (type(songBpm) == "string") then local s = split(songBpm, '-'); songBpm = tonumber(s[1]); -- Lowest bpm value end end -- If the original songBpm is "2021.04.01" for example, the above code can produce `nil` in the songBpm -- since it cannot parse the number out of that string. Here we implement a fallback, to not crash -- USC on whacky charts. Whacky charters, quit using batshit insane bpm values. It makes me angery >:( if (songBpm == nil) then songBpm = 120; 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 (transitionLeaveReverse) then if transitionLeaveScale > 0 then transitionLeaveScale = transitionLeaveScale - deltaTime / 0.5 -- transition should last for that time in seconds else transitionLeaveScale = 0 end else if transitionLeaveScale < 1 then transitionLeaveScale = transitionLeaveScale + deltaTime / 0.5 -- transition should last for that time in seconds else transitionLeaveScale = 1 end end transitionLeaveOffsetY = Easing.inOutQuad(transitionLeaveScale) * (desh+songPlateHeight*2+145); end render = function (deltaTime) resetLayoutInformation(); tickTransitions(deltaTime); gfx.Scale(scale, scale); if not difficultyNumbers then difficultyNumbers = load_number_image('diff_num') end drawBackground(deltaTime); drawSongList() drawData() drawFilterInfo(deltaTime) drawSearch(); if (game.GetSkinSetting('_songWheelOverlayActive') ~= 1) then transitionLeaveReverse = true; else transitionLeaveReverse = false; end gfx.BeginPath(); gfx.FontSize(18) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) local debugScrollingUp= "FALSE" if scrollingUp then debugScrollingUp = "TRUE" end; -- gfx.Text('S_I: ' .. selectedIndex .. ' // S_D: ' .. selectedDifficulty .. ' // S_UP: ' .. debugScrollingUp .. ' // AC_TS: ' .. transitionAfterscrollScale, 8, 8); end songs_changed = function (withAll) 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] diff.force = VolforceCalc.calc(diff) table.insert(diffs, diff) end end table.sort(diffs, function (l, r) return l.force > r.force end) totalForce = 0 for i = 1, 50 do if diffs[i] then top50diffs[diffs[i].id] = true; totalForce = totalForce + diffs[i].force end end game.SetSkinSetting('_volforce', totalForce) end set_index = function(newIndex) transitionScrollScale = 0; transitionAfterscrollScale = 0; transitionJacketBgScrollScale = 0; 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; game.PlaySample('song_wheel/cursor_change.wav'); selectedIndex = newIndex; end; 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 selectedDifficulty = newDiff; end;