local Easing = require('common.easings'); local resx, resy = game.GetResolution() local desw = 1080 local desh = 1920 local bgSfxPlayed = false; local backgroundImage = gfx.CreateSkinImage("bg.png", 0); local topBarImage = gfx.CreateSkinImage("result/top_bar.png", 0); local jacketPanelImage = gfx.CreateSkinImage("result/panels/jacket.png", 0); local rightPanelImage = gfx.CreateSkinImage("result/panels/right.png", 0); local bottomPanelImage = gfx.CreateSkinImage("result/panels/bottom.png", 0); local defaultJacketImage = gfx.CreateSkinImage("result/default_jacket.png", 0); local appealCardImage = gfx.CreateSkinImage("appeal_card.png", 0); local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0); local volforceBadgeImage = gfx.CreateSkinImage("volforce/10.png", 0); local gradeImages = { S = gfx.CreateSkinImage("score/S.png", 0), AAA_P = gfx.CreateSkinImage("score/AAA+.png", 0), AAA = gfx.CreateSkinImage("score/AAA.png", 0), AA_P = gfx.CreateSkinImage("score/AA+.png", 0), AA = gfx.CreateSkinImage("score/AA.png", 0), A_P = gfx.CreateSkinImage("score/A+.png", 0), A = gfx.CreateSkinImage("score/A.png", 0), B = gfx.CreateSkinImage("score/B.png", 0), C = gfx.CreateSkinImage("score/C.png", 0), D = gfx.CreateSkinImage("score/D.png", 0), } local difficultyLabelImages = { gfx.CreateSkinImage("diff/1 novice.png", 0), gfx.CreateSkinImage("diff/2 advanced.png", 0), gfx.CreateSkinImage("diff/3 exhaust.png", 0), gfx.CreateSkinImage("diff/4 maximum.png", 0), gfx.CreateSkinImage("diff/5 infinite.png", 0), gfx.CreateSkinImage("diff/6 gravity.png", 0), gfx.CreateSkinImage("diff/7 heavenly.png", 0), gfx.CreateSkinImage("diff/8 vivid.png", 0), } -- ANIMS local idolAnimation = gfx.LoadSkinAnimation('idol', 1/30, 0, false); local transitionEnterScale = 0; local idolAnimTransitionScale = 0; local rightPanelX = 0; local rightPanelY = 850; local bottomPanelX = 0; local bottomPanelY = 1110; local jacketPanelX = 0; local jacketPanelY = 820; local JACKET_PANEL_TRANSTION_ENTER_OFFSET = -256; local RIGHT_PANEL_TRANSTION_ENTER_OFFSET = 256; local BOTTOM_PANEL_TRANSTION_ENTER_OFFSET = 256; local highScore; local username = game.GetSkinSetting('username'); local earlyLateBarsStats = { earlyErrors = 0, earlyNears = 0, criticals = 0, lateNears = 0, lateErrors = 0 }; local objectTypeTimingStats = { chip = {criticals = 0, nears = 0, errors = 0}, long = {criticals = 0, errors = 0}, vol = {criticals = 0, errors = 0} } game.LoadSkinSample("result") function resetLayoutInformation() resx, resy = game.GetResolution() desw = 1080 desh = 1920 scale = resx / desw end local handleSfx = function() if not bgSfxPlayed then game.PlaySample("result", true) bgSfxPlayed = true end if game.GetButton(game.BUTTON_STA) then game.StopSample("result") end if game.GetButton(game.BUTTON_BCK) then game.StopSample("result") end end function drawTimingBar(y, value, max, type) gfx.BeginPath(); if type == 'crit' then gfx.FillColor(253,243,24,255); elseif type == 'early' then gfx.FillColor(215,48,182,255); elseif type == 'late' then gfx.FillColor(46,211,241,255); end gfx.Rect(rightPanelX+696,rightPanelY+y,293*(value/max), 8); gfx.Fill(); gfx.ClosePath(); end function load_number_image(path) local images = {} for i = 0, 9 do images[i + 1] = gfx.CreateSkinImage(string.format("%s/%d.png", path, i), 0) end return images end function draw_number(x, y, alpha, num, digits, images, is_dim, scale, kern) scale = scale or 1; kern = kern or 1; local tw, th = gfx.ImageSize(images[1]) tw = tw * scale; th = th * scale; x = x + (tw * (digits - 1)) / 2 y = y - th / 2 for i = 1, digits do local mul = 10 ^ (i - 1) local digit = math.floor(num / mul) % 10 local a = alpha if is_dim and num < mul then a = 0.4 end gfx.BeginPath() gfx.ImageRect(x, y, tw, th, images[digit + 1], a, 0) x = x - (tw * kern) end end local drawIdol = function (deltaTime) local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime); if idolAnimTickRes == 1 then gfx.GlobalAlpha(idolAnimTransitionScale); idolAnimTransitionScale = idolAnimTransitionScale + 1/60; if (idolAnimTransitionScale > 1) then idolAnimTransitionScale = 1; end gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0); gfx.GlobalAlpha(1); end end local drawTopBar = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(topBarImage); gfx.ImageRect(0, -th * (1 - Easing.outQuad(transitionEnterScale)), desw, th, topBarImage, 1, 0); end local drawRightPanel = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(rightPanelImage); gfx.ImageRect(rightPanelX, rightPanelY, tw, th, rightPanelImage, 1, 0); end local scoreNumber = load_number_image("score_num"); local drawRightPanelContent = function() -- Draw song name and artist gfx.FontSize(28) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text(result.realTitle, rightPanelX + 435, rightPanelY + 108); gfx.Text(result.artist, rightPanelX + 435, rightPanelY + 143); -- Draw score draw_number(rightPanelX+580, rightPanelY+192, 1.0, math.floor(result.score / 10000), 4, scoreNumber, true, 0.42, 1.12) draw_number(rightPanelX+768, rightPanelY+202, 1.0, result.score, 4, scoreNumber, true, 0.25, 1.12) -- Draw grade local gradeImageKey = string.gsub(result.grade, '+', '_P'); local gradeImage = gradeImages[gradeImageKey] or gradeImages.D gfx.BeginPath(); gfx.ImageRect(rightPanelX+890, rightPanelY+130, 85, 85, gradeImage, 1, 0); -- Draw best score gfx.FontSize(20) gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.LoadSkinFont('Digital-Serial-Bold.ttf') local highScoreScore = 0; if highScore then highScoreScore = highScore.score end local highScoreDelta = result.score - highScoreScore local deltaPrefix = '-' if highScoreDelta > 0 then deltaPrefix = '+' end highScoreDelta = math.abs(highScoreDelta); gfx.Text(string.format("%08d", highScoreScore), rightPanelX + 962, rightPanelY + 239); gfx.Text(deltaPrefix .. string.format("%08d", highScoreDelta), rightPanelX + 962, rightPanelY + 259); -- Draw gauge status gfx.FontSize(24) gfx.Text(math.floor(result.gauge * 100) .. '%', rightPanelX + 984, rightPanelY + 295); -- Draw err/early/critical/late/err texts gfx.Text(earlyLateBarsStats.earlyErrors, rightPanelX + 683, rightPanelY + 370); gfx.Text(earlyLateBarsStats.earlyNears, rightPanelX + 683, rightPanelY + 401); gfx.Text(earlyLateBarsStats.criticals, rightPanelX + 683, rightPanelY + 432); gfx.Text(earlyLateBarsStats.lateNears, rightPanelX + 683, rightPanelY + 463); gfx.Text(earlyLateBarsStats.lateErrors, rightPanelX + 683, rightPanelY + 494); -- Draw hit timing bars local totalHits = earlyLateBarsStats.earlyErrors + earlyLateBarsStats.earlyNears + earlyLateBarsStats.criticals + earlyLateBarsStats.lateNears + earlyLateBarsStats.lateErrors gfx.Save() drawTimingBar(365, earlyLateBarsStats.earlyErrors, totalHits, 'early') drawTimingBar(396, earlyLateBarsStats.earlyNears, totalHits, 'early') drawTimingBar(427, earlyLateBarsStats.criticals, totalHits, 'crit') drawTimingBar(458, earlyLateBarsStats.lateNears, totalHits, 'late') drawTimingBar(489, earlyLateBarsStats.lateErrors, totalHits, 'late') gfx.Restore() -- Draw hit stats based on objects -- CHIP gfx.Text(objectTypeTimingStats.chip.criticals, rightPanelX + 255, rightPanelY + 365); gfx.Text(objectTypeTimingStats.chip.nears, rightPanelX + 255, rightPanelY + 395); gfx.Text(objectTypeTimingStats.chip.errors, rightPanelX + 255, rightPanelY + 425); -- LONG gfx.Text(objectTypeTimingStats.long.criticals, rightPanelX + 333, rightPanelY + 365); gfx.Text('-', rightPanelX + 333, rightPanelY + 395); gfx.Text(objectTypeTimingStats.long.errors, rightPanelX + 333, rightPanelY + 425); -- VOL gfx.Text(objectTypeTimingStats.vol.criticals, rightPanelX + 411, rightPanelY + 365); gfx.Text('-', rightPanelX + 411, rightPanelY + 395); gfx.Text(objectTypeTimingStats.vol.errors, rightPanelX + 411, rightPanelY + 425); -- Draw max combo gfx.Text(result.maxCombo, rightPanelX + 371, rightPanelY + 466); end local drawBottomPanel = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(bottomPanelImage); gfx.ImageRect(bottomPanelX, bottomPanelY, tw, th, bottomPanelImage, 1, 0); end local drawBottomPanelContent = function () -- Draw appeal card gfx.BeginPath(); gfx.ImageRect(bottomPanelX+58, bottomPanelY+277, 103, 132, appealCardImage, 1, 0); -- Draw description gfx.FontSize(22) gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text('Hellooooooo', bottomPanelX+190, bottomPanelY+282); -- Draw username gfx.FontSize(28) gfx.Text(username, bottomPanelX+190, bottomPanelY+314); -- Draw dan badge gfx.BeginPath(); gfx.ImageRect(bottomPanelX+187, bottomPanelY+362, 107, 29, danBadgeImage, 1, 0); -- Draw volforce badge gfx.BeginPath(); gfx.ImageRect(bottomPanelX+310, bottomPanelY+355, 42, 42, volforceBadgeImage, 1, 0); -- Draw volforce label gfx.FontSize(11) gfx.Text('VOLFORCE', bottomPanelX+357, bottomPanelY+369); gfx.FontSize(18) gfx.Text('20.148', bottomPanelX+357, bottomPanelY+385); end local drawJacketPanel = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(jacketPanelImage); gfx.ImageRect(jacketPanelX, jacketPanelY, tw, th, jacketPanelImage, 1, 0); end local drawJacketPanelContent = function() gfx.BeginPath(); gfx.ImageRect(jacketPanelX + 12, jacketPanelY + 26, 273, 273, jacketImage or defaultJacketImage, 1, 0); gfx.BeginPath(); gfx.ImageRect(jacketPanelX + 188, jacketPanelY + 3, 140/1.5, 31/1.5, difficultyLabelImages[result.difficulty+1] or difficultyLabelImages[4], 1, 0); gfx.FontSize(17) gfx.LoadSkinFont('Digital-Serial-Bold.ttf') gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text(result.level, jacketPanelX+270, jacketPanelY+14.5); end local tickTransitions = function(deltaTime) if transitionEnterScale < 1 then transitionEnterScale = transitionEnterScale + deltaTime / 0.66 -- transition should last for that time in seconds else transitionEnterScale = 1 end rightPanelX = 0 + (RIGHT_PANEL_TRANSTION_ENTER_OFFSET * (1 - Easing.outQuad(transitionEnterScale))) bottomPanelY = 1110 + (BOTTOM_PANEL_TRANSTION_ENTER_OFFSET * (1 - Easing.outQuad(transitionEnterScale))) jacketPanelX = 40 + (JACKET_PANEL_TRANSTION_ENTER_OFFSET * (1 - Easing.outQuad(transitionEnterScale))) end result_set = function() if result.jacketPath ~= nil and result.jacketPath ~= "" then jacketImage = gfx.CreateImage(result.jacketPath, 0) end -- Store the highest score so we can use it later for delta and stuff highScore = result.highScores[1]; -- "CHIP" objects for hitStatIndex = 1, #result.noteHitStats do local hitStat = result.noteHitStats[hitStatIndex]; if (hitStat.rating == 0) then -- Errors objectTypeTimingStats.chip.errors = objectTypeTimingStats.chip.errors + 1; if hitStat.delta < 0 then earlyLateBarsStats.earlyErrors = earlyLateBarsStats.earlyErrors + 1; else earlyLateBarsStats.lateErrors = earlyLateBarsStats.lateErrors + 1; end elseif hitStat.rating == 1 then -- Nears objectTypeTimingStats.chip.nears = objectTypeTimingStats.chip.nears + 1; if hitStat.delta < 0 then earlyLateBarsStats.earlyNears = earlyLateBarsStats.earlyNears + 1; else earlyLateBarsStats.lateNears = earlyLateBarsStats.lateNears + 1; end else -- Criticals objectTypeTimingStats.chip.criticals = objectTypeTimingStats.chip.criticals + 1; end end -- "LONG" objects for hitStatIndex = 1, #result.holdHitStats do local hitStat = result.holdHitStats[hitStatIndex]; if (hitStat.rating == 0) then -- Errors objectTypeTimingStats.long.errors = objectTypeTimingStats.long.errors + 1; earlyLateBarsStats.lateErrors = earlyLateBarsStats.lateErrors + 1; else -- Criticals objectTypeTimingStats.long.criticals = objectTypeTimingStats.long.criticals + 1; end end -- "VOL" a.k.a laser objects for hitStatIndex = 1, #result.laserHitStats do local hitStat = result.laserHitStats[hitStatIndex]; if (hitStat.rating == 0) then -- Errors objectTypeTimingStats.vol.errors = objectTypeTimingStats.vol.errors + 1; earlyLateBarsStats.lateErrors = earlyLateBarsStats.lateErrors + 1; else -- Criticals objectTypeTimingStats.vol.criticals = objectTypeTimingStats.vol.criticals + 1; end end earlyLateBarsStats.criticals = result.perfects -- Criticals are for all objects -- misses on LONGs or LAZERs are automatically late errors -- so we add errors that are not ealy or late to late errors -- earlyLateBarsStats.lateErrors = earlyLateBarsStats.lateErrors + -- (result.misses - -- earlyLateBarsStats.lateErrors - -- earlyLateBarsStats.earlyErrors) -- criticals are same for all objects so we just copy them from results end render = function(deltaTime, showStats) gfx.Save(); resetLayoutInformation() gfx.ResetTransform(); gfx.ResetScissor(); gfx.BeginPath() gfx.Scale(scale, scale) gfx.ImageRect(0, 0, desw, desh, backgroundImage, 1, 0); drawIdol(deltaTime) drawTopBar() gfx.GlobalAlpha(Easing.outQuad(transitionEnterScale)) drawBottomPanel() drawBottomPanelContent() drawRightPanel() drawRightPanelContent() drawJacketPanel() drawJacketPanelContent() gfx.GlobalAlpha(1) handleSfx() -- debug gfx.FontSize(18) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) -- gfx.Text('DELTA: ' .. deltaTime .. ' // TRANSITION_ENTER_SCALE: ' .. -- transitionEnterScale .. ' // EASING_OUT_QUAD: ' .. -- Easing.outQuad(transitionEnterScale), 8, 8); tickTransitions(deltaTime) gfx.Restore(); gfx.BeginPath(); end