Skip to content

Instantly share code, notes, and snippets.

@itszn
Last active February 13, 2021 22:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itszn/4529fa62d01251de0106fd31e70697d9 to your computer and use it in GitHub Desktop.
Save itszn/4529fa62d01251de0106fd31e70697d9 to your computer and use it in GitHub Desktop.
LiqidWave-1.4.1 Challenges
-- 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 badges = {
gfx.CreateSkinImage("song_select/medal/played.png", 0),
gfx.CreateSkinImage("song_select/medal/clear.png", 0),
gfx.CreateSkinImage("song_select/medal/hard-clear.png", 0),
gfx.CreateSkinImage("song_select/medal/full-combo.png", 0),
gfx.CreateSkinImage("song_select/medal/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("result")
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
local initialRender = true
local function startResultSample()
game.PlaySample("result", true)
game.SetSkinSetting("music_playing", "result")
end
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)
if initialRender then
startResultSample()
end
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
-- Screenshot notification
shotTimer = math.max(shotTimer - deltaTime, 0)
if shotTimer > 1 then
draw_shotnotif(505,755);
end
if initialRender then initialRender = false 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
--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 jacketFallback = gfx.CreateSkinImage("song_select/jacket_loading.png", 0)
--local showGuide = game.GetSkinSetting("show_guide")
local showGuide = false
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("song_select/grade/D.png", 0)},
{["max"] = 7999999, ["image"] = gfx.CreateSkinImage("song_select/grade/C.png", 0)},
{["max"] = 8699999, ["image"] = gfx.CreateSkinImage("song_select/grade/B.png", 0)},
{["max"] = 8999999, ["image"] = gfx.CreateSkinImage("song_select/grade/A.png", 0)},
{["max"] = 9299999, ["image"] = gfx.CreateSkinImage("song_select/grade/A+.png", 0)},
{["max"] = 9499999, ["image"] = gfx.CreateSkinImage("song_select/grade/AA.png", 0)},
{["max"] = 9699999, ["image"] = gfx.CreateSkinImage("song_select/grade/AA+.png", 0)},
{["max"] = 9799999, ["image"] = gfx.CreateSkinImage("song_select/grade/AAA.png", 0)},
{["max"] = 9899999, ["image"] = gfx.CreateSkinImage("song_select/grade/AAA+.png", 0)},
{["max"] = 99999999, ["image"] = gfx.CreateSkinImage("song_select/grade/S.png", 0)}
}
local badges = {
gfx.CreateSkinImage("song_select/medal/played.png", 0),
gfx.CreateSkinImage("song_select/medal/clear.png", 0),
gfx.CreateSkinImage("song_select/medal/hard-clear.png", 0),
gfx.CreateSkinImage("song_select/medal/full-combo.png", 0),
gfx.CreateSkinImage("song_select/medal/perfect.png", 0)
}
gfx.LoadSkinFont("NotoSans-Regular.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;
if x >= y and a <= landscapeStandardRatio then
aspectRatio = "landscapeStandard"
aspectFloat = 1.1
elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then
aspectRatio = "landscapeWidescreen"
aspectFloat = 1.2
elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then
aspectRatio = "PortraitWidescreen"
aspectFloat = 0.5
else
aspectRatio = "landscapeWidescreen"
aspectFloat = 1.0
end
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, 40, 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%% Complete", 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 = "Charts:\n"
for _, chart in ipairs(song.charts) do
desc = desc .. " " .. chart.title .. "\n"
end
if song.missing_chart then
desc = desc .. " *COULD NOT FIND ALL CHARTS!*\n"
end
desc = desc .. "\nGoal:\n" .. song.requirement_text
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)
for i,v in ipairs(grades) do
if v.max > highScore.score then
gfx.BeginPath()
iw,ih = gfx.ImageSize(v.image)
iar = iw / ih;
gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0)
break
end
end
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)
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))
end
draw_cursor = function(x,y,rotation,width)
gfx.Save()
gfx.BeginPath();
gfx.Translate(x,y)
gfx.Rotate(rotation)
gfx.StrokeColor(255,128,0)
gfx.StrokeWidth(4)
gfx.Rect(-width/2, -width/2, width, width)
gfx.Stroke()
gfx.Restore()
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
if aspectRatio == "PortraitWidescreen" then
xPadding = math.floor(w/64)
yPadding = math.floor(h/32)
xMargin = math.floor(w/64)
yMargin = math.floor(h/32)
width = (w-(xMargin*2))
height = (h-(yMargin*2))
xpos = x+xMargin
ypos = y+yMargin
end
--Border
--local diff = song.difficulties[selectedDiff]
gfx.BeginPath()
gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding)
gfx.FillColor(30,30,30)
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)
local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin
if aspectRatio == "PortraitWidescreen" then
--Unless its portrait widesreen..
imageSize = math.floor((height/2)*2)-10
imageXPos = x+xMargin+xPadding
end
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*square_size - #song.charts) * imageSize/2)
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 + bottom_off
end
gfx.ImageRect(xoff + imageXPos, img_row * imageSize + y+yMargin+yPadding, imageSize, imageSize, 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
--gfx.ImageRect(imageXPos, y+yMargin+yPadding, imageSize, imageSize, jacketFallback, 1, 0)
-- difficulty should take up 1/6 of height, full width, and be centered
--if aspectRatio == "PortraitWidescreen" then
--difficulty wheel should be right below the jacketImage, and the same width as
--the jacketImage
-- draw_diffs(song.difficulties,xpos+xPadding,(ypos+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding)
--else
-- difficulty should take up 1/6 of height, full width, and be centered
-- draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6))
--end
-- effector / bpm should take up 1/3 of height, full width
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
if aspectRatio == "PortraitWidescreen" then
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
starty = y+yMargin+yPadding
startx = xpos+xPadding+origImageSize+10
gfx.DrawLabel(songCache[song.id]["title"], startx, starty, 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(width-40, height-50, 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(width-40-iar*50, height-50, iar * 50, 50, gradeImg, 1, 0)
end
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE)
gfx.DrawLabel(songCache[song.id]["percent"], width, height-65, 190)
end
else
starty = (height/10)*6;
if num_img_rows < square_size then
starty = starty - imageSize*(square_size - num_img_rows)
end
gfx.FontSize(40)
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
gfx.DrawLabel(songCache[song.id]["title"], xpos+10, starty, width-20)
-- Scroll box info
boxh = height - starty - 45 - 50
boxw = width-20
startx = xpos + 10
starty = starty + 45
--gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20)
--gfx.FillColor(255,255,255)
--gfx.FontSize(20)
--gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85)
if song.topBadge ~= 0 then
gfx.BeginPath()
gfx.ImageRect(width-25, height-40, 50, 50, badges[song.topBadge], 1, 0)
local iar = 0
local iw, ih = 0;
if gradeImg ~= nil then
gfx.BeginPath()
iw,ih = gfx.ImageSize(gradeImg)
iar = iw/ih
gfx.ImageRect(xpos + 10, height-40, iar * 50, 50, gradeImg, 1, 0)
end
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE )
local ledge = iar * 50 + 20
local redge = 50
local maxLen = width - ledge - redge
local center = ledge + (maxLen/2)
gfx.DrawLabel(songCache[song.id]["percent"], center, height-16, maxLen)
end
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.Scissor(startx, starty, boxw, boxh)
gfx.BeginPath()
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP )
gfx.DrawLabel(songCache[song.id]["desc"], startx+5, starty - (100*scrollmul)//1, boxw)
gfx.ResetScissor()
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)
if aspectRatio == "landscapeWidescreen" then
wheelSize = 12
offsetX = 80
elseif aspectRatio == "landscapeStandard" then
wheelSize = 10
offsetX = 40
elseif aspectRatio == "PortraitWidescreen" then
wheelSize = 20
offsetX = 20
width = w
end
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("NotoSans-Regular.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)
gfx.ResetTransform()
timer = (timer + deltaTime)
timer = timer % 2
resx,resy = game.GetResolution();
adjustScreen(resx,resy);
gfx.BeginPath();
gfx.LoadSkinFont("NotoSans-Regular.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
if aspectRatio == "PortraitWidescreen" then
local song = draw_songwheel(0,0,fullX,fullY)
--render selected song information
draw_selected(song, 0,0,fullX,fifthY)
else
local song = draw_songwheel(fifthX*2-82,0,fifthX*3+82,fullY)
--render selected song information
draw_selected(song, 0,0,fifthX*2-82,(fifthY/2)*9)
end
end
--Draw Legend Information
if showGuide then
if aspectRatio == "PortraitWidescreen" then
draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1)
else
draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2))
end
end
--draw text search
if aspectRatio == "PortraitWidescreen" then
draw_search(fifthX*2,5,fifthX*3,fifthY/5)
else
draw_search(fifthX*2,5,fifthX*3,fifthY/3)
end
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("NotoSans-Regular.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
local DrawCrew = require("shared/draw_crew")local mposx = 0
local mposy = 0
local beginning = false
local cursorIndex = 1
local currentTime = 0
local cursorTimer = 0
local mouseHoverActive = false
local lastMousePos = {0,0}
local buttons = nil
local resx, resy = game.GetResolution();
local desw = 720
local desh = 1280
local scale = resy / desh
local xshift = (resx - desw * scale) / 2
local yshift = (resy - desh * scale) / 2
local CardSize = 330/1.5
local CardSpacing = 14
local bgVersion = game.GetSkinSetting("menu_bg")
local noBgAnim = (bgVersion == "Not animated")
local compressAnim = game.GetSkinSetting("compress_animations")
local backgroundAnimation
if noBgAnim then
backgroundAnimation = gfx.CreateSkinImage("song_select/bg_anim_hq"..d.."/testbg2_fhd000.jpg", 0)
elseif bgVersion == "High quality" then
backgroundAnimation = gfx.LoadSkinAnimation("song_select/bg_anim_hq"..d, 0.033, 0, compressAnim)
else
backgroundAnimation = gfx.LoadSkinAnimation("song_select/bg_anim"..d, 0.05, 0, compressAnim)
end
local fillTop = Image.skin("fill_top"..d..".png")
local fillBottom = Image.skin("fill_bottom"..d..".png")
local fillConsole = Image.skin("fill_console"..d..".png")
local fillConsoleTxt = Image.skin("title/fill_console_txt.png")
local bgFill = gfx.CreateSkinImage("bg_fill"..d..".png", 0)
local topLabel = Image.skin("title/top_label"..d..".png")
local backBlack = Image.skin("title/back_black.png")
local backYellow = Image.skin("title/back_yellow.png")
local lightMask = Image.skin("title/light_mask.png")
local rasis = randomNumber() > 0.5 and Image.skin("title/rasis.png") or Image.skin("title/rasis2.png")
local sankaku = Image.skin("title/sankaku.png")
local playedTheme = false
game.LoadSkinSample("title")
game.LoadSkinSample("intermission")
game.LoadSkinSample("loading")
game.LoadSkinSample("tick")
local view_update = function()
local updateUrl, updateVersion = game.UpdateAvailable()
if package.config:sub(1,1) == '\\' then --windows
os.execute("start " .. updateUrl)
else --unix
-- TODO: Mac solution
os.execute("xdg-open " .. updateUrl)
end
end
local function normalizeScreenCoords()
gfx.ResetTransform()
gfx.ResetScissor()
gfx.Translate(xshift, yshift)
gfx.Scale(scale, scale)
gfx.Scissor(0, 0, desw, desh)
end
local function calculateScreenCoords(x, y)
return (x - xshift) / scale, (y - yshift) / scale
end
local function updateMousePosition()
lastMousePos = {mposx, mposy}
mposx, mposy = game.GetMousePos()
end
local function mousePositionChanged()
return lastMousePos[1] ~= mposx or lastMousePos[2] ~= mposy
end
local function mouseClipped(x,y,w,h)
local localMposX, localMposY = calculateScreenCoords(mposx, mposy)
return localMposX > x and localMposY > y and localMposX < x+w and localMposY < y+h;
end;
local function setButtons()
if buttons == nil then
buttons = {
{action=Menu.Start, card=Image.skin("title/card_title.png"), txt=Image.skin("title/txt_start.png"), subtxt=Image.skin("title/subtxt_start.png")},
{action=Menu.Settings, card=Image.skin("title/card_green.png"), txt=Image.skin("title/txt_settings.png"), subtxt=Image.skin("title/subtxt_settings.png")},
{action=Menu.Multiplayer, card=Image.skin("title/card_orange.png"), txt=Image.skin("title/txt_friend.png"), subtxt=Image.skin("title/subtxt_friend.png")},
{action=Menu.DLScreen, card=Image.skin("title/card_pink.png"), txt=Image.skin("title/txt_nautica.png"), subtxt=Image.skin("title/subtxt_nautica.png")},
{action=Menu.Exit, card=Image.skin("title/card_purple.png"), txt=Image.skin("title/txt_exit.png"), subtxt=Image.skin("title/subtxt_exit.png")},
{action=Menu.Challenges, card=Image.skin("title/card_green.png"), txt=Image.skin("title/txt_friend.png"), subtxt=Image.skin("title/subtxt_friend.png")},
}
end
end
local function sign(x)
return x>0 and 1 or x<0 and -1 or 0
end
local function roundToZero(x)
if x<0 then return math.ceil(x)
elseif x>0 then return math.floor(x)
else return 0 end
end
local function deltaKnob(delta)
if math.abs(delta) > 1.5 * math.pi then
return delta + 2 * math.pi * sign(delta) * -1
end
return delta
end
local function doAction(action)
game.StopSample("title")
game.PlaySample("intermission")
if action == Menu.Start then
game.PlaySample("loading", true)
game.SetSkinSetting("music_playing", "loading")
end
action()
end
local function setCursorIndex(val)
if cursorIndex ~= val then
cursorIndex = val
game.PlaySample("tick")
cursorTimer = 0
end
end
-- If no textX/textY provided then don't draw any text
local function drawButton(action, key, x, y, w, h)
local rx = x - (w / 2)
local ty = y - (h / 2)
if mouseClipped(rx,ty,w,h) then
mouseHoverActive = true
if key and mousePositionChanged() then setCursorIndex(key) end
if cursorIndex == key then
return true, true
end
end
if cursorIndex == key then
return true
end
return false
end
local selectBgMesh = gfx.CreateShadedMesh("cardSelect")
selectBgMesh:SetPrimitiveType(selectBgMesh.PRIM_TRIFAN)
selectBgMesh:AddSkinTexture("mainTex", "title/card_select_bg.png")
local function drawCardButton(buttonKey, x, y, opts)
local button = buttons[buttonKey]
if not opts then opts = {} end
local settings = {x=x, y=y, w=CardSize, h=CardSize}
local selected, hovered = drawButton(button.action, buttonKey,
settings.x, settings.y,
settings.w, settings.h
)
backYellow:draw(settings)
backBlack:draw(settings)
gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
if selected then
local ms = CardSize*1.13
local mx = x - ms/2
local my = y - ms/2
selectBgMesh:SetParam("time", currentTime)
selectBgMesh:SetData({
{{mx,my},{0,0}},
{{mx+ms,my},{1,0}},
{{mx+ms,my+ms},{1,1}},
{{mx,my+ms},{0,1}},
})
selectBgMesh:Draw()
gfx.ForceRender()
normalizeScreenCoords()
end
button.card:draw(settings)
if hovered then
gfx.GlobalAlpha(0.09); gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER)
lightMask:draw(settings)
gfx.GlobalAlpha(1); gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
end
local txtScale = selected and ( math.sin(cursorTimer*10)*0.5+0.5 )*0.1+1 or 1
button.txt:draw({x=settings.x, y=settings.y-30, w=CardSize*txtScale, h=150/1.5*txtScale})
if button.subtxt then
button.subtxt:draw({x=settings.x, y=settings.y+50, w=CardSize, h=100/1.5})
end
if not selected then
gfx.GlobalAlpha(0.12); lightMask:draw(settings); gfx.GlobalAlpha(1)
end
return selected
end
local function drawTextButton(button, x, y)
local selected = drawButton(button[2], nil, x, y, 300, 30)
gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
gfx.BeginPath()
gfx.FontSize(30)
gfx.FillColor(0,0,0);
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.LoadSkinFont("airone.ttf")
gfx.Text(button[1], x, y)
if selected then
gfx.FillColor(255,255,255)
gfx.Text(button[1], x, y-3)
end
end
local function getCardCoords(i)
return
125 + (i%3) * (CardSize+CardSpacing),
630 + math.floor(i/3) * (CardSize+CardSpacing)
end
local function drawUIDecoration(deltaTime)
local selX, selY
for i = 0, #buttons-1 do
local x, y = getCardCoords(i)
local sel = drawCardButton(i+1, x, y)
if sel then
selX = x
selY = y
end
end
if selX and selY then
gfx.Save();gfx.Translate(selX, selY)
gfx.Save()
for i = 0, 3 do
gfx.Rotate((i/4)*math.pi*2)
local sankakuOffset = (math.cos(cursorTimer*10)*0.5+0.5) * 6
sankaku:draw({x=100+sankakuOffset, y=-100-sankakuOffset, w=70/1.5, h=70/1.5})
end
gfx.Restore()
rasis:draw({x=110, y=-100, w=110, h=110})
gfx.Restore()
end
end
local lastKnobs = nil
local knobProgress = 0
local function handleController()
if lastKnobs == nil then
lastKnobs = {game.GetKnob(0), game.GetKnob(1)}
else
local newKnobs = {game.GetKnob(0), game.GetKnob(1)}
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2
lastKnobs = newKnobs
if math.abs(knobProgress) > 0.1 and not beginning then
beginning = false
keepSelection = true
end
if math.abs(knobProgress) > 1 then
setCursorIndex( (((cursorIndex - 1) + roundToZero(knobProgress)) % #buttons) + 1 )
knobProgress = knobProgress - roundToZero(knobProgress)
keepSelection = true
end
end
end
local function draw1080pAsset(img, x, y, opts)
opts = opts or {}
img:draw({x=x, y=y, w=img.w/1.5, h=img.h/1.5, alpha=(opts.alpha or 1),
anchor_h=Image.ANCHOR_LEFT, anchor_v=(opts.anchor_v or Image.ANCHOR_TOP)})
end
render = function(deltaTime)
if not playedTheme then
game.PlaySample("title", true)
playedTheme = true
stopMusic()
end
DrawCrew.prepareCrew(true, true)
currentTime = currentTime + deltaTime
cursorTimer = cursorTimer + deltaTime
setButtons()
updateMousePosition()
resx, resy = game.GetResolution()
scale = resy / desh
xshift = (resx - desw * scale) / 2
yshift = (resy - desh * scale) / 2
if game.GetSkinSetting("landscape_bg_fill") then
gfx.BeginPath()
gfx.FillColor(255, 255, 255)
gfx.ImageRect(0, 0, resx, resy, bgFill, 1, 0)
end
normalizeScreenCoords()
if not noBgAnim then
gfx.TickAnimation(backgroundAnimation, deltaTime)
end
gfx.ImageRect(0, 0, desw, desh, backgroundAnimation, 1, 0)
gfx.Save();gfx.Translate(0, -220)
DrawCrew.drawCrew(deltaTime, true, -40)
gfx.Restore()
draw1080pAsset(fillTop, 0, 0)
draw1080pAsset(topLabel, 15, 10)
draw1080pAsset(fillConsole, 0, desh, {anchor_v=Image.ANCHOR_BOTTOM})
draw1080pAsset(fillConsoleTxt, 0, desh, {anchor_v=Image.ANCHOR_BOTTOM})
draw1080pAsset(fillBottom, 0, desh, {anchor_v=Image.ANCHOR_BOTTOM})
mouseHoverActive = false
drawUIDecoration(deltaTime)
handleController()
local updateUrl, updateVersion = game.UpdateAvailable()
if updateUrl then
local x, y = getCardCoords(5)
if not buttons[6] then buttons[6] = {action=Menu.Update, card=lightMask, txt=Image.skin("title/txt_update.png"), subtxt=nil} end
gfx.BeginPath()
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
gfx.FontSize(24)
gfx.FillColor(0,0,0)
gfx.LoadSkinFont("rounded_bold.ttf")
gfx.Text("UPGRADE TO:", x, y+35)
gfx.Text(string.format("VERSION %s", updateVersion), x, y+55)
end
Credit:draw({x=desw-90, y=desh-18, w=Credit.w/1.5, h=Credit.h/1.5})
gfx.ForceRender()
end
mouse_pressed = function(button)
if mouseHoverActive then
doAction(buttons[cursorIndex].action)
end
return 0
end
button_pressed = function(button)
if button == game.BUTTON_STA then
doAction(buttons[cursorIndex].action)
elseif button == game.BUTTON_BCK then
Menu.Exit()
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment