-- TODO: move util functions to common.lua local charts = {} local passed = false local desw = 770 local desh = 800 local moveX = 0 local moveY = 0 local currResX = 0 local currResY = 0 local scale = 1 local gradeImg = nil; local badgeImg = nil; local gradeAR = 1 --grade aspect ratio local chartDuration = 0 local chartDurationText = "0m 00s" local badgeImages = { gfx.CreateSkinImage("badges/played.png", 0), gfx.CreateSkinImage("badges/clear.png", 0), gfx.CreateSkinImage("badges/hard-clear.png", 0), gfx.CreateSkinImage("badges/full-combo.png", 0), gfx.CreateSkinImage("badges/perfect.png", 0) } local laneNames = {"A", "B", "C", "D", "L", "R"} local diffNames = {"NOV", "ADV", "EXH", "INF"} local backgroundImage = gfx.CreateSkinImage("bg.png", 1); game.LoadSkinSample("applause") local played = false local shotTimer = 0; local shotPath = ""; game.LoadSkinSample("shutter") local hitWindowPerfect = 46 local hitWindowGood = 92 local clearTextBase = "" -- Used to determind the type of clear local clearText = "" local currTime = 0 local critText = "CRIT" local nearText = "NEAR" local hitDeltaScale = game.GetSkinSetting("hit_graph_delta_scale") local showGuide = game.GetSkinSetting("show_result_guide") local showIcons = game.GetSkinSetting("show_result_icons") --local showStatsHit = game.GetSkinSetting("show_detailed_results") local showStatsHit = true local showChartInfo = 0 local scroll = 0.0 local scrolloff = 0.0 function waveParam(period, offset) local t = currTime if offset then t = t+offset end t = t / period return 0.5 + 0.5*math.cos(t * math.pi * 2) end function getTextScale(txt, max_width) local x1, y1, x2, y2 = gfx.TextBounds(0, 0, txt) if x2 < max_width then return 1 else return max_width / x2 end end function drawScaledText(txt, x, y, max_width) local text_scale = getTextScale(txt, max_width) if text_scale == 1 then gfx.BeginPath() gfx.Text(txt, x, y) return end gfx.Save() gfx.Translate(x, y) gfx.Scale(text_scale, 1) gfx.BeginPath() gfx.Text(txt, 0, 0) gfx.Restore() end function drawLine(x1,y1,x2,y2,w,r,g,b) gfx.BeginPath() gfx.MoveTo(x1,y1) gfx.LineTo(x2,y2) gfx.StrokeColor(r,g,b) gfx.StrokeWidth(w) gfx.Stroke() end function getScoreBadgeDesc(s) if s.badge == 1 then if s.flags & 1 ~= 0 then return "crash" else return string.format("%.1f%%", s.gauge * 100) end elseif 2 <= s.badge and s.badge <= 4 and s.misses < 10 then return string.format("%d-%d", s.goods, s.misses) end return "" end result_set = function() currentAdded = false passed = result.passed for i,chart in ipairs(result.charts) do chart.index = i chart.chartTitle = string.format("#%u %s", i, chart.title) if chart.duration ~= nil then chart.chartDuration = chart.duration chart.chartDurationText = string.format("%dm %02d.%01ds", chart.chartDuration // 60000, (chart.chartDuration // 1000) % 60, (chart.chartDuration // 100) % 10) hitGraphHoverScale = math.max(chart.chartDuration / 10000, 5) else chartDuration = 0 chartDurationText = "" hitGraphHoverScale = 10 end chart.rawHighScores = chart.highScores chart.highScores = { } chart.highestScore = 0 for i,s in ipairs(chart.rawHighScores) do newScore = { } if currentAdded == false and chart.score > s.score then newScore.score = string.format("%08d", chart.score) newScore.badge = chart.badge newScore.badgeDesc = getScoreBadgeDesc(chart) newScore.color = {255, 127, 0} newScore.subtext = "Now" newScore.xoff = 0 table.insert(chart.highScores, newScore) newScore = { } currentAdded = true end newScore.score = string.format("%08d", s.score) newScore.badge = s.badge newScore.badgeDesc = getScoreBadgeDesc(s) newScore.color = {0, 127, 255} newScore.xoff = 0 if s.timestamp > 0 then newScore.subtext = os.date("%Y-%m-%d %H:%M:%S", s.timestamp) else newScore.subtext = "" end if chart.highestScore < s.score then chart.highestScore = s.score end table.insert(chart.highScores, newScore) end if currentAdded == false then newScore = { } newScore.score = string.format("%08d", chart.score) newScore.badge = chart.badge newScore.badgeDesc = getScoreBadgeDesc(chart) newScore.color = {255, 127, 0} newScore.subtext = "Now" newScore.xoff = 0 table.insert(chart.highScores, newScore) newScore = { } currentAdded = true end chart.scoreString = string.format("%08d", chart.score) chart.badgeDesc = getScoreBadgeDesc(chart) if chart.jacketPath ~= nil and chart.jacketPath ~= "" then chart.jacketImg = gfx.CreateImage(chart.jacketPath, 0) end chart.gradeAR = 1 chart.gradeImg = gfx.CreateSkinImage(string.format("score/%s.png", chart.grade), 0) if chart.gradeImg ~= nil then local gradew, gradeh = gfx.ImageSize(chart.gradeImg) chart.gradeAR = gradew / gradeh end if 1 <= chart.badge and chart.badge <= 5 then chart.badgeImg = badgeImages[chart.badge] else chart.badgeImg = nil end if chart.passed then chart.clearTextBase = "CHALLENGE PASS" else chart.clearTextBase = "CHALLENGE FAIL" end if chart.badge == 2 then chart.clearTextBase = chart.clearTextBase .. " - CLEAR" elseif chart.badge == 3 then chart.clearTextBase = chart.clearTextBase .. " - HARD CLEAR" elseif chart.badge == 4 then chart.clearTextBase = chart.clearTextBase .. " - FULL COMBO" elseif chart.badge == 5 then chart.clearTextBase = chart.clearTextBase .. " - PERFECT" end if chart.playbackSpeed ~= nil and chart.playbackSpeed ~= 1.00 then if chart.clearTextBase == "" then chart.clearText = string.format("x%.2f play", chart.playbackSpeed) else chart.clearText = string.format("%s (x%.2f play)", chart.clearTextBase, chart.playbackSpeed) end else chart.clearText = chart.clearTextBase end if chart.speedModType ~= nil then if chart.speedModType == 0 then chart.speedMod = "XMOD" chart.speedModValue = string.format("%.2f", chart.speedModValue) elseif chart.speedModType == 1 then chart.speedMod = "MMOD" chart.speedModValue = string.format("%.1f", chart.speedModValue) elseif chart.speedModType == 2 then chart.speedMod = "CMOD" chart.speedModValue = string.format("%.1f", chart.speedModValue) else chart.speedMod = "" chart.speedModValue = "" end else chart.speedMod = "" chart.speedModValue = "" end chart.hasHitStat = chart.noteHitStats ~= nil and #(chart.noteHitStats) > 0 chart.hitWindowPerfect = 46 chart.hitWindowGood = 92 if chart.hitWindow ~= nil then chart.hitWindowPerfect = chart.hitWindow.perfect chart.hitWindowGood = chart.hitWindow.good end chart.hitHistogram = {} chart.hitMinDelta = 0 chart.hitMaxDelta = 0 if chart.hasHitStat then for i = 1, #chart.noteHitStats do local hitStat = chart.noteHitStats[i] if hitStat.rating == 1 or hitStat.rating == 2 then if chart.hitHistogram[hitStat.delta] == nil then chart.hitHistogram[hitStat.delta] = 0 end chart.hitHistogram[hitStat.delta] = chart.hitHistogram[hitStat.delta] + 1 if hitStat.delta < chart.hitMinDelta then chart.hitMinDelta = hitStat.delta end if hitStat.delta > chart.hitMaxDelta then chart.hitMaxDelta = hitStat.delta end end end end charts[i] = chart end critText = "CRIT" nearText = "NEAR" end draw_shotnotif = function(x,y) gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.Save() gfx.Translate(x,y) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.BeginPath() gfx.Rect(0,0,200,40) gfx.FillColor(30,30,30) gfx.StrokeColor(255,128,0) gfx.Fill() gfx.Stroke() gfx.FillColor(255,255,255) gfx.FontSize(15) gfx.Text("Screenshot saved to:", 3,5) gfx.Text(shotPath, 3,20) gfx.Restore() end --------------------- -- Subcomponents -- --------------------- draw_stat = function(x,y,w,h, name, value, format,r,g,b) gfx.Save() gfx.Translate(x,y) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.FontSize(h) gfx.Text(name .. ":",0, 0) gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) gfx.Text(string.format(format, value),w, 0) gfx.BeginPath() gfx.MoveTo(0,h) gfx.LineTo(w,h) if r then gfx.StrokeColor(r,g,b) else gfx.StrokeColor(200,200,200) end gfx.StrokeWidth(1) gfx.Stroke() gfx.Restore() return y + h + 5 end draw_score = function(score, x, y, w, h, pre) local center = x + w * 0.54 local prefix = "" if pre ~= nil then prefix = pre end gfx.LoadSkinFont("NovaMono.ttf") gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT) gfx.FontSize(h) gfx.Text(string.format("%s%04d", prefix, score // 10000), center-h/70, y) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) gfx.FontSize(h*0.75) gfx.Text(string.format("%04d", score % 10000), center+h/70, y) end draw_highscores = function(chart, full) gfx.FillColor(255,255,255) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.FontSize(30) gfx.Text("High Scores",510,30) gfx.StrokeWidth(1) for i,s in ipairs(chart.highScores) do gfx.Save() gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.BeginPath() local ypos = 45 + (i - 1) * 80 if ypos > desh then break end gfx.Translate(510 + s.xoff, ypos) gfx.RoundedRectVarying(0, 0, 260, 70,0,0,25,0) gfx.FillColor(15,15,15) gfx.StrokeColor(s.color[1], s.color[2], s.color[3]) gfx.Fill() gfx.Stroke() gfx.BeginPath() gfx.FillColor(255,255,255) gfx.FontSize(25) gfx.Text(string.format("#%d",i), 5, 5) if s.badge ~= nil and 1 <= s.badge and s.badge <= 5 then gfx.BeginPath() gfx.ImageRect(37, 7, 36, 36, badgeImages[s.badge], 1, 0) if full then gfx.BeginPath() gfx.FontSize(15) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM) gfx.Text(s.badgeDesc, 55, 52) end end draw_score(s.score, 55, 42, 215, 60) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP) gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.FontSize(20) gfx.Text(s.subtext, 135, 45) gfx.Restore() end end draw_gauge_graph = function(chart, x, y, w, h, alpha, xfocus, xscale) if alpha == nil then alpha = 160 end if xfocus == nil then xfocus = 0 xscale = 1 end local leftIndex = math.floor(#(chart.gaugeSamples)/w * (-xfocus / xscale + xfocus)) leftIndex = math.max(1, math.min(#(chart.gaugeSamples), leftIndex)) gfx.BeginPath() gfx.MoveTo(x, y + h - h * chart.gaugeSamples[leftIndex]) for i = leftIndex+1, #(chart.gaugeSamples) do local gaugeX = i * w / #(chart.gaugeSamples) gaugeX = (gaugeX - xfocus) * xscale + xfocus gfx.LineTo(x + gaugeX,y + h - h * chart.gaugeSamples[i]) if gaugeX > w then break end end gfx.StrokeWidth(2.0) if chart.flags & 1 ~= 0 then gfx.StrokeColor(255,80,0,alpha) gfx.Stroke() else gfx.StrokeColor(0,180,255,alpha) gfx.Scissor(x, y + h * 0.3, w, h * 0.7) gfx.Stroke() gfx.ResetScissor() gfx.Scissor(x,y-10,w,10+h*0.3) gfx.StrokeColor(255,0,255,alpha) gfx.Stroke() gfx.ResetScissor() end end draw_hit_graph_lines = function(chart, x, y, w, h) local maxDispDelta = h/2 / hitDeltaScale gfx.StrokeWidth(1) gfx.BeginPath() gfx.StrokeColor(128, 255, 128, 128) gfx.MoveTo(x, y+h/2) gfx.LineTo(x+w, y+h/2) gfx.Stroke() gfx.BeginPath() gfx.StrokeColor(64, 128, 64, 64) for i = -math.floor(maxDispDelta / 10), math.floor(maxDispDelta / 10) do local lineY = y + h/2 + i*10*hitDeltaScale if i ~= 0 then gfx.MoveTo(x, lineY) gfx.LineTo(x+w, lineY) end end gfx.Stroke() end draw_hit_graph = function(chart, x, y, w, h, xfocus, xscale) if not chart.hasHitStat or hitDeltaScale == 0.0 then return end if xfocus == nil then xfocus = 0 end if xscale == nil then xscale = 1 end draw_hit_graph_lines(chart, x, y, w, h) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.FontSize(12) for i = 1, #(chart.noteHitStats) do local hitStat = chart.noteHitStats[i] local hitStatX = (hitStat.timeFrac*w - xfocus)*xscale + xfocus if 0 <= hitStatX then if hitStatX > w then break end local hitStatY = h/2 + hitStat.delta * hitDeltaScale if hitStatY < 0 then hitStatY = 0 elseif hitStatY > h then hitStatY = h end local hitStatSize = 1 if hitStat.rating == 2 then hitStatSize = 1.25 gfx.FillColor(255, 150, 0, 160) elseif hitStat.rating == 1 then hitStatSize = 1.75 gfx.FillColor(255, 0, 200, 128) elseif hitStat.rating == 0 then hitStatSize = 2 gfx.FillColor(255, 0, 0, 128) end gfx.BeginPath() if xscale > 1 then gfx.Text(laneNames[hitStat.lane + 1], x+hitStatX, y+hitStatY) else gfx.Rect(x+hitStatX-hitStatSize/2, y+hitStatY-hitStatSize/2, hitStatSize, hitStatSize) gfx.Fill() end end end end draw_left_graph = function(chart, x, y, w, h) local mx, my = game.GetMousePos() mx = mx / scale - moveX my = my / scale - moveY local mhit = x <= mx and mx <= x+w and y <= my and my <= y+h local hit_xfocus = 0 local hit_xscale = 1 gfx.BeginPath() gfx.Rect(x, y, w, h) gfx.FillColor(255, 255, 255, 32) gfx.Fill() local chartDurationDisp = string.format("Duration: %s", chart.chartDurationText) if mhit then hit_xfocus = mx - x hit_xscale = hitGraphHoverScale local currPos = chart.chartDuration * ((mx - x) / w) chartDurationDisp = string.format("%dm %02d.%01ds / %s" , currPos // 60000, (currPos // 1000) % 60, (currPos // 100) % 10, chart.chartDurationText) drawLine(mx, y, mx, y+h, 1, 64, 96, 64) end gfx.FontSize(17) gfx.FillColor(64, 128, 64, 96) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.BeginPath() gfx.Text(chartDurationDisp, x+5, y) if chart.bpm ~= nil then gfx.BeginPath() gfx.Text(string.format("BPM: %s", chart.bpm), x+5, y+15) end draw_hit_graph(chart, x, y, w, h, hit_xfocus, hit_xscale) if hit_xscale == 1 then draw_gauge_graph(chart, x, y, w, h) else draw_gauge_graph(chart, x, y, w, h, 64, hit_xfocus, hit_xscale) draw_gauge_graph(chart, x, y, w, h) local gaugeInd = math.floor(1 + #(chart.gaugeSamples)/w * ((mx-x - hit_xfocus) / hit_xscale + hit_xfocus)) gaugeInd = math.max(1, math.min(#(chart.gaugeSamples), gaugeInd)) local gaugeY = h - h * chart.gaugeSamples[gaugeInd] gfx.StrokeColor(255, 0, 0, 196) gfx.FillColor(255, 255, 255, 196) gfx.FontSize(16) gfx.BeginPath() gfx.Circle(mx, y + gaugeY, 2) gfx.Stroke() gfx.BeginPath() gfx.Text(string.format("%.1f%%", chart.gaugeSamples[gaugeInd]*100), mx, y + gaugeY - 10) end gfx.FontSize(16) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) gfx.BeginPath() gfx.FillColor(255, 255, 255, 128) gfx.Text(string.format("Mean: %.1f ms, Median: %d ms", chart.meanHitDelta, chart.medianHitDelta), x+4, y+h) -- End gauge display local endGauge = chart.gauge local endGaugeY = y + h - h * endGauge if endGaugeY > y+h - 10 then endGaugeY = y+h - 10 elseif endGaugeY < y + 10 then endGaugeY = y + 10 end local gaugeText = string.format("%.1f%%", endGauge*100) gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) local x1, y1, x2, y2 = gfx.TextBounds(x+w-6, endGaugeY, gaugeText) gfx.BeginPath() gfx.FillColor(80, 80, 80, 128) gfx.RoundedRect(x1-3, y1, x2-x1+6, y2-y1, 4) gfx.Fill() gfx.BeginPath() gfx.LoadSkinFont("NovaMono.ttf") gfx.FillColor(255, 255, 255) gfx.Text(gaugeText, x+w-6, endGaugeY) end draw_hit_histogram = function(chart, x, y, w, h) if not chart.hasHitStat or hitDeltaScale == 0.0 then return end local maxDispDelta = math.floor(h/2 / hitDeltaScale) local mode = 0 local modeCount = 0 for i = -maxDispDelta-1, maxDispDelta+1 do if chart.hitHistogram[i] == nil then chart.hitHistogram[i] = 0 end end for i = -maxDispDelta, maxDispDelta do local count = chart.hitHistogram[i-1] + chart.hitHistogram[i]*2 + chart.hitHistogram[i+1] if count > modeCount then mode = i modeCount = count end end gfx.StrokeWidth(1.5) gfx.BeginPath() gfx.StrokeColor(255, 255, 128, 96) gfx.MoveTo(x, y) for i = -maxDispDelta, maxDispDelta do local count = chart.hitHistogram[i-1] + chart.hitHistogram[i]*2 + chart.hitHistogram[i+1] gfx.LineTo(x + 0.9 * w * count / modeCount, y+h/2 + i*hitDeltaScale) end gfx.LineTo(x, y+h) gfx.Stroke() end draw_right_graph = function(chart, x, y, w, h) if not chart.hasHitStat or hitDeltaScale == 0.0 then return end gfx.BeginPath() gfx.Rect(x, y, w, h) gfx.FillColor(64, 64, 64, 32) gfx.Fill() draw_hit_graph_lines(chart, x, y, w, h) draw_hit_histogram(chart, x, y, w, h) local meanY = h/2 + hitDeltaScale * chart.meanHitDelta local medianY = h/2 + hitDeltaScale * chart.medianHitDelta drawLine(x, y+meanY, x+w, y+meanY, 1.25, 255, 0, 0, 192) drawLine(x, y+medianY, x+w, y+medianY, 1.25, 64, 64, 255, 192) gfx.LoadSkinFont("NovaMono.ttf") gfx.BeginPath() if meanY < medianY then gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) else gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) end gfx.FillColor(255, 128, 128) gfx.FontSize(16) gfx.Text(string.format("Mean: %.1f ms", chart.meanHitDelta), x+2, y+meanY) gfx.BeginPath() if medianY <= meanY then gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) else gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) end gfx.FillColor(196, 196, 255) gfx.FontSize(16) gfx.Text(string.format("Median: %d ms", chart.medianHitDelta), x+2, y+medianY) gfx.FillColor(255, 255, 255) gfx.FontSize(15) gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.Text(string.format("Earliest: %d ms", chart.hitMinDelta), x+5, y) gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) gfx.Text(string.format("Latest: %d ms", chart.hitMaxDelta), x+5, y+h) end draw_laser_icon = function(chart, x, y, s) gfx.Save() gfx.Translate(x, y) local r, g, b = game.GetLaserColor(0) gfx.BeginPath() gfx.FillColor(r, g, b, 96) gfx.MoveTo(s*0.1, s*0.1) gfx.LineTo(s*0.4, s*0.5) gfx.LineTo(s*0.1, s*0.9) gfx.LineTo(s*0.3, s*0.9) gfx.LineTo(s*0.6, s*0.5) gfx.LineTo(s*0.3, s*0.1) gfx.LineTo(s*0.1, s*0.1) gfx.Fill() local r, g, b = game.GetLaserColor(1) gfx.BeginPath() gfx.FillColor(r, g, b, 96) gfx.MoveTo(s*0.7, s*0.1) gfx.LineTo(s*0.4, s*0.5) gfx.LineTo(s*0.7, s*0.9) gfx.LineTo(s*0.9, s*0.9) gfx.LineTo(s*0.6, s*0.5) gfx.LineTo(s*0.9, s*0.1) gfx.LineTo(s*0.7, s*0.1) gfx.Fill() gfx.Restore() return x - s end draw_speed_icon = function(chart, x, y, s) if chart.speedMod == "" then return x end gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.FillColor(255, 255, 255) gfx.BeginPath() gfx.FontSize(15) gfx.Text(chart.speedMod, x + s/2, y + s*0.3) gfx.BeginPath() gfx.FontSize(20) gfx.Text(chart.speedModValue, x + s/2, y + s*0.65) return x - s end draw_hidsud_icon = function(chart, x, y, s) if chart.hidsud == nil then return x end gfx.FontSize(15) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.FillColor(255, 255, 255) gfx.BeginPath() gfx.Text("SUDDEN", x + s/2, y + s*0.13) gfx.Text("HIDDEN", x + s/2, y + s*0.62) gfx.BeginPath() gfx.FontSize(13) gfx.Text(string.format("%.2f fd %.1f", chart.hidsud.suddenCutoff, chart.hidsud.suddenFade), x + s/2, y + s*0.35) gfx.Text(string.format("%.2f fd %.1f", chart.hidsud.hiddenCutoff, chart.hidsud.hiddenFade), x + s/2, y + s*0.84) return x - s end draw_mir_ran_icon = function(chart,x, y, s) if chart.flags & 6 == 0 then return x end gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.FillColor(255, 255, 255) if chart.flags & 2 ~= 0 then gfx.BeginPath() gfx.Text("MIR", x + s/2, y + s*0.3) end if chart.flags & 4 ~= 0 then gfx.BeginPath() gfx.Text("RAN", x + s/2, y + s*0.7) end return x - s end --------------------- -- Main components -- --------------------- draw_challenge_info = function(x, y, w, h) local centerLineY = y+h*0.6 gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.BeginPath() gfx.FillColor(255, 255, 255) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(37) drawScaledText(result.title, x+w/2, centerLineY-15, w/2-5) drawLine(x+30, centerLineY, x+w-30,centerLineY, 1, 64, 64, 64) gfx.FontSize(25) if result.passed then gfx.FillColor(150, 255, 150) drawScaledText(string.format("CHALLENGE PASSED: %d%% Percent Complete", result.avgPercentage), x+w/2, centerLineY+25, w/2-5) else gfx.FillColor(255, 20, 20) drawScaledText(string.format("CHALLENGE FAILED: %d%% Percent Complete", result.avgPercentage), x+w/2, centerLineY+25, w/2-5) gfx.FontSize(23) drawScaledText(result.failReason, x+w/2, centerLineY+45, w/2-5) end end draw_title = function(chart,x, y, w, h) local centerLineY = y+h*0.6 gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.BeginPath() gfx.FillColor(255, 255, 255) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(48) drawScaledText(chart.chartTitle, x+w/2, centerLineY-18, w/2-5) drawLine(x+30, centerLineY, x+w-30,centerLineY, 1, 64, 64, 64) gfx.FontSize(27) drawScaledText(chart.artist, x+w/2, centerLineY+28, w/2-5) end draw_challenge_title = function(chart, x, y, w, h) local centerLineY = y+h gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.BeginPath() gfx.FillColor(255, 255, 255) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(27) drawScaledText(chart.chartTitle, x+w/2, centerLineY-10, w/2-5) drawLine(x+30, centerLineY, x+w-30,centerLineY, 1, 64, 64, 64) end draw_chart_info = function(chart, x, y, w, h, full) local jacket_size = 250 local jacket_y = y+40 if not full then jacket_y = y jacket_size = 300 end local jacket_x = x+(w-jacket_size)/2 gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.BeginPath() if chart.jacketImg ~= nil then gfx.ImageRect(jacket_x, jacket_y, jacket_size, jacket_size, chart.jacketImg, 1, 0) else gfx.BeginPath() gfx.FillColor(0, 0, 0, 128) gfx.Rect(jacket_x, jacket_y, jacket_size, jacket_size) gfx.Fill() gfx.BeginPath() gfx.FillColor(255, 255, 255, math.floor(40+80*waveParam(4))) gfx.FontSize(30) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.Text("No Image", x+w/2, jacket_y + jacket_size/2) end if full then gfx.BeginPath() gfx.FillColor(255, 255, 255) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(30) gfx.Text(string.format("%s %02d", diffNames[chart.difficulty + 1], chart.level), x+w/2, y+30) else do gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) local level_text = string.format("%s %02d", diffNames[chart.difficulty + 1], chart.level) local _a, _b, level_text_width, _c = gfx.TextBounds(0, 0, level_text) local box_width = level_text_width local effector_text = "" if chart.effector ~= nil and chart.effector ~= "" then effector_text = string.format(" by %s", chart.effector) gfx.FontSize(16) local _d, _e, effector_text_width, _f = gfx.TextBounds(0, 0, effector_text) box_width = box_width + effector_text_width end box_width = box_width + 10 if box_width > jacket_size then box_width = jacket_size end gfx.BeginPath() gfx.FillColor(0, 0, 0, 200) gfx.RoundedRectVarying(jacket_x, jacket_y, box_width, 25, 0, 0, 5, 0) gfx.Fill() gfx.FillColor(255, 255, 255) gfx.BeginPath() gfx.FontSize(20) gfx.Text(level_text, jacket_x+5, jacket_y+22) if effector_text ~= "" then gfx.FontSize(16) drawScaledText(effector_text, jacket_x+level_text_width+5, jacket_y+21, jacket_size-level_text_width-10) end end local graph_height = jacket_size * 0.3 local graph_y = jacket_y+jacket_size - graph_height gfx.BeginPath() gfx.FillColor(0,0,0,200) gfx.Rect(jacket_x, graph_y, jacket_size, graph_height) gfx.Fill() draw_gauge_graph(chart, jacket_x, graph_y, jacket_size, graph_height) if gradeImg ~= nil then gfx.BeginPath() gfx.ImageRect(jacket_x+jacket_size-60*chart.gradeAR, jacket_y+jacket_size-60, 60*chart.gradeAR, 60, chart.gradeImg, 1, 0) end gfx.BeginPath() gfx.FillColor(255,255,255) gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text(string.format("%.1f%%", chart.gauge*100), jacket_x+jacket_size+10, jacket_y+jacket_size-graph_height*chart.gauge) return end draw_y = jacket_y + jacket_size + 27 if chart.effector ~= nil and chart.effector ~= "" then gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FillColor(255, 255, 255) gfx.FontSize(16) gfx.Text("Effected by", x+w/2, draw_y) gfx.FontSize(27) drawScaledText(chart.effector, x+w/2, draw_y+24, w/2-5) draw_y = draw_y + 50 end if chart.illustrator ~= nil and chart.illustrator ~= "" then gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(16) gfx.Text("Illustrated by", x+w/2, draw_y) gfx.FontSize(27) drawScaledText(chart.illustrator, x+w/2, draw_y+24, w/2-5) draw_y = draw_y + 50 end end draw_challenge_chart_info = function(chart, x, y, w, h) local jacket_size = 200 local jacket_y = y local jacket_x = x+(w-jacket_size)/2 gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.BeginPath() if chart.jacketImg ~= nil then gfx.ImageRect(jacket_x, jacket_y, jacket_size, jacket_size, chart.jacketImg, 1, 0) else gfx.BeginPath() gfx.FillColor(0, 0, 0, 128) gfx.Rect(jacket_x, jacket_y, jacket_size, jacket_size) gfx.Fill() gfx.BeginPath() gfx.FillColor(255, 255, 255, math.floor(40+80*waveParam(4))) gfx.FontSize(30) gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) gfx.Text("No Image", x+w/2, jacket_y + jacket_size/2) end do gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) local level_text = string.format("%s %02d", diffNames[chart.difficulty + 1], chart.level) local _a, _b, level_text_width, _c = gfx.TextBounds(0, 0, level_text) local box_width = level_text_width local effector_text = "" if chart.effector ~= nil and chart.effector ~= "" then effector_text = string.format(" by %s", chart.effector) gfx.FontSize(16) local _d, _e, effector_text_width, _f = gfx.TextBounds(0, 0, effector_text) box_width = box_width + effector_text_width end box_width = box_width + 10 if box_width > jacket_size then box_width = jacket_size end gfx.BeginPath() gfx.FillColor(0, 0, 0, 200) gfx.RoundedRectVarying(jacket_x, jacket_y, box_width, 25, 0, 0, 5, 0) gfx.Fill() gfx.FillColor(255, 255, 255) gfx.BeginPath() gfx.FontSize(20) gfx.Text(level_text, jacket_x+5, jacket_y+22) if effector_text ~= "" then gfx.FontSize(16) drawScaledText(effector_text, jacket_x+level_text_width+5, jacket_y+21, jacket_size-level_text_width-10) end end do gfx.FontSize(17) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) local gauge_text = string.format("%02.1f%% Gauge", chart.gauge*100) local _a, _b, gauge_text_width, _c = gfx.TextBounds(0, 0, gauge_text) local gauge_box_width = gauge_text_width gauge_box_width = gauge_box_width + 10 gfx.BeginPath() gfx.FillColor(0, 0, 0, 200) gfx.RoundedRectVarying(jacket_x, jacket_y + jacket_size - 25, gauge_box_width, 25, 0, 5, 0, 0) gfx.Fill() gfx.FillColor(255, 255, 255) gfx.BeginPath() gfx.Text(gauge_text, jacket_x+5, jacket_y + jacket_size - 3) end do gfx.BeginPath() if chart.gradeImg ~= nil then gfx.BeginPath() gfx.ImageRect(jacket_x+jacket_size-60*chart.gradeAR, jacket_y+jacket_size-60, 60*chart.gradeAR, 60, chart.gradeImg, 1, 0) end end end draw_basic_hitstat = function(chart, x, y, w, h, full) local grade_width = 70 * chart.gradeAR local stat_y = y local stat_gap = 15 local stat_size = 30 local stat_width = w-8 local showRetryCount = (chart.retryCount ~= nil and chart.retryCount > 0) or (chart.mission ~= nil and chart.mission ~= "") if full then stat_gap = 6 stat_size = 25 stat_width = w-18 if not showRetryCount then stat_gap = 25 stat_y = stat_y + 15 end gfx.BeginPath() gfx.ImageRect(x + (w-grade_width)/2 - 5, stat_y, grade_width, 70, chart.gradeImg, 1, 0) stat_y = stat_y + 85 else stat_y = y + 12 if not showRetryCount then stat_gap = 30 end end if chart.clearTextBase ~= "" then if chart.badge == 5 then gfx.FillColor(255, 255, math.floor(120+125*waveParam(2.0))) elseif chart.badge == 4 then gfx.FillColor(255, 0, 200) elseif chart.badge == 0 then local w = math.floor(128*waveParam(2.0)) gfx.FillColor(255, w, w) else gfx.FillColor(255, 255, 255) end gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(20) gfx.Text(chart.clearText, x+w/2 - 5, stat_y) end stat_y = stat_y + 50 if chart.score == 10000000 then gfx.FillColor(255, 255, math.floor(120+125*waveParam(2.0))) else gfx.FillColor(255, 255, 255) end if full then draw_score(chart.score, x, stat_y, w, 72) stat_y = stat_y + 19 else stat_y = stat_y + 10 stat_gap = stat_gap - 8 draw_score(chart.score, x, stat_y, w, 88) stat_y = stat_y + 19 end if chart.highestScore > 0 then if chart.highestScore > chart.score then gfx.FillColor(255, 32, 32) draw_score(chart.highestScore - chart.score, x+w/2, stat_y, w/2, 25, "-") elseif chart.highestScore == chart.score then gfx.FillColor(128, 128, 128) draw_score(0, x+w/2, stat_y, w/2, 25, utf8.char(0xB1)) else gfx.FillColor(32, 255, 32) draw_score(chart.score - chart.highestScore, x+w/2, stat_y, w/2, 25, "+") end end stat_y = stat_y + stat_gap gfx.FillColor(255, 255, 255) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, critText, chart.perfects, "%d", 255, 150, 0) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, nearText, chart.goods, "%d", 255, 0, 200) local early_late_width = w/2-20 local late_x = x+stat_width-early_late_width draw_stat(late_x-early_late_width-10, stat_y, early_late_width, stat_size-6, "EARLY", chart.earlies, "%d", 255, 0, 255) draw_stat(late_x, stat_y, early_late_width, stat_size-6, "LATE", chart.lates, "%d", 0, 255, 255) stat_y = stat_y + stat_size + 5 stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, "ERROR", chart.misses, "%d", 255, 0, 0) stat_y = draw_stat(x+4, stat_y+15, stat_width, stat_size, "MAX COMBO", chart.maxCombo, "%d", 255, 255, 0) if showRetryCount then local retryCount = 0 if chart.retryCount ~= nil then retryCount = chart.retryCount end stat_y = draw_stat(x+4, stat_y+15, stat_width, stat_size-6, "RETRY", retryCount, "%d") if chart.mission ~= nil and chart.mission ~= "" then gfx.LoadSkinFont("NotoSans-Regular.ttf") gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT) gfx.BeginPath() gfx.FontSize(16) gfx.Text(string.format("Mission: %s", chart.mission), x+4, stat_y) end end end draw_challenge_basic_hitstat = function(chart, x, y, w, h) local stat_y = y + 12 local stat_gap = 25 local stat_size = 25 local stat_width = w-8 if chart.clearTextBase ~= nil and chart.clearTextBase ~= "" then if not chart.passed then gfx.FillColor(math.floor(175+80*waveParam(2.0)), 0, 0) elseif chart.badge == 5 then gfx.FillColor(255, 255, math.floor(120+125*waveParam(2.0))) elseif chart.badge == 4 then gfx.FillColor(255, 0, 200) else gfx.FillColor(150, 255, 150) end gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(19) if chart.passed then gfx.Text(chart.clearTextBase, x+w/2, stat_y+13) else gfx.Text(chart.clearTextBase, x+w/2, stat_y) end end if not chart.passed then stat_y = stat_y + 18 gfx.BeginPath() gfx.TextAlign(gfx.TEXT_ALIGN_CENTER) gfx.FontSize(19) gfx.Text(chart.failReason, x+w/2, stat_y) stat_y = stat_y + 37 else stat_y = stat_y + 55 end if chart.score == 10000000 then gfx.FillColor(255, 255, math.floor(120+125*waveParam(2.0))) else gfx.FillColor(255, 255, 255) end stat_y = stat_y + 10 stat_gap = stat_gap - 8 draw_score(chart.score, x + 5, stat_y, w, 70) stat_y = stat_y gfx.FillColor(170, 210, 255) gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT) gfx.Text(string.format("%00d%% Percent Complete", chart.percent), x+w, stat_y + 15) stat_y = stat_y + stat_gap gfx.FillColor(255, 255, 255) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, critText, chart.perfects, "%d", 255, 150, 0) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, nearText, chart.goods, "%d", 255, 0, 200) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, "ERROR", chart.misses, "%d", 255, 0, 0) stat_y = draw_stat(x+4, stat_y, stat_width, stat_size, "MAX COMBO", chart.maxCombo, "%d", 255, 255, 0) end draw_graphs = function(chart, x, y, w, h) if not chart.hasHitStat or hitDeltaScale == 0.0 then draw_left_graph(chart, x, y, w, h) else draw_left_graph(chart, x, y, w - w//4, h) draw_right_graph(chart, x + (w - w//4), y, w//4, h) end end draw_guide = function(x, y, w, h, full) gfx.LoadSkinFont("NotoSans-Regular.ttf") local fxLText = "FX-L: cycle left" local fxRText = "FX-R: cycle right" local scrollText = "Knobs: scroll results" gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM) gfx.BeginPath() gfx.FillColor(255, 255, 255, 96) gfx.Text(string.format("%s, %s, %s", fxLText, fxRText, scrollText), x+5, y+h) end draw_icons = function(chart, x, y, w, h) gfx.LoadSkinFont("NotoSans-Regular.ttf") local icon_x = x+w-h icon_x = draw_laser_icon(chart, icon_x, y, h) icon_x = draw_speed_icon(chart, icon_x, y, h) --icon_x = draw_hidsud_icon(chart, icon_x, y, h) icon_x = draw_mir_ran_icon(chart, icon_x, y, h) end render = function(deltaTime, newScroll) scroll = newScroll + scrolloff currTime = currTime + deltaTime -- Note: these keys are also used for viewing other players' scores on multiplayer. local fxLeft = game.GetButton(4) local fxRight = game.GetButton(5) if prevFXLeft ~= fxLeft then prevFXLeft = fxLeft if fxLeft then showChartInfo = (showChartInfo - 1) % (#charts + 1) game.PlaySample("menu_click") end end if prevFXRight ~= fxRight then prevFXRight = fxRight if fxRight then showChartInfo = (showChartInfo + 1) % (#charts + 1) game.PlaySample("menu_click") end end local resx,resy = game.GetResolution() if resx ~= currResX or resy ~= currResY or showHiScore ~= prevShowHiScore then prevShowHiScore = showHiScore if showHiScore then desw = 770 else desw = 500 end local scaleX = resx / desw local scaleY = resy / desh scale = math.min(scaleX, scaleY) if scaleX > scaleY then moveX = resx / (2*scale) - desw / 2 moveY = 0 else moveX = 0 moveY = resy / (2*scale) - desh / 2 end currResX = resX currResY = resY end -- For better screenshot display gfx.BeginPath() gfx.FillColor(0, 0, 0) gfx.Rect(0, 0, resx, resy) gfx.Fill() -- Background image gfx.BeginPath() gfx.ImageRect(0, 0, resx, resy, backgroundImage, 0.5, 0); gfx.Scale(scale,scale) gfx.Translate(moveX,moveY) gfx.BeginPath() gfx.Rect(0,0,500,800) gfx.FillColor(30,30,30,128) gfx.Fill() if showChartInfo == 0 then draw_challenge_info(0, 0, 500, 100) end local ystart = 75 if passed then ystart = 55 end -- Result if #charts > 0 then if showChartInfo == 0 then ystart = ystart + 50 local boxh = 770 - ystart if scroll < 0 then scrolloff = scrolloff - scroll scroll = 0 end local scrollh = #charts * 250 -- Check if we can scroll further down local overBottom = scrollh - 100*scroll - boxh if overBottom < 0 and scroll > 0 then local scrollend = (scrollh - boxh)/100 if scrollend < 0 then scrollend = 0 end scrolloff = scrolloff - (scroll - scrollend) scroll = scrollend end -- Draw scroll bar if scrollh > boxh then gfx.BeginPath() gfx.Rect(495, ystart, 5, boxh) gfx.FillColor(30,30,30) gfx.Fill() gfx.BeginPath() local barStart = (100*scroll) / scrollh -- Start percent of visible area local barh = (boxh / scrollh) * boxh gfx.Rect(495, ystart + (barStart*boxh)//1, 5, barh//1) gfx.FillColor(80,80,80) gfx.Fill() end gfx.Scissor(0, ystart, 500, boxh) ystart = ystart - 100*scroll for i,chart in ipairs(charts) do local yloc = ystart + (i-1) * 250 - 40 draw_challenge_title(chart, 0, yloc, 500, 70) yloc = yloc + 72 draw_challenge_chart_info(chart, -15, yloc + 5, 300, 310, false) draw_challenge_basic_hitstat(chart, 40 + 220, yloc, 200, 310) end gfx.ResetScissor() else local chart = charts[showChartInfo] draw_title(chart, 0, 0, 500, 110) if showStatsHit then draw_chart_info(chart, 0, 120, 280, 420, true) draw_basic_hitstat(chart, 280, 120, 220, 420, true) draw_graphs(chart, 0, 540, 500, 210) else draw_chart_info(chart, 0, 120, 500, 310, false) draw_basic_hitstat(chart, 50, 430, 400, 400, false) end if showIcons and result.isSelf ~= false then draw_icons(chart, 0, 750, 500, 50) end if showHiScore then draw_highscores(chart, showStatsHit) end end end if showGuide then draw_guide(0, 750, 500, 50, showStatsHit) end --if showIcons and result.isSelf ~= false then -- draw_icons(0, 750, 500, 50) --end --if showHiScore then -- draw_highscores(showStatsHit) --end -- Applause SFX if result.passed and not played then game.PlaySample("applause") played = true end -- Screenshot notification shotTimer = math.max(shotTimer - deltaTime, 0) if shotTimer > 1 then draw_shotnotif(505,755); end end get_capture_rect = function() local x = moveX * scale local y = moveY * scale local w = 500 * scale local h = 800 * scale return x,y,w,h end screenshot_captured = function(path) shotTimer = 10; shotPath = path; game.PlaySample("shutter") end