local Easing = require('common.easings'); local Bars = require('components.bars'); local backgroundImage = gfx.CreateSkinImage("song_select/bg.png", 1) 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 cursorImage = gfx.CreateSkinImage("song_select/cursor.png", 1) local diffCursorImage = gfx.CreateSkinImage("song_select/level_cursor.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), } -- ANIMS local laserAnimation = gfx.LoadSkinAnimation('song_select/laser_anim', 1/60, 0, false); game.LoadSkinSample('song_wheel/cursor_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 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; 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; 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[1].jacketPath, defaultJacketImage, 0, 0); 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 drawBackground(deltaTime) gfx.BeginPath() gfx.ImageRect(0, 0, desw, desh, backgroundImage, 1, 0) -- If the score for song exists local song = songwheel.songs[selectedIndex]; local diff = song.difficulties[selectedDifficulty]; local bestScore = diff.scores[1]; 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) 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 local laserAnimTickRes = gfx.TickAnimation(laserAnimation, deltaTime); if laserAnimTickRes == 1 then gfx.BeginPath() gfx.ImageRect(0, 0, desw, desh, laserAnimation, 1, 0); gfx.GlobalAlpha(1); end end function drawSong(song, y) if (not song) then return end; local songX = desw/2+28 local selectedSongDifficulty = song.difficulties[selectedDifficulty] 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() gfx.ImageRect(songX, y+95, 83, 74, difficultyLabelImages[selectedSongDifficulty.difficulty+1], 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 bestScore then badgeImage = badgeImages[bestScore.badge+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+390, y+47, 60, 60, gradeImage, 1, 0); end function drawSongList() local numOfSongsAround = 7; -- How many songs should be up and how many should be down of the selected one local i=1; while (i <= numOfSongsAround) do local songIndex = getCorrectedIndex(selectedIndex, -i) drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2-songPlateHeight*i + transitionScrollOffsetY) i=i+1; end; -- Draw the selected song drawSong(songwheel.songs[selectedIndex], desh/2-songPlateHeight/2 + transitionScrollOffsetY) i=1; while (i <= numOfSongsAround) do local songIndex = getCorrectedIndex(selectedIndex, i) drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2+songPlateHeight*i + transitionScrollOffsetY) i=i+1; end; end function drawCursor() gfx.BeginPath() gfx.ImageRect(desw/2-14, desh/2-213/2, 555, 213, cursorImage, 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.difficulties[selectedDifficulty]; local bestScore = diff.scores[1]; local jacketImage = getJacketImage(song); gfx.BeginPath() gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0) 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(245, 797, 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[bestScore.badge+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 index, diff in ipairs(song.difficulties) do gfx.BeginPath() if index == 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[diff.difficulty+1]; 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 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 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() drawData() drawSearch(); Bars.draw(); 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 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) selectedDifficulty = newDiff; end;