local Easing = require('common.easing'); local Charting = require('common.charting'); local Background = require('components.background'); local Footer = require('components.footer'); local Numbers = require('components.numbers') local DiffRectangle = require('components.diff_rectangle'); local lang = require("language.call") require('common.gameconfig') local crew = game.GetSkinSetting("single_idol") local VolforceWindow = require('components.volforceWindow') -- Window variables local resX, resY -- Aspect Ratios local landscapeWidescreenRatio = 16 / 9 local landscapeStandardRatio = 4 / 3 local portraitWidescreenRatio = 9 / 16 -- Portrait sizes local fullX, fullY local desw = 1080 local desh = 1920 local function resolutionChange(x, y) resX = x resY = y fullX = portraitWidescreenRatio * y fullY = y end local bgSfxPlayed = false; local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY) 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 cMOD = gfx.CreateSkinImage("result/panels/cmod.png",0); local warn = gfx.CreateSkinImage("result/panels/warning.png",0); local warntxt = gfx.CreateSkinImage("result/warnings/AUTOPLAY.png",0); local defaultJacketImage = gfx.CreateSkinImage("result/default_jacket.png", 0); local bestScoreBadgeImage = gfx.CreateSkinImage("result/best.png", 0); local defaultCardImage = gfx.CreateSkinImage("result/default_appeal_card.png", 0) local defaultBadgeImage = gfx.CreateSkinImage("result/default_dan.png", 0) local appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0); local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0); local badgeLines = gfx.CreateSkinImage("result/badge_lines.png", 0); local badgeGrade = gfx.CreateSkinImage("result/badge_gradient.png", 0); local gaugeTypeMirrorImage = gfx.CreateSkinImage("result/gauge_type_badges/mirror.png", 0); local gaugeTypeRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random.png", 0); local gaugeTypeMirrorRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random_mirror.png", 0); local gradeImages = { S = gfx.CreateSkinImage("common/grades/S.png", 0), AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0), AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0), AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0), AA = gfx.CreateSkinImage("common/grades/AA.png", 0), A_P = gfx.CreateSkinImage("common/grades/A+.png", 0), A = gfx.CreateSkinImage("common/grades/A.png", 0), B = gfx.CreateSkinImage("common/grades/B.png", 0), C = gfx.CreateSkinImage("common/grades/C.png", 0), D = gfx.CreateSkinImage("common/grades/D.png", 0) } local gaugeTypeBadgeImages = { gfx.CreateSkinImage("result/gauge_type_badges/effective.png", 0), gfx.CreateSkinImage("result/gauge_type_badges/excessive.png", 0), gfx.CreateSkinImage("result/gauge_type_badges/permissive.png", 0), gfx.CreateSkinImage("result/gauge_type_badges/blastive.png", 0), gfx.CreateSkinImage("result/gauge_type_badges/effective.png", 0), -- placeholders in case other types get added gfx.CreateSkinImage("result/gauge_type_badges/effective.png", 0) } local gaugeEffFailFillImage = gfx.CreateSkinImage( "gameplay/gauges/effective/gauge_fill_fail.png", 0) local gaugeEffPassFillImage = gfx.CreateSkinImage( "gameplay/gauges/effective/gauge_fill_pass.png", 0) local gaugeExcFillImage = gfx.CreateSkinImage( "gameplay/gauges/excessive/gauge_result_fill.png", 0) local gaugePermFillImage = gfx.CreateSkinImage( "gameplay/gauges/permissive/gauge_fill.png", 0) local gaugeBlastiveFillImage = gfx.CreateSkinImage( "gameplay/gauges/blastive/gauge_fill.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), gfx.CreateSkinImage("diff/9 exceed.png", 0) } local clearBadgeImages = { { image = gfx.CreateSkinImage("result/clears/CRASH.png", 0), xPos = 970 }, { image = gfx.CreateSkinImage("result/clears/CRASH.png", 0), xPos = 970 }, { image = gfx.CreateSkinImage("result/clears/COMPLETE.png", 0), xPos = 1090 }, { image = gfx.CreateSkinImage("result/clears/COMPLETE.png", 0), xPos = 1090 }, { image = gfx.CreateSkinImage("result/clears/UC.png", 0), xPos = 1150 }, { image = gfx.CreateSkinImage("result/clears/PUC.png", 0), xPos = 1080 }, { image = gfx.CreateSkinImage("result/clears/AUTOPLAY.png", 0), xPos = 1100 }, } -- ANIMS local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..crew, 1 / 30, 0, true); if not idolAnimation then game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING) end local transitionEnterScale = 0; local idolAnimTransitionScale = 0; local badgeLinesAnimScale = 0; local badgeLinesAnimOffsetX = 0; local rightPanelX = 0; local rightPanelY = 910; local bottomPanelX = 0; local bottomPanelY = 1170; local jacketPanelX = 0; local jacketPanelY = 880; local AutoPosiX = 260; local JACKET_PANEL_TRANSTION_ENTER_OFFSET = -256; local RIGHT_PANEL_TRANSTION_ENTER_OFFSET = 256; local BOTTOM_PANEL_TRANSTION_ENTER_OFFSET = 256; local highScore; -- gameplay table does not have a current username field, because why would it lmao -- workaround: retrieve it directly from Main.cfg file local username = GameConfig["MultiplayerUsername"] or game.GetSkinSetting('username') or ''; local msg = game.GetSkinSetting("MSG"); 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} } local irHeartbeatRequested = false; local irText = '' game.LoadSkinSample("result") game.LoadSkinSample("shutter") local function isHard(result) if result.flags == nil then return result.gauge_type == 1 end return result.flags & 1 == 1 end local handleSfx = function() if not bgSfxPlayed then game.PlaySample("result", true) bgSfxPlayed = true game.SetSkinSetting('_musicPlaying', 'result'); end end local drawGraph = function(x,y,w,h) if isHard(result) then gfx.BeginPath() gfx.Rect(x,y,w,103) gfx.FillColor(26,26,26,255) gfx.Fill() gfx.FillColor(255,255,255,255) else gfx.BeginPath() gfx.Rect(x,y,w,h-68) gfx.FillColor(55,27,51,255) gfx.Fill() gfx.BeginPath() gfx.Rect(x,y+30,w,72) gfx.FillColor(7,24,28,255) gfx.Fill() gfx.FillColor(255,255,255,255) end gfx.BeginPath() gfx.MoveTo(x,y + h + 2 - h * result.gaugeSamples[1]) for i = 2, #result.gaugeSamples do gfx.LineTo(x + i * w / #result.gaugeSamples,y + h + 2 - h * result.gaugeSamples[i]) end if isHard(result) then gfx.StrokeWidth(3) gfx.StrokeColor(232,163,10) gfx.Stroke() gfx.Scissor(x, y + h *0.01, w, h*0.98) gfx.Stroke() gfx.ResetScissor() gfx.Scissor(x, y + h * 0.99, w, (h * 0.03) + 4) gfx.StrokeColor(255,0,0) gfx.Stroke() gfx.ResetScissor() else gfx.StrokeWidth(3) gfx.StrokeColor(46,211,241) gfx.Scissor(x, y + h * 0.3, w, (h * 0.7) + 4) gfx.Stroke() gfx.ResetScissor() gfx.Scissor(x, y, w, h*0.3) gfx.StrokeColor(215,48,182) gfx.Stroke() gfx.ResetScissor() end end function drawTimingBar(y, value, max, type) gfx.BeginPath(); if type == 'crit' then gfx.FillColor(255, 255, 84, 255); elseif type == 'early' then gfx.FillColor(255, 16, 94, 255); elseif type == 'late' then gfx.FillColor(16, 225, 255, 255); end gfx.Rect(rightPanelX + 696, rightPanelY + y, 293 * (value / max), 8); gfx.Fill(); gfx.ClosePath(); end local drawIdol = function(deltaTime) if idolAnimation then local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime); if idolAnimTickRes == 1 then gfx.GlobalAlpha(idolAnimTransitionScale); idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60; if (GameConfig["AutoScoreScreenshot"] or idolAnimTransitionScale > 1) then idolAnimTransitionScale = 1; end gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0); gfx.GlobalAlpha(1); end end end local drawbgrade = function() gfx.BeginPath(); gfx.ImageRect(rightPanelX + 1080 - 531, rightPanelY + 2, 531, 85, badgeGrade, 1, 0); end local drawRightBarAni = function(deltaTime) -- badgeLines gfx.BeginPath(); gfx.ImageRect(rightPanelX + 1080 - 531 + badgeLinesAnimOffsetX, rightPanelY + 2, 531, 85, badgeLines, 1, 0); end local drawmod = function () if result.speedModType == 2 then gfx.BeginPath(); gfx.ImageRect(rightPanelX + 1080 - 250, rightPanelY-35, 254, 35, cMOD, 1, 0); end end local drawarn = function () if (result.autoplay) then gfx.BeginPath(); gfx.ImageRect(bottomPanelX+30 , bottomPanelY+495, 541*0.85, 136*0.85, warn, 1, 0); gfx.BeginPath(); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE); gfx.FontSize(20) gfx.Text(lang.Result.re,bottomPanelX + AutoPosiX + 30, bottomPanelY+534); gfx.Text(lang.Result.re1,bottomPanelX + AutoPosiX, bottomPanelY+564); gfx.Text(lang.Result.re2,bottomPanelX + AutoPosiX, bottomPanelY+584); end end local drawTopBar = function() gfx.BeginPath(); local tw, th = gfx.ImageSize(topBarImage); th = (desw / tw) * th; -- recalculate the height of the bar to scale it down 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 = Numbers.load_number_image("score_num"); local drawRightPanelContent = function() local highScoreScore = 0; if highScore then highScoreScore = highScore.score end local highScoreDelta = result.score - highScoreScore -- Draw clear badge local badgeData = clearBadgeImages[result.badge + 1] or clearBadgeImages[1] if (result.autoplay) then badgeData = clearBadgeImages[7]; -- Display AUTOPLAY badge end local tw, th = gfx.ImageSize(badgeData.image); gfx.BeginPath(); gfx.ImageRect(rightPanelX + badgeData.xPos - tw, rightPanelY - 10, tw * 0.85, th * 0.85, badgeData.image, 1, 0); -- 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 Numbers.draw_number(rightPanelX + 580, rightPanelY + 192, 1.0, math.floor(result.score / 10000), 4, scoreNumber, true, 0.40, 1.12) Numbers.draw_number(rightPanelX + 775, rightPanelY + 200, 1.0, result.score, 4, scoreNumber, true, 0.25, 1.12) -- If this is the highscore, draw over the glowing best badge if highScoreDelta > 0 then gfx.BeginPath(); gfx.ImageRect(rightPanelX + 364, rightPanelY + 167, 97, 53, bestScoreBadgeImage, 1, 0); end -- 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 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 type badge gfx.BeginPath(); gfx.ImageRect(rightPanelX + 722, rightPanelY + 273, 211, 40, gaugeTypeBadgeImages[result.gauge_type + 1], 1, 0); -- Draw gauge % gfx.FontSize(24) if result.gauge == 1 then gfx.Text('100%', rightPanelX + 987, rightPanelY + 294); else gfx.Text(math.floor(result.gauge * 100) .. '.', rightPanelX + 964, rightPanelY + 294); gfx.FontSize(18) local decimalPortion = math.floor( ( result.gauge * 100 - math.floor(result.gauge * 100) ) * 10 ); gfx.Text(decimalPortion .. '%', rightPanelX + 988, rightPanelY + 296); -- gfx.FontSize(24) -- gfx.Text('%', rightPanelX + 988, rightPanelY + 294); end gfx.FontSize(24) -- Draw gauge fill local gaugeFillImage = gaugeEffPassFillImage; local gaugeBreakpoint = 0; if result.gauge_type == 0 then gaugeBreakpoint = 0.7; if result.gauge <= 0.7 then gaugeFillImage = gaugeEffFailFillImage; else gaugeFillImage = gaugeEffPassFillImage; end elseif result.gauge_type == 1 then gaugeFillImage = gaugeExcFillImage; elseif result.gauge_type == 2 then gaugeFillImage = gaugePermFillImage; elseif result.gauge_type == 3 then -- BLASTIVE RATE gaugeFillImage = gaugeBlastiveFillImage; end local gaugePosX = rightPanelX + 1027; local gaugePosY = rightPanelY + 309; local FillW, FillH = 9.5, 236; gfx.BeginPath(); gfx.Scissor(gaugePosX, gaugePosY + (FillH - (FillH * (result.gauge))), FillW, FillH * (result.gauge)) gfx.ImageRect(gaugePosX, gaugePosY, FillW, FillH, gaugeFillImage, 1, 0); gfx.ResetScissor(); -- Draw the breakpoint line if needed if (gaugeBreakpoint > 0) then gfx.Save() gfx.BeginPath() gfx.GlobalAlpha(0.75); local lineY = gaugePosY + (FillH - (FillH * (gaugeBreakpoint))) gfx.MoveTo(gaugePosX, lineY) gfx.LineTo(gaugePosX + 10, lineY) gfx.StrokeWidth(2) gfx.StrokeColor(255, 255, 255) gfx.Stroke() gfx.ClosePath() gfx.Restore() end -- Draw the gauge type flags if needed (mirror, random) if(result.mirror or result.random) then gfx.BeginPath(); local gaugeTypeFlagPosX = gaugePosX + 10; local gaugeTypeFlagPosY = gaugePosY - 30; local flagw, flagh = gfx.ImageSize(gaugeTypeMirrorImage) if(result.mirror and result.random) then gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorRandomImage, 1, 0) elseif(result.mirror) then gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorImage, 1, 0) elseif(result.random) then gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeRandomImage, 1, 0) end end -- 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(deltatime) -- Draw appeal card gfx.BeginPath(); gfx.ImageRect(bottomPanelX + 58, bottomPanelY + 277, 103, 132, result.isSelf and appealCardImage or defaultCardImage, 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(result.isSelf and msg or ("Player "..result.displayIndex), bottomPanelX + 190, bottomPanelY + 282); -- Draw username gfx.FontSize(28) gfx.Text(result.playerName or username, bottomPanelX + 190, bottomPanelY + 314); -- Draw dan badge gfx.BeginPath(); gfx.ImageRect(bottomPanelX + 187, bottomPanelY + 362, 107, 29, result.isSelf and danBadgeImage or defaultBadgeImage, 1, 0); -- Draw volforce if result.isSelf then VolforceWindow.render(0, bottomPanelX + 310, bottomPanelY + 355) end -- Draw IR text gfx.FontSize(22) gfx.Text(irText, bottomPanelX + 80, bottomPanelY + 461); -- Draw median and mean hit delta local leftX = bottomPanelX + 600 local rightX = bottomPanelX + 1010 local baseY = bottomPanelY + 440 local detailTextMargin = 25 gfx.FontSize(20) gfx.Text('Median hit delta', leftX, baseY); gfx.Text('Mean hit delta', leftX, baseY + detailTextMargin); gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) gfx.Text(result.medianHitDelta.." ms", rightX, baseY); gfx.Text(math.floor(result.meanHitDelta).." ms", rightX, baseY + detailTextMargin); --Draw Graph drawGraph(leftX-22, baseY-18, 454, 98); --draw Recommended Offset local delta = math.floor(result.medianHitDelta); local songOffset = 0; if (songOffset == nil) then songOffset = 0; end local offset = tonumber(songOffset) + delta; gfx.FillColor(255,255,255,255); gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) gfx.Text('RECOMMENDED SONG OFFSET:', leftX + 367, baseY + 89); gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) gfx.Text(string.format("%dms", offset), leftX + 370, baseY + 89); 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(deltaTime) gfx.BeginPath(); gfx.ImageRect(jacketPanelX + 13, jacketPanelY + 28, 265, 265, jacketImage or defaultJacketImage, 1, 0); local adjustedDiff = Charting.GetDisplayDifficulty(result.jacketPath, result.difficulty) DiffRectangle.render(deltaTime, jacketPanelX+183, jacketPanelY+2.5, 0.67, adjustedDiff, result.level); -- gfx.BeginPath(); -- gfx.ImageRect(jacketPanelX + 183, jacketPanelY + 2.5, 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 + 265, jacketPanelY + 13.5); end local IR_HeartbeatResponse = function(res) if res.statusCode == IRData.States.Success then irText = res.body.serverName .. ' ' .. res.body.irVersion; else game.Log("Can't connect to IR!", game.LOGGER_WARNING) end end local IR_Handle = function() if not irHeartbeatRequested then IR.Heartbeat(IR_HeartbeatResponse) irHeartbeatRequested = true; end end local tickTransitions = function(deltaTime) if not GameConfig["AutoScoreScreenshot"] and 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 = 1170 + (BOTTOM_PANEL_TRANSTION_ENTER_OFFSET * (1 - Easing.outQuad(transitionEnterScale))) jacketPanelX = 40 + (JACKET_PANEL_TRANSTION_ENTER_OFFSET * (1 - Easing.outQuad(transitionEnterScale))) if not GameConfig["AutoScoreScreenshot"] and badgeLinesAnimScale < 1 then badgeLinesAnimScale = badgeLinesAnimScale + deltaTime / 0.5 -- transition should last for that time in seconds else badgeLinesAnimScale = 0 end badgeLinesAnimOffsetX = 16 * (1-badgeLinesAnimScale); end result_set = function() if result.jacketPath ~= nil and result.jacketPath ~= "" then jacketImage = gfx.CreateImage(result.jacketPath, 0) end -- Reset stats earlyLateBarsStats = { earlyErrors = 0, earlyNears = 0, criticals = 0, lateNears = 0, lateErrors = 0 }; objectTypeTimingStats = { chip = {criticals = 0, nears = 0, errors = 0}, long = {criticals = 0, errors = 0}, vol = {criticals = 0, errors = 0} } -- Store the highest score so we can use it later for delta and stuff highScore = result.highScores[1]; -- This check is to prevent errors when these are not available if (result.noteHitStats and result.holdHitStats and result.laserHitStats) then -- "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 else objectTypeTimingStats = { chip = {criticals = 'N/A', nears = 'N/A', errors = 'N/A'}, long = {criticals = 'N/A', errors = 'N/A'}, vol = {criticals = 'N/A', errors = 'N/A'} } end earlyLateBarsStats.criticals = result.perfects -- Criticals are for all objects end drawResultScreen = function (x, y, w, h, deltaTime) gfx.BeginPath() gfx.Translate(x, y); gfx.Scale(w / 1080, h / 1920); gfx.Scissor(0, 0, 1080, 1920); Background.draw(deltaTime) drawIdol(deltaTime) drawTopBar() gfx.GlobalAlpha(Easing.outQuad(transitionEnterScale)) drawBottomPanel() drawBottomPanelContent(deltaTime) drawRightPanel() drawRightBarAni(deltaTime) drawbgrade() drawmod() drawRightPanelContent() drawJacketPanel() drawJacketPanelContent(deltaTime) drawarn() gfx.GlobalAlpha(1) Footer.draw(deltaTime); handleSfx(); IR_Handle(); gfx.ResetTransform() end render = function(deltaTime, showStats) -- detect resolution change local resx, resy = game.GetResolution() if resx ~= resX or resy ~= resY then resolutionChange(resx, resy) end gfx.BeginPath() local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage) gfx.Rect(0, 0, resX, resY) gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2)) gfx.Fill() drawResultScreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime) -- debug gfx.FontSize(18) gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) if game.GetSkinSetting('debug_showInformation') then gfx.Text('DELTA: ' .. deltaTime .. ' // TRANSITION_ENTER_SCALE: ' .. transitionEnterScale .. ' // EASING_OUT_QUAD: ' .. Easing.outQuad(transitionEnterScale), 8, 8); end tickTransitions(deltaTime) end get_capture_rect = function () return (resX - fullX) / 2, 0, fullX, fullY end screenshot_captured = function(path) game.PlaySample("shutter") end