local DiffRectangle = require("components.diff_rectangle") --Horizontal alignment TEXT_ALIGN_LEFT = 1 TEXT_ALIGN_CENTER = 2 TEXT_ALIGN_RIGHT = 4 --Vertical alignment TEXT_ALIGN_TOP = 8 TEXT_ALIGN_MIDDLE = 16 TEXT_ALIGN_BOTTOM = 32 TEXT_ALIGN_BASELINE = 64 local jacket = nil; local selectedIndex = 1 local selectedDiff = 1 local songCache = {} local ioffset = 0 local doffset = 0 local soffset = 0 local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}} local timer = 0 local scrollmul = 0 local scrollmulOffset = 0 -- bc we have min/max the game doesn't know we have to account for extra local effector = 0 local searchText = gfx.CreateLabel("",5,0) local searchIndex = 1 local backgroundImage = gfx.CreateSkinImage("challenge_select/bg.png", 0) local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) local showGuide = game.GetSkinSetting("show_guide") local legendTable = { {["labelSingleLine"] = gfx.CreateLabel("SCROLL INFO",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SCROLL\nINFO",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)}, {["labelSingleLine"] = gfx.CreateLabel("CHALL SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("CHALLENGE\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)}, {["labelSingleLine"] = gfx.CreateLabel("FILTER CHALLS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nCHALLENGES",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)}, {["labelSingleLine"] = gfx.CreateLabel("SORT CHALLS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nCHALLENGES",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)}, {["labelSingleLine"] = gfx.CreateLabel("GAME SETTINGS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("GAME\nSETTINGS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)}, {["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)} } local grades = { {["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)}, {["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)}, {["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)}, {["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)}, {["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)}, {["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)}, {["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)}, {["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)}, {["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)}, {["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)} } local badges = { 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), } gfx.LoadSkinFont("dfmarugoth.ttf"); game.LoadSkinSample("menu_click") game.LoadSkinSample("click-02") game.LoadSkinSample("woosh") local wheelSize = 12 get_page_size = function() return math.floor(wheelSize/2) end -- Responsive UI variables -- Aspect Ratios local aspectFloat = 1.850 local aspectRatio = "widescreen" local landscapeWidescreenRatio = 1.850 local landscapeStandardRatio = 1.500 local portraitWidescreenRatio = 0.5 -- Responsive sizes local fifthX = 0 local fourthX= 0 local thirdX = 0 local halfX = 0 local fullX = 0 local fifthY = 0 local fourthY= 0 local thirdY = 0 local halfY = 0 local fullY = 0 adjustScreen = function(x,y) local a = x/y; fifthX = x/5 fourthX= x/4 thirdX = x/3 halfX = x/2 fullX = x fifthY = y/5 fourthY= y/4 thirdY = y/3 halfY = y/2 fullY = y end check_or_create_cache = function(song, full) if not songCache[song.id] then songCache[song.id] = {} end if not songCache[song.id]["title"] then songCache[song.id]["title"] = gfx.CreateLabel(song.title, 30, 0) end if not songCache[song.id]["chart_names"] then local names = "Charts:" for _, chart in ipairs(song.charts) do names = names .. " [" .. chart.title .. "]" end if song.missing_chart then names = names .. " *COULD NOT FIND ALL CHARTS!*" end songCache[song.id]["chart_names"] = gfx.CreateLabel(names, 20, 0) end if song.topBadge ~= 0 and ( not songCache[song.id]["percent"] or not songCache[song.id]["percent_value"] or songCache[song.id]["percent_value"] ~= song.bestScore) then songCache[song.id]["percent"] = gfx.CreateLabel(string.format("%u%%", math.max(0,(song.bestScore - 8000000)//10000)), 35, 0) songCache[song.id]["percent_value"] = song.bestScore end if full then if not songCache[song.id]["jackets"] then local jackets = {} for i, chart in ipairs(song.charts) do jackets[i] = gfx.LoadImageJob(chart.jacketPath, jacketFallback, 200,200) end songCache[song.id]["jackets"] = jackets end if not songCache[song.id]["desc"] then local desc = "\n" for _, chart in ipairs(song.charts) do desc = desc .. ""..chart.title.."\n\n" end if song.missing_chart then desc = desc .. "*COULD NOT FIND ALL CHARTS!*\n\n" end desc = desc .. "" songCache[song.id]["desc"] = gfx.CreateLabel(desc, 20,0) end end end draw_scores = function(difficulty, x, y, w, h) -- draw the top score for this difficulty local xOffset = 5 local height = h/3 - 10 local ySpacing = h/3 local yOffset = h/3 gfx.FontSize(30); gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER); gfx.FastText("HIGH SCORE", x +(w/2), y+(h/2)) gfx.BeginPath() gfx.Rect(x+xOffset,y+h/2,w-(xOffset*2),h/2) gfx.FillColor(30,30,30,10) gfx.StrokeColor(0,128,255) gfx.StrokeWidth(1) gfx.Fill() gfx.Stroke() if difficulty.scores[1] ~= nil then local highScore = difficulty.scores[1] scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0) if difficulty.topBadge ~= 0 then gfx.BeginPath() gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0) end gfx.FillColor(255,255,255) gfx.FontSize(40); gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER); gfx.DrawLabel(scoreLabel, x+(w/2),y+(h/4)*3,w) end end draw_song = function(song, x, y, w, h, selected) check_or_create_cache(song) gfx.BeginPath() -- gfx.RoundedRectVarying(x,y, w, h,0,0,0,40) gfx.FillColor(30,30,30) gfx.StrokeColor(0,128,255) gfx.StrokeWidth(1) if selected then gfx.StrokeColor(255,128,0) gfx.StrokeWidth(2) end gfx.Fill() gfx.Stroke() gfx.FillColor(255,255,255) gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) -- gfx.DrawLabel(songCache[song.id]["title"], x+10, y + 5, w-10) if (song.missing_chart) then gfx.FillColor(255,20,20) end -- gfx.DrawLabel(songCache[song.id]["chart_names"], x+20, y + 50, w-10) --gfx.DrawLabel(songCache[song.id]["artist"], x+20, y + 50, w-10) gfx.ForceRender() end draw_diff_icon = function(diff, x, y, w, h, selected, deltaTime) local shrinkX = w/4 local shrinkY = h/4 if selected then gfx.FontSize(h/2) shrinkX = w/6 shrinkY = h/6 else gfx.FontSize(math.floor(h / 3)) end gfx.BeginPath() gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0) gfx.FillColor(15,15,15) gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1])) gfx.StrokeWidth(2) gfx.Fill() gfx.Stroke() gfx.FillColor(255,255,255) gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2)) -- gfx.BeginPath() -- DiffRectangle.render(deltaTime, 183, 2.5, 10, songCache[song.id][selectedDiff],tostring(diff.level)); end draw_diffs = function(diffs, x, y, w, h) local diffWidth = w/2.5 local diffHeight = w/2.5 local diffCount = #diffs gfx.Scissor(x,y,w,h) for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do local diff = diffs[i] local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) if i ~= selectedDiff then draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) end end --after selected for i = math.min(selectedDiff + 2, diffCount), selectedDiff + 1,-1 do local diff = diffs[i] local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth)) if i ~= selectedDiff then draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false) end end local diff = diffs[selectedDiff] local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth)) draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true) gfx.BeginPath() gfx.FillColor(0,128,255) gfx.Rect(x,y+10,2,diffHeight-h/6) gfx.Fill() gfx.BeginPath() gfx.Rect(x+w-2,y+10,2,diffHeight-h/6) gfx.Fill() gfx.ResetScissor() draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5) end draw_selected = function(song, x, y, w, h) check_or_create_cache(song, true) -- set up padding and margins local xPadding = math.floor(w/16) local yPadding = math.floor(h/32) local xMargin = math.floor(w/16) local yMargin = math.floor(h/32) local width = (w-(xMargin*2)) local height = (h-(yMargin*2)) local xpos = x+xMargin local ypos = y+yMargin --Border -- local diff = song.difficulties[selectedDiff] -- gfx.BeginPath() -- gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding) -- gfx.FillColor(30,30,30,100) -- gfx.StrokeColor(0,128,255) -- gfx.StrokeWidth(1) -- gfx.Fill() -- gfx.Stroke() -- jacket should take up 1/3 of height, always be square, and be centered local imageSize = math.floor((height/2)*2)-10 local imageXPos = x+xMargin+xPadding local square_size = math.ceil(math.sqrt(#song.charts)) local origImageSize = imageSize imageSize = math.floor(imageSize / square_size) local bottom_off = math.ceil((square_size - #song.charts)) local img_row = 0; local img_col = 0; for i, chart in ipairs(song.charts) do if songCache[song.id]["jackets"][i] == jacketFallback then songCache[song.id]["jackets"][i] = gfx.LoadImageJob(chart.jacketPath, jacketFallback, 200,200) end gfx.BeginPath() local xoff = img_col * imageSize if math.ceil(i / square_size) == square_size then xoff = xoff end gfx.ImageRect(62.52,888.6+ (img_row/200 * imageSize), imageSize*1.19, imageSize*1.19, songCache[song.id]["jackets"][i], 1, 0) img_col = img_col + 1 if img_col >= square_size then img_row = img_row + 1 img_col = 0 end end local num_img_rows = img_row + 1 if img_col == 0 then num_img_rows = img_row end if songCache[song.id][selectedDiff] then gfx.BeginPath() --gfx.ImageRect(imageXPos, y+yMargin+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0) end local gradeImg = nil for i,v in ipairs(grades) do if v.max > song.bestScore then gfx.BeginPath() gradeImg = v.image break end end if scrollmul < 0 then scrollmulOffset = scrollmulOffset - scrollmul scrollmul = 0 end local descw, desch = gfx.LabelSize(songCache[song.id]["desc"]) local boxh, boxw = 0 local starty, startx = 0 gfx.FontSize(40) gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_MIDDLE) starty = y+yMargin+yPadding startx = xpos+xPadding+origImageSize+10 gfx.DrawLabel(songCache[song.id]["title"], 295, 845, width-origImageSize-30) gfx.FontSize(30) -- Scroll box info starty = starty + 50 boxh = height - starty boxw = width-startx-200 --gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 45, width-imageSize-20) --gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 45, width-imageSize-20) --gfx.FontSize(20) --gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 85, width-imageSize-20) --gfx.FastText(string.format("Effector: %s", diff.effector), xpos+xPadding+imageSize+3, y+yMargin+yPadding + 115) if song.topBadge ~= 0 then gfx.BeginPath() gfx.ImageRect(978, 1026.5, 50, 50, badges[song.topBadge], 1, 0) local iar = 0 if gradeImg ~= nil then gfx.BeginPath() local iw,ih = gfx.ImageSize(gradeImg) iar = iw/ih gfx.ImageRect(961-iar*50, 1026.5, iar * 50, 50, gradeImg, 1, 0) end gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.DrawLabel(songCache[song.id]["percent"], 595, 1045, 200) end -- Render desc scrolling box do -- gfx.BeginPath() -- gfx.Rect(startx, starty, boxw, boxh) -- gfx.FillColor(50,50,50) -- gfx.Fill() local overBottom = desch - 100*scrollmul - boxh if overBottom < 0 and scrollmul > 0 then local scrollend = (desch - boxh)/100 if scrollend < 0 then scrollend = 0 end scrollmulOffset = scrollmulOffset - (scrollmul - scrollend) scrollmul = scrollend end -- Draw scroll bar if desch > boxh then gfx.BeginPath() local barStart = (100*scrollmul) / desch -- Start percent of visible desc local barh = (boxh / desch) * boxh gfx.Rect(startx + boxw - 5, starty + (barStart*boxh)//1, 5, barh//1) gfx.FillColor(20,20,20) gfx.Fill() end gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE ) gfx.DrawLabel(songCache[song.id]["desc"], 600, 942) end --if aspectRatio == "PortraitWidescreen" then -- draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding) --else -- draw_scores(diff, xpos, (height/6)*5, width, (height/6)) --end gfx.ForceRender() end draw_songwheel = function(x,y,w,h) local offsetX = fifthX/2 local width = math.floor((w/5)*4) local height = math.floor((h/wheelSize)*1.5) for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do local song = chalwheel.challenges[i] local xpos = x + offsetX + ((selectedIndex - i + ioffset) ^ 2) * 3 local offsetY = (selectedIndex - i + ioffset) * ( height - (wheelSize/2*((selectedIndex-i + ioffset)*aspectFloat))) local ypos = y+((h/2 - height/2) - offsetY) draw_song(song, xpos, ypos, width, height) end --after selected for i = math.min(selectedIndex + wheelSize/2, #chalwheel.challenges), selectedIndex + 1,-1 do local song = chalwheel.challenges[i] local xpos = x + offsetX + ((i - selectedIndex - ioffset) ^ 2) * 2 local offsetY = (selectedIndex - i + ioffset) * ( height - (wheelSize/2*((i-selectedIndex - ioffset)*aspectFloat))) local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY) local alpha = 255 - (selectedIndex - i + ioffset) * 31 draw_song(song, xpos, ypos, width, height) end -- draw selected local xpos = x + offsetX/1.2 + ((-ioffset) ^ 2) * 2 local offsetY = (ioffset) * ( height - (wheelSize/2*((1)*aspectFloat))) local ypos = y+((h/2 - height/2) - (ioffset) - offsetY) draw_song(chalwheel.challenges[selectedIndex], xpos, ypos, width, height, true) return chalwheel.challenges[selectedIndex] end draw_legend_pane = function(x,y,w,h,obj) local xpos = x+5 local ypos = y local imageSize = h gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT) gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0) xpos = xpos + imageSize + 5 gfx.FontSize(16); if h < (w-(10+imageSize))/2 then gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize)) else gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize)) end gfx.ForceRender() end draw_legend = function(x,y,w,h) gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT); gfx.BeginPath() gfx.FillColor(0,0,0,170) gfx.Rect(x,y,w,h) gfx.Fill() local xpos = 10; local legendWidth = math.floor((w-20)/#legendTable) for i,v in ipairs(legendTable) do local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i]) end end draw_search = function(x,y,w,h) soffset = soffset + (searchIndex) - (chalwheel.searchInputActive and 0 or 1) if searchIndex ~= (chalwheel.searchInputActive and 0 or 1) then game.PlaySample("woosh") end searchIndex = chalwheel.searchInputActive and 0 or 1 gfx.BeginPath() local bgfade = 1 - (searchIndex + soffset) --if not chalwheel.searchInputActive then bgfade = soffset end gfx.FillColor(0,0,0,math.floor(200 * bgfade)) gfx.Rect(0,0,resx,resy) gfx.Fill() gfx.ForceRender() local xpos = x + (searchIndex + soffset)*w gfx.UpdateLabel(searchText ,string.format("Search: %s",chalwheel.searchText), 30, 0) gfx.BeginPath() gfx.RoundedRect(xpos,y,w,h,h/2) gfx.FillColor(30,30,30) gfx.StrokeColor(0,128,255) gfx.StrokeWidth(1) gfx.Fill() gfx.Stroke() gfx.BeginPath(); gfx.LoadSkinFont("dfmarugoth.ttf"); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20) end render = function(deltaTime) timer = (timer + deltaTime) timer = timer % 2 resx,resy = game.GetResolution(); gfx.BeginPath() gfx.ImageRect(0, 0, resx, resy, backgroundImage, 1, 0) adjustScreen(resx,resy); gfx.BeginPath(); gfx.LoadSkinFont("dfmarugoth.ttf"); gfx.FontSize(40); gfx.FillColor(255,255,255); if chalwheel.challenges == nil then elseif chalwheel.challenges[1] ~= nil then --draw chalwheel and get selected song local song = draw_songwheel(0,0,fullX,fullY) --render selected song information draw_selected(song, 0,0,fullX,fifthY) end --Draw Legend Information if showGuide then draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1) end --draw text search draw_search(fifthX*2,5,fifthX*3,fifthY/5) ioffset = ioffset * 0.9 doffset = doffset * 0.9 soffset = soffset * 0.8 if chalwheel.searchStatus then gfx.BeginPath() gfx.FillColor(255,255,255) gfx.FontSize(20); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.Text(chalwheel.searchStatus, 3, 3) end gfx.LoadSkinFont("dfmarugoth.ttf"); gfx.ResetTransform() gfx.ForceRender() end set_index = function(newIndex, scrollamt) if newIndex ~= selectedIndex then game.PlaySample("menu_click") scrollmulOffset = 0 end ioffset = ioffset + selectedIndex - newIndex selectedIndex = newIndex scrollmul = scrollamt + scrollmulOffset end; local badgeRates = { 0.5, -- Played 1.0, -- Cleared 1.02, -- Hard clear 1.04, -- UC 1.1 -- PUC } local gradeRates = { {["min"] = 9900000, ["rate"] = 1.05}, -- S {["min"] = 9800000, ["rate"] = 1.02}, -- AAA+ {["min"] = 9700000, ["rate"] = 1}, -- AAA {["min"] = 9500000, ["rate"] = 0.97}, -- AA+ {["min"] = 9300000, ["rate"] = 0.94}, -- AA {["min"] = 9000000, ["rate"] = 0.91}, -- A+ {["min"] = 8700000, ["rate"] = 0.88}, -- A {["min"] = 7500000, ["rate"] = 0.85}, -- B {["min"] = 6500000, ["rate"] = 0.82}, -- C {["min"] = 0, ["rate"] = 0.8} -- D } challenges_changed = function(withAll) if not withAll then return end end