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 scrollbarBgImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1) local scrollbarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.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 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 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 transitionAfterscrollJacketBgAlpha = 0; local transitionLaserScale = 0; local transitionLaserY = 0; 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(0+transitionAfterscrollScale*-300, 0, 900, 900, jacketImage or defaultJacketImage, transitionAfterscrollJacketBgAlpha, 0) gfx.BeginPath(); gfx.FillColor(0,0,0,math.floor(transitionAfterscrollJacketBgAlpha*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 gfx.BeginPath() gfx.ImageRect(songX+282, y+44, 79, 69, badgeImage, 1, 0) -- Draw grade local gradeImage = gradeImages.none; if bestScore then gradeImage = getGradeImageForScore(bestScore.score) end gfx.BeginPath(); gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, 1, 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 function drawCursor() 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 function drawScrollbar() gfx.BeginPath() local bgW = 13*0.85; local bgH = 1282*0.85; local scrollPosX = desw-20 local scrollPosY = desh/2-bgH/2 gfx.ImageRect(scrollPosX, scrollPosY, bgW, bgH, scrollbarBgImage, 1, 0) gfx.BeginPath() local fillW = 27*0.85 local fillH = 65*0.85 local fillPosOffsetY = (bgH-fillH)*( (selectedIndex-1) / math.max(1,#songwheel.songs-1) ) gfx.ImageRect(scrollPosX-6, scrollPosY+fillPosOffsetY, fillW, fillH, scrollbarFillImage, 1, 0) 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() if bestScore then -- Draw best score gfx.BeginPath() draw_number(100, 793, 1.0, math.floor(bestScore.score / 10000), 4, scoreNumbers, true, 0.3, 1.12) draw_number(253, 798, 1.0, bestScore.score, 4, scoreNumbers, true, 0.22, 1.12) -- Draw grade local gradeImage = gradeImages.none; if bestScore then gradeImage = getGradeImageForScore(bestScore.score) end gfx.BeginPath(); gfx.ImageRect(360, 773, 45, 45, gradeImage, transitionAfterscrollGradeAlpha, 0); -- Draw badge badgeImage = badgeImages[diff.topBadge+1]; gfx.BeginPath() gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, transitionAfterscrollBadgeAlpha, 0) end 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); gfx.FontSize(22) gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha); gfx.Text(diff.effector, 270, 1180); gfx.Text(diff.illustrator, 270, 1210); 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 background alpha if transitionAfterscrollScale < 0.05 or transitionAfterscrollScale >= 1 then transitionAfterscrollJacketBgAlpha = 0; elseif transitionAfterscrollScale >= 0.05 and transitionAfterscrollScale < 0.1 then transitionAfterscrollJacketBgAlpha = math.min(1, (transitionAfterscrollScale-0.05) / 0.05); elseif transitionAfterscrollScale >= 0.95 and transitionAfterscrollScale < 1 then transitionAfterscrollJacketBgAlpha = math.min(1, 1-((transitionAfterscrollScale-0.95) / 0.05)); else transitionAfterscrollJacketBgAlpha = 1; end -- 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); -- 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() drawCursor() drawScrollbar() 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 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; 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;