From 26ad00bd7fc87158451dac897071b54805b07bca Mon Sep 17 00:00:00 2001 From: FajsiEx Date: Sun, 29 Aug 2021 17:52:44 +0200 Subject: [PATCH] * adjust the critbar position & - legacy unused gameplay scripts --- ...lback.lua => _gameplay before rewrite.lua} | 206 ++- scripts/gameplay newest.lua | 1436 ----------------- scripts/gameplay old.lua | 938 ----------- scripts/gameplay.lua | 14 +- scripts/gameplay2.lua | 1436 ----------------- 5 files changed, 145 insertions(+), 3885 deletions(-) rename scripts/{gameplay fallback.lua => _gameplay before rewrite.lua} (86%) delete mode 100644 scripts/gameplay newest.lua delete mode 100644 scripts/gameplay old.lua delete mode 100644 scripts/gameplay2.lua diff --git a/scripts/gameplay fallback.lua b/scripts/_gameplay before rewrite.lua similarity index 86% rename from scripts/gameplay fallback.lua rename to scripts/_gameplay before rewrite.lua index 0ea9a7c..a67e380 100644 --- a/scripts/gameplay fallback.lua +++ b/scripts/_gameplay before rewrite.lua @@ -6,6 +6,8 @@ -- or behaviours of the default to better suit them. -- Skinning should be easy and fun! +local VolforceWindow = require('components.volforceWindow') + local RECT_FILL = "fill" local RECT_STROKE = "stroke" local RECT_FILL_STROKE = RECT_FILL .. RECT_STROKE @@ -141,8 +143,6 @@ end -- -------------------------------------------------------------------------- -- -- Global data used by many things: -- local resx, resy -- The resolution of the window -local resx_old = 0 -local resy_old = 0 local portrait -- whether the window is in portrait orientation local desw, desh -- The resolution of the deisign local scale -- the scale to get from design to actual units @@ -224,14 +224,8 @@ function render(deltaTime) -- make sure that our transform is cleared, clean working space -- TODO: this shouldn't be necessary!!! gfx.ResetTransform() - - -- While the intro timer is running, we fade in from black - if introTimer > 0 then - gfx.FillColor(0, 0, 0, math.floor(255 * math.min(introTimer, 1))) - gfx.DrawRect(RECT_FILL, 0, 0, resx, resy) - end - gfx.Scale(scale, scale) + local yshift = 0 -- In portrait, we draw a banner across the top @@ -309,7 +303,7 @@ function render_crit_base(deltaTime) -- get the scaled dimensions of the crit line pieces local clw, clh = gfx.ImageSize(critAnim) - local critAnimHeight = 9 * scale + local critAnimHeight = 12 * scale local critAnimWidth = critAnimHeight * (clw / clh) local cbw, cbh = gfx.ImageSize(critBar) @@ -343,7 +337,7 @@ function render_crit_base(deltaTime) end -- Draw the critical bar - gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale, critWidth, critBarHeight) + gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale + 24, critWidth, critBarHeight) -- Draw back portion of the console if portrait then @@ -649,7 +643,7 @@ function draw_best_diff(deltaTime, x, y) elseif difference > 0 then -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(170, 160, 255) + gfx.FillColor(120, 146, 218) difference = math.abs(difference) prefix = "+ " end @@ -685,8 +679,8 @@ function draw_score(deltaTime) gfx.ImageRect(desw - tw + 12, portrait and 50 or 0, tw, th, scoreBack, 1, 0) gfx.FillColor(255, 255, 255) - draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.40, 1.12) - draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.3, 1.12) + draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.38, 1.12) + draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.28, 1.12) -- Draw max combo gfx.FillColor(255, 255, 255) @@ -694,44 +688,141 @@ function draw_score(deltaTime) end -- -------------------------------------------------------------------------- -- -- draw_gauge: -- -local gaugeNumBack = gfx.CreateSkinImage("gauge_num_back.png", 0) -gfx.SetGaugeColor(0, 47, 244, 255) --Normal gauge fail -gfx.SetGaugeColor(1, 252, 76, 171) --Normal gauge clear -gfx.SetGaugeColor(2, 255, 255, 255) --Hard gauge low (<30%) -gfx.SetGaugeColor(3, 255, 255, 255) --Hard gauge high (>30%) +local gaugeMarkerBgImage = gfx.CreateSkinImage("gameplay/gauges/marker_bg.png", 0) + +local gaugeWarnTransitionScale = 0; + +local gaugeEffBgImage = gfx.CreateSkinImage("gameplay/gauges/effective/gauge_back.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 gaugeExcBgImage = gfx.CreateSkinImage("gameplay/gauges/excessive/gauge_back.png", 0) +local gaugeExcFillImage = gfx.CreateSkinImage("gameplay/gauges/excessive/gauge_fill.png", 0) + +local gaugeExcArsBgImage = gfx.CreateSkinImage("gameplay/gauges/excessive_ars/gauge_back.png", 0) +local gaugeExcArsFillImage = gfx.CreateSkinImage("gameplay/gauges/excessive_ars/gauge_fill.png", 0) + +local gaugePermBgImage = gfx.CreateSkinImage("gameplay/gauges/permissive/gauge_back.png", 0) +local gaugePermFillImage = gfx.CreateSkinImage("gameplay/gauges/permissive/gauge_fill.png", 0) + +local gaugeBlastiveBgImage = gfx.CreateSkinImage("gameplay/gauges/blastive/gauge_back.png", 0) +local gaugeBlastiveFillImage = gfx.CreateSkinImage("gameplay/gauges/blastive/gauge_fill.png", 0) + function draw_gauge(deltaTime) - local height = 702 * scale / 1 - local width = 82 * scale / 1 - local posy = resy / 2 - height / 2 + 60 - local posx = resx - width - if portrait then - posy = posy - 90 - posx = resx - width + -- fallbacks in case of unsupported type + local gaugeFillAlpha = 1; + local gaugeBgImage = gaugeEffBgImage; + local gaugeFillImage = gaugeEffPassFillImage; + local gaugeBreakpoint = 0; + + if gameplay.gauge.type == 0 then + gaugeBgImage = gaugeEffBgImage; + gaugeBreakpoint = 0.7; + + if gameplay.gauge.value <= 0.7 then + gaugeFillImage = gaugeEffFailFillImage; + else + gaugeFillImage = gaugeEffPassFillImage; + end + + elseif gameplay.gauge.type == 1 then + gaugeBgImage = gaugeExcBgImage; + gaugeFillImage = gaugeExcFillImage; + + if (game.GetSkinSetting('_gaugeARS') == 1) then + gaugeBgImage = gaugeExcArsBgImage + gaugeFillImage = gaugeExcArsFillImage + end + + gaugeBreakpoint = 0.3; + + if gameplay.gauge.value < 0.3 then + gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 20 -> 100 + + gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; + if gaugeWarnTransitionScale > 1 then + gaugeWarnTransitionScale = 0; + end + end + elseif gameplay.gauge.type == 2 then + gaugeBgImage = gaugePermBgImage; + gaugeFillImage = gaugePermFillImage; + + if gameplay.gauge.value < 0.3 then + gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 52 -> 100 + + gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; + if gaugeWarnTransitionScale > 1 then + gaugeWarnTransitionScale = 0; + end + end + elseif gameplay.gauge.type == 3 then -- BLASTIVE RATE + gaugeBgImage = gaugeBlastiveBgImage; + gaugeFillImage = gaugeBlastiveFillImage; + + if gameplay.gauge.value < 0.3 then + gaugeFillAlpha = 1 - math.abs(gaugeWarnTransitionScale - 0.25); -- 100 -> 20 -> 100 + + gaugeWarnTransitionScale = gaugeWarnTransitionScale + deltaTime*10; + if gaugeWarnTransitionScale > 1 then + gaugeWarnTransitionScale = 0; + end + end end - gfx.DrawGauge(gameplay.gauge, posx, posy, width, height, deltaTime) + + + local BgW, BgH = gfx.ImageSize(gaugeBgImage); + local FillW, FillH = gfx.ImageSize(gaugeFillImage); + local gaugePosX = 1080 - BgW - 110; + local gaugePosY = 1920/2 - BgH/2 - 95; - --draw gauge % label - posx = posx / scale - posx = posx + (-20 * 0.5) - -- 630 = 0% position - height = 960 * 0.5 - posy = posy / scale + -- gfx.Text('RESX: ' .. resx .. ' // RESY: ' .. resy .. ' // GPX: ' .. gaugePosX, 255,1200); - local tw, th = gfx.ImageSize(gaugeNumBack) - -- 80 = 100% position - posy = posy + (95 * 0.5) + height - height * gameplay.gauge - -- Draw the background gfx.BeginPath() + gfx.ImageRect(gaugePosX, gaugePosY, BgW, BgH, gaugeBgImage, 1, 0) + + gfx.GlobalAlpha(gaugeFillAlpha); + gfx.BeginPath() + gfx.Scissor(gaugePosX+18, gaugePosY+9+(FillH-(FillH*(gameplay.gauge.value))), FillW, FillH*(gameplay.gauge.value)) + gfx.ImageRect(gaugePosX+18, gaugePosY+9, FillW, FillH, gaugeFillImage, 1, 0) + gfx.ResetScissor(); + gfx.GlobalAlpha(1); + + -- Draw the breakpoint line if needed + if (gaugeBreakpoint > 0) then + gfx.Save() + gfx.BeginPath() + gfx.GlobalAlpha(0.75); + + local lineY = gaugePosY+6+(FillH-(FillH*(gaugeBreakpoint))) + + gfx.MoveTo(gaugePosX+18, lineY) + gfx.LineTo(gaugePosX+36, lineY) + + gfx.StrokeWidth(2) + gfx.StrokeColor(255,255,255) + gfx.Stroke() + + gfx.ClosePath() + gfx.Restore() + end + + -- Draw gauge % label + local gaugeMarkerY = gaugePosY-6+(FillH-(FillH*(gameplay.gauge.value))) + + gfx.BeginPath() + gfx.ImageRect(gaugePosX-64, gaugeMarkerY, 83*0.85, 37*0.85, gaugeMarkerBgImage, 1, 0) + + gfx.BeginPath() gfx.FillColor(255, 255, 255) - gfx.ImageRect(posx - 44, posy - 10, tw, th, gaugeNumBack, 1, 0) + gfx.LoadSkinFont("Digital-Serial-Bold.ttf") + gfx.FontSize(22) + gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) + gfx.Text(math.floor(gameplay.gauge.value * 100), gaugePosX-16, gaugeMarkerY+17) - gfx.BeginPath() - -- gfx.FillColor(250, 228, 112) - draw_number(posx - 24, posy + 4, 1.0, math.floor(gameplay.gauge * 100), 3, numberImages, true) - -- gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) - -- gfx.FontSize(18) - -- gfx.Text(string.format("%d", math.floor(gameplay.gauge * 100)), posx, posy + 4) + gfx.FontSize(16) + gfx.Text('%', gaugePosX-4, gaugeMarkerY+17) end -- -------------------------------------------------------------------------- -- -- draw_combo: -- @@ -762,26 +853,13 @@ function draw_combo(deltaTime) local tw, th tw, th = gfx.ImageSize(comboBottom) gfx.BeginPath() - gfx.ImageRect(posx - tw / 2 - 5, posy - th / 2 - 270, tw, th, comboBottom, alpha, 0) + gfx.ImageRect(posx - tw / 2 + 10, posy - th / 4 - 210, tw * 0.85, th * 0.85, comboBottom, alpha, 0) tw, th = gfx.ImageSize(comboCurrent[1]) - posy = posy - th + posy = posy - th + 32 - local digit = combo % 10 - gfx.BeginPath() - gfx.ImageRect(posx + 70, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], alpha, 0) - - digit = math.floor(combo / 10) % 10 - gfx.BeginPath() - gfx.ImageRect(posx, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 10 and alpha or 0.2, 0) - - digit = math.floor(combo / 100) % 10 - gfx.BeginPath() - gfx.ImageRect(posx - 70, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 100 and alpha or 0.2, 0) - - digit = math.floor(combo / 1000) % 10 - gfx.BeginPath() - gfx.ImageRect(posx - 140, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 1000 and alpha or 0.2, 0) + local comboScale = 0.45; + draw_number(desw/2 - (tw*4*comboScale)/2+(tw*comboScale*1.5)+10, posy - th / 2, 1.0, combo, 4, comboCurrent, true, comboScale, 1.12) end -- -------------------------------------------------------------------------- -- -- draw_earlate: -- @@ -859,7 +937,7 @@ end local statusBack = Image.skin("status_back.png") local apealCard = Image.skin("appeal_card.png") local dan = Image.skin("dan.png") -local volforce = Image.skin ("volforce.png") + function draw_status(deltaTime) -- Draw the background gfx.FillColor(255, 255, 255) @@ -872,10 +950,10 @@ function draw_status(deltaTime) dan:draw({ x = 164, y = desh / 2 - 117, w = dan.w * 0.32, h = dan.h * 0.32 }) -- Draw the Volforce - volforce:draw({ x = 240, y = desh / 2 - 119, w = volforce.w * 0.12, h = volforce.h * 0.12 }) + VolforceWindow.render(deltaTime, 220, desh / 2 - 140); -- Draw the best difference - draw_best_diff(deltaTime, 145, desh / 2 - 175) + draw_best_diff(deltaTime, 145, desh / 2 - 174) -- Draw the username draw_username(deltatime, 145, desh / 2 - 198) diff --git a/scripts/gameplay newest.lua b/scripts/gameplay newest.lua deleted file mode 100644 index ab2a7cb..0000000 --- a/scripts/gameplay newest.lua +++ /dev/null @@ -1,1436 +0,0 @@ --- The following code slightly simplifies the render/update code, making it easier to explain in the comments --- It replaces a few of the functions built into USC and changes behaviour slightly --- Ideally, this should be in the common.lua file, but the rest of the skin does not support it --- I'll be further refactoring and documenting the default skin and making it more easy to --- modify for those who either don't know how to skin well or just want to change a few images --- or behaviours of the default to better suit them. --- Skinning should be easy and fun! - - --- Animation functions begin -function clamp(x, min, max) - if x < min then - x = min - end - if x > max then - x = max - end - - return x -end - -function smootherstep(edge0, edge1, x) - -- Scale, and clamp x to 0..1 range - x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) - -- Evaluate polynomial - return x * x * x * (x * (x * 6 - 15) + 10) -end - -function to_range(val, start, stop) - return start + (stop - start) * val -end - -Animation = { - start = 0, - stop = 0, - progress = 0, - duration = 1, - smoothStart = false -} - -function Animation:new(o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o -end - -function Animation:restart(start, stop, duration) - self.progress = 0 - self.start = start - self.stop = stop - self.duration = duration -end - -function Animation:tick(deltaTime) - self.progress = math.min(1, self.progress + deltaTime / self.duration) - if self.progress == 1 then return self.stop end - if self.smoothStart then - return to_range(smootherstep(0, 1, self.progress), self.start, self.stop) - else - return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop) - end -end ---- Animation Functions end - - -local RECT_FILL = "fill" -local RECT_STROKE = "stroke" -local RECT_FILL_STROKE = RECT_FILL .. RECT_STROKE - -gfx._ImageAlpha = 1 -if gfx._FillColor == nil then - gfx._FillColor = gfx.FillColor - gfx._StrokeColor = gfx.StrokeColor - gfx._SetImageTint = gfx.SetImageTint -end - --- we aren't even gonna overwrite it here, it's just dead to us -gfx.SetImageTint = nil - -function gfx.FillColor(r, g, b, a) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - a = math.floor(a or 255) - - gfx._ImageAlpha = a / 255 - gfx._FillColor(r, g, b, a) - gfx._SetImageTint(r, g, b) -end - -function gfx.StrokeColor(r, g, b) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - - gfx._StrokeColor(r, g, b) -end - -function gfx.DrawRect(kind, x, y, w, h) - local doFill = kind == RECT_FILL or kind == RECT_FILL_STROKE - local doStroke = kind == RECT_STROKE or kind == RECT_FILL_STROKE - - local doImage = not (doFill or doStroke) - - gfx.BeginPath() - - if doImage then - gfx.ImageRect(x, y, w, h, kind, gfx._ImageAlpha, 0) - else - gfx.Rect(x, y, w, h) - if doFill then gfx.Fill() end - if doStroke then gfx.Stroke() end - end -end - -local buttonStates = { } -local buttonsInOrder = { - game.BUTTON_BTA, - game.BUTTON_BTB, - game.BUTTON_BTC, - game.BUTTON_BTD, - - game.BUTTON_FXL, - game.BUTTON_FXR, - - game.BUTTON_STA, -} - -function UpdateButtonStatesAfterProcessed() - for i = 1, 6 do - local button = buttonsInOrder[i] - buttonStates[button] = game.GetButton(button) - end -end - -function game.GetButtonPressed(button) - return game.GetButton(button) and not buttonStates[button] -end --- -------------------------------------------------------------------------- -- --- game.IsUserInputActive: -- --- Used to determine if (valid) controller input is happening. -- --- Valid meaning that laser motion will not return true unless the laser is -- --- active in gameplay as well. -- --- This restriction is not applied to buttons. -- --- The player may press their buttons whenever and the function returns true. -- --- Lane starts at 1 and ends with 8. -- -function game.IsUserInputActive(lane) - if lane < 7 then - return game.GetButton(buttonsInOrder[lane]) - end - return gameplay.IsLaserHeld(lane - 7) -end --- -------------------------------------------------------------------------- -- --- gfx.FillLaserColor: -- --- Sets the current fill color to the laser color of the given index. -- --- An optional alpha value may be given as well. -- --- Index may be 1 or 2. -- -function gfx.FillLaserColor(index, alpha) - alpha = math.floor(alpha or 255) - local r, g, b = game.GetLaserColor(index - 1) - gfx.FillColor(r, g, b, alpha) -end --- -------------------------------------------------------------------------- -- --- load_number_image: -- --- Loads numbers from files to allow usage of multi-colored numbers, custom -- --- fonts, and many other things -- -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 --- -------------------------------------------------------------------------- -- --- draw_number: -- --- Draws numbers from images in the skin. -- --- Additional values control scaling and spacing. -- -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 - --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- The actual gameplay script starts here! -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local resx_old = 0 -local resy_old = 0 -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- All images used by the script: -- -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) -local bottomFill = gfx.CreateSkinImage("console/console.png", 0) -local topFill = gfx.CreateSkinImage("fill_top.png", 0) -local critAnimImg = gfx.CreateSkinImage("crit_anim.png", gfx.IMAGE_REPEATX) -local critAnim = gfx.ImagePattern(0,-50,100,100,0,critAnimImg,1) -local critBar = gfx.CreateSkinImage("crit_bar.png", 0) -local critConsole = gfx.CreateSkinImage("console/crit_console.png", 0) -local critCap = gfx.CreateSkinImage("crit_cap.png", 0) -local critCapBack = gfx.CreateSkinImage("crit_cap_back.png", 0) -local laserTail = gfx.CreateSkinImage("laser_tail.png", 0) -local laserCursor = gfx.CreateSkinImage("pointer.png", 0) -local laserCursorText = gfx.CreateSkinImage("pointer_bottom.png", 0) -local laserCursorOverlay = gfx.CreateSkinImage("pointer_overlay.png", 0) -local laserCursorGlow = gfx.CreateSkinImage("pointer_glow.png", 0) -local laserCursorShine = gfx.CreateSkinImage("pointer_shine.png", 0) -local laserTopWave = gfx.CreateSkinImage("laser_top_wave.png", 0) -local scoreEarly = gfx.CreateSkinImage("score_early.png", 0) -local scoreLate = gfx.CreateSkinImage("score_late.png", 0) -local numberImages = load_number_image("number") - -local prevGaugeType = nil -local gaugeTransition = nil - ---Skin Settings info -local username = game.GetSkinSetting('username') or ''; - -local ioConsoleDetails = { - gfx.CreateSkinImage("console/detail_left.png", 0), - gfx.CreateSkinImage("console/detail_right.png", 0), -} - -local consoleAnimImages = { - gfx.CreateSkinImage("console/glow_bta.png", 0), - gfx.CreateSkinImage("console/glow_btb.png", 0), - gfx.CreateSkinImage("console/glow_btc.png", 0), - gfx.CreateSkinImage("console/glow_btd.png", 0), - - gfx.CreateSkinImage("console/glow_fxl.png", 0), - gfx.CreateSkinImage("console/glow_fxr.png", 0), - - gfx.CreateSkinImage("console/glow_voll.png", 0), - gfx.CreateSkinImage("console/glow_volr.png", 0), -} --- -------------------------------------------------------------------------- -- -local resx, resy -- The resolution of the window -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- Timers, used for animations: -- -if introTimer == nil then - introTimer = 2 - outroTimer = 0 -end -local alertTimers = {-2,-2} - -local earlateTimer = 0 -local critAnimTimer = 0 - -local consoleAnimSpeed = 10 -local consoleAnimTimers = { 0, 0, 0, 0, 0, 0, 0, 0 } --- -------------------------------------------------------------------------- -- --- Miscelaneous, currently unsorted: -- -local score = 0 -local combo = 0 -local jacket = nil -local critLinePos = { 0.95, 0.75 }; -local comboScale = 1.0 -local late = false -local diffNames = {"NOV", "ADV", "EXH", "MXM", "INF", "GRV", "HVN", "VVD"} -local clearTexts = {"TRACK FAILED", "TRACK COMPLETE", "TRACK COMPLETE", "FULL COMBO", "PERFECT" } --- -------------------------------------------------------------------------- -- --- Cached calculations -- -local song_info = {} -local gauge_info = {} -local crit_base_info = {} -local combo_info = {} -local practice_info = {} - - -function LoadGauge(type) - - local name = type == 0 and "normal" or "hard" - local gauge_verts = { - {{gauge_info.posx, gauge_info.posy}, {0,1}}, - {{gauge_info.posx + gauge_info.width, gauge_info.posy}, {1,1}}, - {{gauge_info.posx + gauge_info.width, gauge_info.posy + gauge_info.height}, {1,0}}, - {{gauge_info.posx, gauge_info.posy + gauge_info.height}, {0,0}}, - } - local meshes = {} - meshes.front = gfx.CreateShadedMesh() - meshes.front:SetPrimitiveType(meshes.front.PRIM_TRIFAN) - meshes.front:SetData(gauge_verts) - meshes.front:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_front.png") - - meshes.back = gfx.CreateShadedMesh() - meshes.back:SetPrimitiveType(meshes.back.PRIM_TRIFAN) - meshes.back:SetData(gauge_verts) - meshes.back:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_back.png") - - meshes.fill = gfx.CreateShadedMesh("gauge") - meshes.fill:SetPrimitiveType(meshes.fill.PRIM_TRIFAN) - meshes.fill:SetData(gauge_verts) - meshes.fill:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_fill.png") - meshes.fill:AddSkinTexture("maskTex", "gauges/" .. name .. "/gauge_mask.png") - - return meshes -end - --- -------------------------------------------------------------------------- -- --- ResetLayoutInformation: -- --- Resets the layout values used by the skin. -- -function ResetLayoutInformation() - portrait = resy > resx - desw = portrait and 720 or 1280 - desh = desw * (resy / resx) - scale = resx / desw - - do --update song_info - local songInfoWidth = 400 - local jacketWidth = 100 - song_info.songInfoWidth = songInfoWidth - song_info.jacketWidth = jacketWidth - - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.FontSize(30) - - song_info.textX = jacketWidth + 10 - local titleWidth = songInfoWidth - jacketWidth - 20 - local x1, y1, x2, y2 = gfx.TextBounds(0, 0, gameplay.title) - song_info.title_textscale = math.min(titleWidth / x2, 1) - x1,y1,x2,y2 = gfx.TextBounds(0,0,gameplay.artist) - song_info.artist_textscale = math.min(titleWidth / x2, 1) - end - - do --update gauge_info - gauge_info.height = 1024 * 0.35 - gauge_info.width = 512 * 0.35 - gauge_info.posy = desh / 2 - gauge_info.height / 2 - gauge_info.posx = desw - gauge_info.width - if portrait then - gauge_info.width = gauge_info.width * 0.8 - gauge_info.height = gauge_info.height * 0.8 - gauge_info.posy = gauge_info.posy - 30 - gauge_info.posx = desw - gauge_info.width - end - - gauge_info.label_posx = gauge_info.posx + (100 * 0.35) - gauge_info.label_height = 880 * 0.35 - if portrait then - gauge_info.label_height = gauge_info.label_height * 0.8; - end - gauge_info.label_posy = gauge_info.posy + (70 * 0.35) + gauge_info.label_height - - gauge_info.meshes = {} - gauge_info.meshes[0] = LoadGauge(0) - gauge_info.meshes[1] = LoadGauge(1) - end - - do --update crit_base_info - -- The absolute width of the crit line itself - -- we check to see if we're playing in portrait mode and - -- change the width accordingly - crit_base_info.critWidth = resx * (portrait and 1 or 0.8) - crit_base_info.half_critWidth = crit_base_info.critWidth / 2 - - -- get the scaled dimensions of the crit line pieces - local clw, clh = gfx.ImageSize(critAnimImg) - crit_base_info.critAnimHeight = 15 * scale - crit_base_info.critAnimWidth = crit_base_info.critAnimHeight * (clw / clh) - - local ccw, cch = gfx.ImageSize(critCap) - crit_base_info.critCapHeight = crit_base_info.critAnimHeight * (cch / clh) - crit_base_info.critCapWidth = crit_base_info.critCapHeight * (ccw / cch) - - crit_base_info.half_critAnimHeight = crit_base_info.critAnimHeight / 2 - crit_base_info.half_critAnimWidth = crit_base_info.critAnimWidth / 2 - crit_base_info.half_critCapHeight = crit_base_info.critCapHeight / 2 - crit_base_info.half_critCapWidth = crit_base_info.critCapWidth / 2 - end - - do --update combo_info - combo_info.posx = desw / 2 - combo_info.posy = desh * critLinePos[1] - 100 - if portrait then combo_info.posy = desh * critLinePos[2] - 150 end - end - - do-- update practice_info - practice_info.posy = 120 - practice_info.posx = 10 - end -end --- -------------------------------------------------------------------------- -- --- render: -- --- The primary & final render call. -- --- Use this to render basically anything that isn't the crit line or the -- --- intro/outro transitions. -- -function render(deltaTime) - -- make sure that our transform is cleared, clean working space - -- TODO: this shouldn't be necessary!!! - gfx.ResetTransform() - - gfx.Scale(scale, scale) - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - -- TODO: this isn't how it'll work in the long run, I don't think - if portrait then yshift = draw_banner(deltaTime) end - - gfx.Translate(0, yshift - 150 * math.max(introTimer - 1, 0)) - draw_song_info(deltaTime) - draw_score(deltaTime) - - - if prevGaugeType ~= nil then - if gameplay.gauge.type ~= prevGaugeType and gaugeTransition == nil then - gaugeTransition = Animation:new() - gaugeTransition.smoothStart = true - gaugeTransition:restart(0, 1, 1 / 3) - end - end - gfx.Translate(0, -yshift + 150 * math.max(introTimer - 1, 0)) - - gfx.Save() - if gaugeTransition ~= nil then - local v = gaugeTransition:tick(deltaTime) - if v < 1 then - local awayGauge = {} - awayGauge.type = prevGaugeType - awayGauge.value = 0.0 - gfx.Save() - gfx.Translate(v * gauge_info.width, 0) - draw_gauge(awayGauge) - gfx.Restore() - gfx.Translate((1-v) * gauge_info.width, 0) - else - prevGaugeType = gameplay.gauge.type - gaugeTransition = nil - end - else - prevGaugeType = gameplay.gauge.type - end - draw_gauge(gameplay.gauge) - gfx.Restore() - - - if earlatePos ~= "off" then - draw_earlate(deltaTime) - end - draw_combo(deltaTime) - draw_alerts(deltaTime) - - if gameplay.practice_setup ~= nil then - draw_practice(deltaTime); - end - - local play_mode = "" - - if gameplay.practice_setup - then play_mode = "Practice Setup" - elseif gameplay.autoplay - then play_mode = "Autoplay" - elseif gameplay.playbackSpeed ~= nil and gameplay.playbackSpeed < 1 - then play_mode = string.format("Speed: x%.2f", gameplay.playbackSpeed) - elseif gameplay.hitWindow ~= nil and gameplay.hitWindow.type == 0 - then play_mode = "Expand Judge" - end - - if play_mode ~= "" then - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.FontSize(30) - gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER) - gfx.FillColor(255,255,255) - gfx.Text(play_mode, desw/2, yshift) - end -end --- -------------------------------------------------------------------------- -- --- SetUpCritTransform: -- --- Utility function which aligns the graphics transform to the center of the -- --- crit line on screen, rotation include. -- --- This function resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function SetUpCritTransform() - -- start us with a clean empty transform - gfx.ResetTransform() - -- translate and rotate accordingly - gfx.Translate(gameplay.critLine.x, gameplay.critLine.y) - gfx.Rotate(-gameplay.critLine.rotation) -end --- -------------------------------------------------------------------------- -- --- GetCritLineCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- crit line on the screen based on its rotation. -- -function GetCritLineCenteringOffset() - return gameplay.critLine.xOffset * 10 -end --- -------------------------------------------------------------------------- -- --- GetConsoleCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- console on the screen based on its position and rotation. -- -function GetConsoleCenteringOffset() - return (resx / 2 - gameplay.critLine.x) * (5 / 6) -end --- -------------------------------------------------------------------------- -- --- render_crit_base: -- --- Called after rendering the highway and playable objects, but before -- --- the built-in hit effects. -- --- This is the first render function to be called each frame. -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_base(deltaTime) - -- Kind of a hack, but here (since this is the first render function - -- that gets called per frame) we update the layout information. - -- This means that the player can resize their window and - -- not break everything - resx, resy = game.GetResolution() - if resx ~= resx_old or resy ~= resy_old then - ResetLayoutInformation() - resx_old = resx - resy_old = resy - end - - critAnimTimer = critAnimTimer + deltaTime - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen - local xOffset = GetCritLineCenteringOffset() - gfx.Translate(xOffset, 0) - - -- Draw a transparent black overlay below the crit line - -- This darkens the play area as it passes - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, -resx, 0, resx * 2, resy) - - -- draw the back half of the caps at each end - do - gfx.FillColor(255, 255, 255) - -- left side - gfx.DrawRect(critCapBack, - -crit_base_info.half_critWidth -crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- scale to flip horizontally - -- right side - gfx.DrawRect(critCapBack, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- unflip horizontally - end - - -- render the core of the crit line - do - -- The crit line is made up of two rects with a pattern that scrolls in opposite directions on each rect - local startOffset = crit_base_info.critAnimWidth * ((critAnimTimer * 1.5) % 1) - - -- left side - -- Use a scissor to limit the drawable area to only what should be visible - gfx.UpdateImagePattern(critAnim, - -startOffset, - -crit_base_info.half_critAnimHeight, - crit_base_info.critAnimWidth, - crit_base_info.critAnimHeight, - 0, 1) - - gfx.Scissor(-crit_base_info.half_critWidth, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - - gfx.BeginPath() - gfx.Rect(-crit_base_info.half_critWidth, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.FillPaint(critAnim) - gfx.Fill() - gfx.ResetScissor() - - - -- right side - -- exactly the same, but in reverse - gfx.UpdateImagePattern(critAnim, - startOffset, - -crit_base_info.half_critAnimHeight, - crit_base_info.critAnimWidth, - crit_base_info.critAnimHeight, - 0, 1) - - gfx.Scissor(0, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.BeginPath() - gfx.Rect(0, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.FillPaint(critAnim) - gfx.Fill() - gfx.ResetScissor() - end - - -- Draw the front half of the caps at each end - do - gfx.FillColor(255, 255, 255) - -- left side - gfx.DrawRect(critCap, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- scale to flip horizontally - -- right side - gfx.DrawRect(critCap, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- unflip horizontally - end - - -- we're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- render_crit_overlay: -- --- Called after rendering built-int crit line effects. -- --- Use this to render laser cursors or an IO Console in portrait mode! -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_overlay(deltaTime) - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen. - local xOffset = GetConsoleCenteringOffset() - - -- When in portrait, we can draw the console at the bottom - if portrait then - -- We're going to make temporary modifications to the transform - gfx.Save() - gfx.Translate(xOffset, 0) - - local bfw, bfh = gfx.ImageSize(bottomFill) - - local distBetweenKnobs = 0.446 - local distCritVertical = 0.098 - - local ioFillTx = bfw / 2 - local ioFillTy = bfh * distCritVertical -- 0.098 - - -- The total dimensions for the console image - local io_x, io_y, io_w, io_h = -ioFillTx, -ioFillTy, bfw, bfh - - -- Adjust the transform accordingly first - local consoleFillScale = (resx * 0.775) / (bfw * distBetweenKnobs) - gfx.Scale(consoleFillScale, consoleFillScale); - - -- Actually draw the fill - gfx.FillColor(255, 255, 255) - gfx.DrawRect(bottomFill, io_x, io_y, io_w, io_h) - - -- Then draw the details which need to be colored to match the lasers - for i = 1, 2 do - gfx.FillLaserColor(i) - gfx.DrawRect(ioConsoleDetails[i], io_x, io_y, io_w, io_h) - end - - -- Draw the button press animations by overlaying transparent images - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - for i = 1, 6 do - -- While a button is held, increment a timer - -- If not held, that timer is set back to 0 - if game.GetButton(buttonsInOrder[i]) then - consoleAnimTimers[i] = consoleAnimTimers[i] + deltaTime * consoleAnimSpeed * 3.14 * 2 - else - consoleAnimTimers[i] = 0 - end - - -- If the timer is active, flash based on a sin wave - local timer = consoleAnimTimers[i] - if timer ~= 0 then - local image = consoleAnimImages[i] - local alpha = (math.sin(timer) * 0.5 + 0.5) * 0.5 + 0.25 - gfx.FillColor(255, 255, 255, alpha * 255); - gfx.DrawRect(image, io_x, io_y, io_w, io_h) - end - end - gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER) - - -- Undo those modifications - gfx.Restore(); - end - - local cw, ch = gfx.ImageSize(laserCursor) - local cursorWidth = 40 * scale - local cursorHeight = cursorWidth * (ch / cw) - - -- draw each laser cursor - for i = 1, 2 do - local cursor = gameplay.critLine.cursors[i - 1] - local pos, skew = cursor.pos, cursor.skew - - -- Add a kinda-perspective effect with a horizontal skew - gfx.SkewX(skew) - - --Add the tail, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 255) - gfx.DrawRect(laserTail, pos - cursorWidth / 2 - 64, -cursorHeight / 2 - 5, cursorWidth * 5, cursorHeight * 5) - end - - -- Draw the SDVX Icon eye and tails below the overlay - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorText, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored background with the appropriate laser color - gfx.FillLaserColor(i, cursor.alpha * 130) - gfx.DrawRect(laserCursor, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - - --Add the top wave effect, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 180) - gfx.DrawRect(laserTopWave, pos - cursorWidth / 2 - 80, -cursorHeight / 2 - 24, cursorWidth * 6, cursorHeight * 6) - end - - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorOverlay, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored glow on top of the pointer - gfx.FillLaserColor(i, cursor.alpha * 160) - gfx.DrawRect(laserCursorGlow, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 150) - gfx.DrawRect(laserCursorShine, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Un-skew - gfx.SkewX(-skew) - end - - -- We're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- draw_banner: -- --- Renders the banner across the top of the screen in portrait. -- --- This function expects no graphics transform except the design scale. -- -function draw_banner(deltaTime) - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - local actualHeight = desw * (bannerHeight / bannerWidth) - - gfx.FillColor(255, 255, 255) - gfx.DrawRect(topFill, 0, 0, desw, actualHeight) - - return actualHeight -end --- -------------------------------------------------------------------------- -- --- draw_stat: -- --- Draws a formatted name + value combination at x, y over w, h area. -- -function draw_stat(x, y, w, h, name, value, format, r, g, b) - gfx.Save() - - -- Translate from the parent transform, wherever that may be - gfx.Translate(x, y) - - -- Draw the `name` top-left aligned at `h` size - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FontSize(h) - gfx.Text(name .. ":", 0, 0) -- 0, 0, is x, y after translation - - -- Realign the text and draw the value, formatted - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format(format, value), w, 0) - -- This draws an underline beneath the text - -- The line goes from 0, h to w, h - gfx.BeginPath() - gfx.MoveTo(0, h) - gfx.LineTo(w, h) -- only defines the line, does NOT draw it yet - - -- If a color is provided, set it - if r then gfx.StrokeColor(r, g, b) - -- otherwise, default to a light grey - else gfx.StrokeColor(200, 200, 200) end - - -- Stroke out the line - gfx.StrokeWidth(1) - gfx.Stroke() - -- Undo our transform changes - gfx.Restore() - - -- Return the next `y` position, for easier vertical stacking - return y + h + 5 -end --- -------------------------------------------------------------------------- -- --- draw_song_info: -- --- Draws current song information at the top left of the screen. -- --- This function expects no graphics transform except the design scale. -- -local songBack = gfx.CreateSkinImage("song_back.png", 0) -local numberDot = gfx.CreateSkinImage("number/dot.png", 0) -local diffImages = { - 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) -} -local memo = Memo.new() - -function draw_song_info(deltaTime) - local jacketWidth = 105 - - -- Check to see if there's a jacket to draw, and attempt to load one if not - if jacket == nil or jacket == jacketFallback then - jacket = gfx.LoadImageJob(gameplay.jacketPath, jacketFallback) - end - - gfx.Save() - - -- Add a small margin at the edge - gfx.Translate(5,5) - -- There's less screen space in portrait, the playable area is effectively a square - -- We scale down to take up less space - if portrait then gfx.Scale(0.7, 0.7) end - - -- Ensure the font has been loaded - gfx.LoadSkinFont("segoeui.ttf") - - -- Draw the background - local tw, th = gfx.ImageSize(songBack) - gfx.FillColor(255,255,255) - gfx.BeginPath() - gfx.ImageRect(-2, -71, tw * 0.855, th * 0.855, songBack, 1, 0) - -- Draw the jacket - gfx.FillColor(255, 255, 255) - gfx.DrawRect(jacket, 31, -39, song_info.jacketWidth, song_info.jacketWidth) - -- Draw a background for the following level stat - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, 0, 85, 60, 15) - -- Level Name : Level Number - gfx.FillColor(255, 255, 255) - draw_stat(0, 85, 55, 15, diffNames[gameplay.difficulty + 1], gameplay.level, "%02d") - -- Reset some text related stuff that was changed in draw_state - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) - gfx.FontSize(30) - - gfx.FillColor(255, 255, 255) - - local textscale = song_info.title_textscale - local textX = song_info.textX - - gfx.Save() - do -- Draw the song title, scaled to fit as best as possible - gfx.Translate(textX, 30) - gfx.Scale(textscale, textscale) - gfx.Text(gameplay.title, 0, 0) - end - gfx.Restore() - - textscale = song_info.artist_textscale - - gfx.Save() - do -- Draw the song artist, scaled to fit as best as possible - gfx.Translate(textX, 60) - gfx.Scale(textscale, textscale) - gfx.Text(gameplay.artist, 0, 0) - end - gfx.Restore() - - -- Draw the BPM - gfx.FontSize(20) - gfx.Text(string.format("BPM: %.1f", gameplay.bpm), textX, 85) - - -- Fill the progress bar - gfx.FillColor(0, 150, 255) - gfx.DrawRect(RECT_FILL, song_info.jacketWidth, song_info.jacketWidth - 10, (song_info.songInfoWidth - song_info.jacketWidth) * gameplay.progress, 10) - - -- When the player is holding Start, the hispeed can be changed - -- Shows the current hispeed values - if game.GetButton(game.BUTTON_STA) then - gfx.FillColor(20, 20, 20, 200); - gfx.DrawRect(RECT_FILL, 100, 100, song_info.songInfoWidth - 100, 20) - gfx.FillColor(255, 255, 255) - if game.GetButton(game.BUTTON_BTB) then - gfx.Text(string.format("Hid/Sud Cutoff: %.1f%% / %.1f%%", - gameplay.hiddenCutoff * 100, gameplay.suddenCutoff * 100), - textX, 115) - - elseif game.GetButton(game.BUTTON_BTC) then - gfx.Text(string.format("Hid/Sud Fade: %.1f%% / %.1f%%", - gameplay.hiddenFade * 100, gameplay.suddenFade * 100), - textX, 115) - else - gfx.Text(string.format("HiSpeed: %.0f x %.1f = %.0f", - gameplay.bpm, gameplay.hispeed, gameplay.bpm * gameplay.hispeed), - textX, 115) - end - end - - -- aaaand, scene! - gfx.Restore() -end --- -------------------------------------------------------------------------- -- --- draw_best_diff: -- --- If there are other saved scores, this displays the difference between -- --- the current play and your best. -- -function draw_best_diff(deltaTime, x, y) - -- Don't do anything if there's nothing to do - if not gameplay.scoreReplays[1] then return end - - -- Calculate the difference between current and best play - local difference = score - gameplay.scoreReplays[1].currentScore - local prefix = "" -- used to properly display negative values - - gfx.BeginPath() - gfx.FontSize(26) - - gfx.FillColor(255, 255, 255) - if difference < 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(255, 90, 70) - difference = math.abs(difference) - prefix = "- " - - elseif difference > 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(170, 160, 255) - difference = math.abs(difference) - prefix = "+ " - end - - -- %08d formats a number to 8 characters - -- This includes the minus sign, so we do that separately - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format("%s%08d", prefix, difference), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - -local score_animation = Animation:new() --- -------------------------------------------------------------------------- -- --- draw_score: -- -local scoreBack = gfx.CreateSkinImage("score_back.png", 0) -local scoreNumber = load_number_image("score_num") -local maxCombo = 0 -function draw_score(deltaTime) - local tw, th = gfx.ImageSize(scoreBack) - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - tw = tw * 0.61; - th = th * 0.61; - gfx.ImageRect(desw - tw + 12, portrait and 50 or 0, tw, th, scoreBack, 1, 0) - - gfx.FillColor(255, 255, 255) - draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.40, 1.12) - draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.3, 1.12) - - -- Draw max combo - gfx.FillColor(255, 255, 255) - draw_number(desw - 300, portrait and 207 or 110, 1.0, maxCombo, 4, numberImages, true) -end --- -------------------------------------------------------------------------- -- --- draw_gauge: -- -function draw_gauge(gauge) - local c = nil - if gauge.type == 0 then - if gauge.value > 0.7 then - c = {r = 1, g = 0, b = 1} - else - c = {r = 0, g = 0.5, b = 1} - end - else - c = {r = 1, g = 0.5, b = 0} - end - - gauge_info.meshes[gauge.type].fill:SetParamVec4("barColor", c.r, c.g, c.b, 1) - gauge_info.meshes[gauge.type].fill:SetParam("rate", gauge.value) - - gauge_info.meshes[gauge.type].back:Draw() - gauge_info.meshes[gauge.type].fill:Draw() - gauge_info.meshes[gauge.type].front:Draw() - - --draw gauge % label - local posy = gauge_info.label_posy - gauge_info.label_height * gauge.value - gfx.BeginPath() - gfx.Rect(gauge_info.label_posx-35, posy-10, 40, 20) - gfx.FillColor(0,0,0,200) - gfx.Fill() - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(20) - gfx.Text(string.format("%d%%", math.floor(gauge.value * 100)), gauge_info.label_posx, posy ) -end --- -------------------------------------------------------------------------- -- --- draw_combo: -- -function draw_combo(deltaTime) - if combo == 0 then return end - gfx.BeginPath() - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - if gameplay.comboState == 2 then - gfx.FillColor(100,255,0) --puc - elseif gameplay.comboState == 1 then - gfx.FillColor(255,200,0) --uc - else - gfx.FillColor(255,255,255) --regular - end - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70 * math.max(comboScale, 1)) - comboScale = comboScale - deltaTime * 3 - gfx.Text(tostring(combo), combo_info.posx, combo_info.posy) -end --- -------------------------------------------------------------------------- -- --- draw_earlate: -- -function draw_earlate(deltaTime) - earlateTimer = math.max(earlateTimer - deltaTime,0) - if earlateTimer == 0 then return nil end - local alpha = math.floor(earlateTimer * 20) % 2 - alpha = alpha * 200 + 55 - gfx.BeginPath() - gfx.FontSize(35) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE) - local ypos = desh * critLinePos[1] - 150 - if portrait then ypos = desh * critLinePos[2] - 200 end - if earlatePos == "middle" then - ypos = ypos - 200 - elseif earlatePos == "top" then - ypos = ypos - 400 - end - - if late then - gfx.FillColor(0,255,255, alpha) - gfx.Text("LATE", desw / 2, ypos) - else - gfx.FillColor(255,0,255, alpha) - gfx.Text("EARLY", desw / 2, ypos) - end -end --- -------------------------------------------------------------------------- -- --- draw_alerts: -- -function draw_alerts(deltaTime) - alertTimers[1] = math.max(alertTimers[1] - deltaTime,-2) - alertTimers[2] = math.max(alertTimers[2] - deltaTime,-2) - if alertTimers[1] > 0 then --draw left alert - gfx.Save() - local posx = desw / 2 - 350 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 135 - posx = 65 - end - gfx.Translate(posx,posy) - r,g,b = game.GetLaserColor(0) - local alertScale = (-(alertTimers[1] ^ 2.0) + (1.5 * alertTimers[1])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.BeginPath() - gfx.RoundedRectVarying(-50,-50,100,100,20,0,20,0) - gfx.StrokeColor(r,g,b) - gfx.FillColor(20,20,20) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.BeginPath() - gfx.FillColor(r,g,b) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(90) - gfx.Text("L",0,0) - gfx.Restore() - end - if alertTimers[2] > 0 then --draw right alert - gfx.Save() - local posx = desw / 2 + 350 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 135 - posx = desw - 65 - end - gfx.Translate(posx,posy) - r,g,b = game.GetLaserColor(1) - local alertScale = (-(alertTimers[2] ^ 2.0) + (1.5 * alertTimers[2])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.BeginPath() - gfx.RoundedRectVarying(-50,-50,100,100,0,20,0,20) - gfx.StrokeColor(r,g,b) - gfx.FillColor(20,20,20) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.BeginPath() - gfx.FillColor(r,g,b) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(90) - gfx.Text("R",0,0) - gfx.Restore() - end -end - -function change_earlatepos() - if earlatePos == "top" then - earlatePos = "off" - elseif earlatePos == "off" then - earlatePos = "bottom" - elseif earlatePos == "bottom" then - earlatePos = "middle" - elseif earlatePos == "middle" then - earlatePos = "top" - end - game.SetSkinSetting("earlate_position", earlatePos) -end --- -------------------------------------------------------------------------- -- --- draw_status: -- -local statusBack = Image.skin("status_back.png") -local apealCard = Image.skin("appeal_card.png") -local dan = Image.skin("dan.png") -local volforce = Image.skin ("volforce.png") -function draw_status(deltaTime) - -- Draw the background - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - statusBack:draw({ x = 0, y = desh / 2 - 195, w = statusBack.w * 0.85, h = statusBack.h * 0.85, anchor_h = Image.ANCHOR_LEFT }) - gfx.Fill() - - -- Draw the apeal card - apealCard:draw({ x = 12, y = desh / 2 - 220, w = apealCard.w * 0.62, h = apealCard.h * 0.62, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP }) - - -- Draw the dan - dan:draw({ x = 164, y = desh / 2 - 117, w = dan.w * 0.32, h = dan.h * 0.32 }) - - -- Draw the Volforce - volforce:draw({ x = 240, y = desh / 2 - 119, w = volforce.w * 0.12, h = volforce.h * 0.12 }) - - -- Draw the best difference - draw_best_diff(deltaTime, 145, desh / 2 - 175) - - -- Draw the username - draw_username(deltatime, 145, desh / 2 - 198) -end - --- -------------------------------------------------------------------------- -- --- render_intro: -- -local bta_last = false -function render_intro(deltaTime) - if gameplay.demoMode then - introTimer = 0 - return true - end - if not game.GetButton(game.BUTTON_STA) then - introTimer = introTimer - deltaTime - earlateTimer = 0 - else - earlateTimer = 1 - if (not bta_last) and game.GetButton(game.BUTTON_BTA) then - change_earlatepos() - end - end - bta_last = game.GetButton(game.BUTTON_BTA) - introTimer = math.max(introTimer, 0) - - return introTimer <= 0 -end --- -------------------------------------------------------------------------- -- --- render_outro: -- -function render_outro(deltaTime, clearState) - if clearState == 0 then return true end - if not gameplay.demoMode then - gfx.ResetTransform() - gfx.BeginPath() - gfx.Rect(0,0,resx,resy) - gfx.FillColor(0,0,0, math.floor(127 * math.min(outroTimer, 1))) - gfx.Fill() - gfx.Scale(scale,scale) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FillColor(255,255,255, math.floor(255 * math.min(outroTimer, 1))) - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70) - gfx.Text(clearTexts[clearState], desw / 2, desh / 2) - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - outroTimer - else - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - end - -end --- -------------------------------------------------------------------------- -- --- update_score: -- -function update_score(newScore) - if newScore ~= score then - score_animation:restart(score_animation:tick(0), newScore, 0.33) - score = newScore - end -end --- -------------------------------------------------------------------------- -- --- update_combo: -- -function update_combo(newCombo) - combo = newCombo - comboScale = 1.5 -end --- -------------------------------------------------------------------------- -- --- near_hit: -- -function near_hit(wasLate) --for updating early/late display - late = wasLate - earlateTimer = 0.75 -end --- -------------------------------------------------------------------------- -- --- laser_alert: -- -function laser_alert(isRight) --for starting laser alert animations - if isRight and alertTimers[2] < -1.5 then - alertTimers[2] = 1.5 - elseif alertTimers[1] < -1.5 then - alertTimers[1] = 1.5 - end -end - - --- ======================== Start mutliplayer ======================== - -json = require "json" - -local normal_font = game.GetSkinSetting('multi.normal_font') -if normal_font == nil then - normal_font = 'NotoSans-Regular.ttf' -end -local mono_font = game.GetSkinSetting('multi.mono_font') -if mono_font == nil then - mono_font = 'NovaMono.ttf' -end - -local users = nil - -function init_tcp() - Tcp.SetTopicHandler("game.scoreboard", function(data) - users = {} - for i, u in ipairs(data.users) do - table.insert(users, u) - end - end) -end - - --- Hook the render function and draw the scoreboard -local real_render = render -render = function(deltaTime) - real_render(deltaTime) - draw_users(deltaTime) -end - --- Update the users in the scoreboard -function score_callback(response) - if response.status ~= 200 then - error() - return - end - local jsondata = json.decode(response.text) - users = {} - for i, u in ipairs(jsondata.users) do - table.insert(users, u) - end -end - --- Render scoreboard -function draw_users(detaTime) - if (users == nil) then - return - end - - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - if portrait then - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - yshift = desw * (bannerHeight / bannerWidth) - gfx.Scale(0.7, 0.7) - end - - gfx.Save() - - -- Add a small margin at the edge - gfx.Translate(5,yshift+200) - - -- Reset some text related stuff that was changed in draw_state - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) - gfx.FontSize(35) - gfx.FillColor(255, 255, 255) - local yoff = 0 - if portrait then - yoff = 75; - end - local rank = 0 - for i, u in ipairs(users) do - gfx.FillColor(255, 255, 255) - local score_big = string.format("%04d",math.floor(u.common/grades/1000)); - local score_small = string.format("%03d",u.score%1000); - local user_text = '('..u.name..')'; - - local size_big = 40; - local size_small = 28; - local size_name = 30; - - if u.id == gameplay.user_id then - size_big = 48 - size_small = 32 - size_name = 40 - rank = i; - end - - gfx.LoadSkinFont(mono_font) - gfx.FontSize(size_big) - gfx.Text(score_big, 0, yoff); - local xmin,ymin,xmax,ymax_big = gfx.TextBounds(0, yoff, score_big); - xmax = xmax + 7 - - gfx.FontSize(size_small) - gfx.Text(score_small, xmax, yoff); - xmin,ymin,xmax,ymax = gfx.TextBounds(xmax, yoff, score_small); - xmax = xmax + 7 - - if u.id == gameplay.user_id then - gfx.FillColor(237, 240, 144) - end - - gfx.LoadSkinFont(normal_font) - gfx.FontSize(size_name) - gfx.Text(user_text, xmax, yoff) - - yoff = ymax_big + 15 - end - - gfx.Restore() -end - --- ======================== Start practice mode ======================== -local is_playing_practice = false -local mission_str = "" - -local count_play = 0 -local count_success = 0 -local last_run = "" -local last_timing = "" - --- Called when the practice starts -function practice_start(mission_type, mission_threshold, mission_description) - is_playing_practice = true - - mission_str = string.format("Mission: %s", mission_description) -end - --- Called when a run for the practice is finished -function practice_end_run(playCount, successCount, isSuccessful, scoring) - count_play = playCount - count_success = successCount - last_run = string.format("Last run: %d (%d-%d)", scoring.score, scoring.goods, scoring.misses) - last_timing = string.format("Hit delta: %d±%dms", scoring.meanHitDelta, scoring.meanHitDeltaAbs) -end - --- Called when user enters the setup again -function practice_end(playCount, successCount) - is_playing_practice = false - - count_play = playCount - count_success = successCount -end - -function draw_practice(deltaTime) - if not is_playing_practice then - return - end - - gfx.Save() - gfx.Translate(practice_info.posx, practice_info.posy) - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FillColor(255, 255, 255) - - gfx.FontSize(25) - gfx.Text(mission_str, 0, 0) - - if count_play > 0 then - local play_stat = string.format("Clear rate: %d/%d (%.1f%%)", count_success, count_play, (100.0 * count_success / count_play)) - gfx.Text(play_stat, 0, 30) - - gfx.FontSize(20) - gfx.Text(last_run, 0, 55) - gfx.Text(last_timing, 0, 75) - end - - gfx.Restore() -end diff --git a/scripts/gameplay old.lua b/scripts/gameplay old.lua deleted file mode 100644 index 0ea9a7c..0000000 --- a/scripts/gameplay old.lua +++ /dev/null @@ -1,938 +0,0 @@ --- The following code slightly simplifies the render/update code, making it easier to explain in the comments --- It replaces a few of the functions built into USC and changes behaviour slightly --- Ideally, this should be in the common.lua file, but the rest of the skin does not support it --- I'll be further refactoring and documenting the default skin and making it more easy to --- modify for those who either don't know how to skin well or just want to change a few images --- or behaviours of the default to better suit them. --- Skinning should be easy and fun! - -local RECT_FILL = "fill" -local RECT_STROKE = "stroke" -local RECT_FILL_STROKE = RECT_FILL .. RECT_STROKE - -gfx._ImageAlpha = 1 - -gfx._FillColor = gfx.FillColor -gfx._StrokeColor = gfx.StrokeColor -gfx._SetImageTint = gfx.SetImageTint - --- we aren't even gonna overwrite it here, it's just dead to us -gfx.SetImageTint = nil - -function gfx.FillColor(r, g, b, a) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - a = math.floor(a or 255) - - gfx._ImageAlpha = a / 255 - gfx._FillColor(r, g, b, a) - gfx._SetImageTint(r, g, b) -end - -function gfx.StrokeColor(r, g, b) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - - gfx._StrokeColor(r, g, b) -end - -function gfx.DrawRect(kind, x, y, w, h) - local doFill = kind == RECT_FILL or kind == RECT_FILL_STROKE - local doStroke = kind == RECT_STROKE or kind == RECT_FILL_STROKE - - local doImage = not (doFill or doStroke) - - gfx.BeginPath() - - if doImage then - gfx.ImageRect(x, y, w, h, kind, gfx._ImageAlpha, 0) - else - gfx.Rect(x, y, w, h) - if doFill then gfx.Fill() end - if doStroke then gfx.Stroke() end - end -end - -local buttonStates = { } -local buttonsInOrder = { - game.BUTTON_BTA, - game.BUTTON_BTB, - game.BUTTON_BTC, - game.BUTTON_BTD, - - game.BUTTON_FXL, - game.BUTTON_FXR, - - game.BUTTON_STA, -} - -function UpdateButtonStatesAfterProcessed() - for i = 1, 6 do - local button = buttonsInOrder[i] - buttonStates[button] = game.GetButton(button) - end -end - -function game.GetButtonPressed(button) - return game.GetButton(button) and not buttonStates[button] -end --- -------------------------------------------------------------------------- -- --- game.IsUserInputActive: -- --- Used to determine if (valid) controller input is happening. -- --- Valid meaning that laser motion will not return true unless the laser is -- --- active in gameplay as well. -- --- This restriction is not applied to buttons. -- --- The player may press their buttons whenever and the function returns true. -- --- Lane starts at 1 and ends with 8. -- -function game.IsUserInputActive(lane) - if lane < 7 then - return game.GetButton(buttonsInOrder[lane]) - end - return gameplay.IsLaserHeld(lane - 7) -end --- -------------------------------------------------------------------------- -- --- gfx.FillLaserColor: -- --- Sets the current fill color to the laser color of the given index. -- --- An optional alpha value may be given as well. -- --- Index may be 1 or 2. -- -function gfx.FillLaserColor(index, alpha) - alpha = math.floor(alpha or 255) - local r, g, b = game.GetLaserColor(index - 1) - gfx.FillColor(r, g, b, alpha) -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 - --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- The actual gameplay script starts here! -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local resx_old = 0 -local resy_old = 0 -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- All images used by the script: -- -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) -local bottomFill = gfx.CreateSkinImage("console/console.png", 0) -local topFill = gfx.CreateSkinImage("fill_top.png", 0) -local critAnim = gfx.CreateSkinImage("crit_anim.png", 0) -local critBar = gfx.CreateSkinImage("crit_bar.png", 0) -local critConsole = gfx.CreateSkinImage("console/crit_console.png", 0) -local laserTail = gfx.CreateSkinImage("laser_tail.png", 0) -local laserCursor = gfx.CreateSkinImage("pointer.png", 0) -local laserCursorText = gfx.CreateSkinImage("pointer_bottom.png", 0) -local laserCursorOverlay = gfx.CreateSkinImage("pointer_overlay.png", 0) -local laserCursorGlow = gfx.CreateSkinImage("pointer_glow.png", 0) -local laserCursorShine = gfx.CreateSkinImage("pointer_shine.png", 0) -local laserTopWave = gfx.CreateSkinImage("laser_top_wave.png", 0) -local scoreEarly = gfx.CreateSkinImage("score_early.png", 0) -local scoreLate = gfx.CreateSkinImage("score_late.png", 0) -local numberImages = load_number_image("number") - -local prevGaugeType = nil -local gaugeTransition = nil - ---Skin Settings info -local username = game.GetSkinSetting('username') or ''; - -local ioConsoleDetails = { - gfx.CreateSkinImage("console/detail_left.png", 0), - gfx.CreateSkinImage("console/detail_right.png", 0), -} - -local consoleAnimImages = { - gfx.CreateSkinImage("console/glow_bta.png", 0), - gfx.CreateSkinImage("console/glow_btb.png", 0), - gfx.CreateSkinImage("console/glow_btc.png", 0), - gfx.CreateSkinImage("console/glow_btd.png", 0), - - gfx.CreateSkinImage("console/glow_fxl.png", 0), - gfx.CreateSkinImage("console/glow_fxr.png", 0), - - gfx.CreateSkinImage("console/glow_voll.png", 0), - gfx.CreateSkinImage("console/glow_volr.png", 0), -} --- -------------------------------------------------------------------------- -- --- Timers, used for animations: -- -local introTimer = 2 -local outroTimer = 0 - -local earlateTimer = 0 -local critAnimTimer = 0 - -local consoleAnimSpeed = 10 -local consoleAnimTimers = { 0, 0, 0, 0, 0, 0, 0, 0 } --- -------------------------------------------------------------------------- -- --- Miscelaneous, currently unsorted: -- -local score = 0 -local jacket = nil -local critLinePos = { 0.95, 0.75 }; -local late = false -local clearTexts = {"TRACK FAILED", "TRACK COMPLETE", "TRACK COMPLETE", "FULL COMBO", "PERFECT" } --- -------------------------------------------------------------------------- -- --- ResetLayoutInformation: -- --- Resets the layout values used by the skin. -- -function ResetLayoutInformation() - resx, resy = game.GetResolution() - portrait = resy > resx - desw = portrait and 1080 or 1920 - desh = desw * (resy / resx) - scale = resx / desw -end --- -------------------------------------------------------------------------- -- --- render: -- --- The primary & final render call. -- --- Use this to render basically anything that isn't the crit line or the -- --- intro/outro transitions. -- -function render(deltaTime) - -- make sure that our transform is cleared, clean working space - -- TODO: this shouldn't be necessary!!! - gfx.ResetTransform() - - -- While the intro timer is running, we fade in from black - if introTimer > 0 then - gfx.FillColor(0, 0, 0, math.floor(255 * math.min(introTimer, 1))) - gfx.DrawRect(RECT_FILL, 0, 0, resx, resy) - end - - gfx.Scale(scale, scale) - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - -- TODO: this isn't how it'll work in the long run, I don't think - if portrait then yshift = draw_banner(deltaTime) end - - -- gfx.Translate(0, yshift - 150 * math.max(introTimer - 1, 0)) - gfx.Translate(0, yshift) - draw_song_info(deltaTime) - draw_score(deltaTime) - -- gfx.Translate(0, -yshift + 150 * math.max(introTimer - 1, 0)) - gfx.Translate(0, -yshift) - draw_status(deltaTime) - draw_gauge(deltaTime) - draw_earlate(deltaTime) - draw_combo(deltaTime) - draw_alerts(deltaTime) -end --- -------------------------------------------------------------------------- -- --- SetUpCritTransform: -- --- Utility function which aligns the graphics transform to the center of the -- --- crit line on screen, rotation include. -- --- This function resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function SetUpCritTransform() - -- start us with a clean empty transform - gfx.ResetTransform() - -- translate and rotate accordingly - gfx.Translate(gameplay.critLine.x, gameplay.critLine.y) - gfx.Rotate(-gameplay.critLine.rotation) -end --- -------------------------------------------------------------------------- -- --- GetCritLineCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- crit line on the screen based on its position and rotation. -- -function GetCritLineCenteringOffset() - local distFromCenter = resx / 2 - gameplay.critLine.x - local dvx = math.cos(gameplay.critLine.rotation) - local dvy = math.sin(gameplay.critLine.rotation) - return math.sqrt(dvx * dvx + dvy * dvy) * distFromCenter -end --- -------------------------------------------------------------------------- -- --- render_crit_base: -- --- Called after rendering the highway and playable objects, but before -- --- the built-in hit effects. -- --- This is the first render function to be called each frame. -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_base(deltaTime) - -- Kind of a hack, but here (since this is the first render function - -- that gets called per frame) we update the layout information. - -- This means that the player can resize their window and - -- not break everything - ResetLayoutInformation() - - critAnimTimer = critAnimTimer + deltaTime - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen - local xOffset = GetCritLineCenteringOffset() - gfx.Translate(xOffset, 0) - - -- Draw a transparent black overlay below the crit line - -- This darkens the play area as it passes - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, -resx, 0, resx * 2, resy) - gfx.FillColor(255, 255, 255) - - -- The absolute width of the crit line itself - -- we check to see if we're playing in portrait mode and - -- change the width accordingly - local critWidth = resx * (portrait and 1.25 or 0.8) - - -- get the scaled dimensions of the crit line pieces - local clw, clh = gfx.ImageSize(critAnim) - local critAnimHeight = 9 * scale - local critAnimWidth = critAnimHeight * (clw / clh) - - local cbw, cbh = gfx.ImageSize(critBar) - local critBarHeight = critAnimHeight * (cbh / clh) - local critBarWidth = critBarHeight * (cbw / cbh) - - -- render the core of the crit line - do - -- The crit line is made up of many small pieces scrolling outward - -- Calculate how many pieces, starting at what offset, are require to - -- completely fill the space with no gaps from edge to center - local animWidth = critWidth * 0.65 - local numPieces = 1 + math.ceil(animWidth / (critAnimWidth * 2)) - local startOffset = critAnimWidth * ((critAnimTimer * 0.15) % 1) - - -- left side - -- Use a scissor to limit the drawable area to only what should be visible - gfx.Scissor(-animWidth / 2, -critAnimHeight / 2, animWidth / 2, critAnimHeight) - for i = 1, numPieces do - gfx.DrawRect(critAnim, -startOffset - critAnimWidth * (i - 1), -critAnimHeight / 2, critAnimWidth, critAnimHeight) - end - gfx.ResetScissor() - - -- right side - -- exactly the same, but in reverse - gfx.Scissor(0, -critAnimHeight / 2, animWidth / 2, critAnimHeight) - for i = 1, numPieces do - gfx.DrawRect(critAnim, -critAnimWidth + startOffset + critAnimWidth * (i - 1), -critAnimHeight / 2, critAnimWidth, critAnimHeight) - end - gfx.ResetScissor() - end - - -- Draw the critical bar - gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale, critWidth, critBarHeight) - - -- Draw back portion of the console - if portrait then - local ccw, cch = gfx.ImageSize(critConsole) - local critConsoleHeight = 190 * scale - local critConsoleWidth = critConsoleHeight * (ccw / cch) - - local critConsoleY = 180 * scale - gfx.DrawRect(critConsole, -critConsoleWidth / 2, -critConsoleHeight / 2 + critConsoleY, critConsoleWidth, critConsoleHeight) - end - - -- we're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- render_crit_overlay: -- --- Called after rendering built-int crit line effects. -- --- Use this to render laser cursors or an IO Console in portrait mode! -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_overlay(deltaTime) - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen. - local xOffset = GetCritLineCenteringOffset() - - -- When in portrait, we can draw the console at the bottom - if portrait then - -- We're going to make temporary modifications to the transform - gfx.Save() - gfx.Translate(xOffset * 0.5, -45) - - local bfw, bfh = gfx.ImageSize(bottomFill) - - local distBetweenKnobs = 0.446 - local distCritVertical = -0.125 - - local ioFillTx = bfw / 2 - local ioFillTy = bfh * distCritVertical -- 0.098 - - -- The total dimensions for the console image - local io_x, io_y, io_w, io_h = -ioFillTx, -ioFillTy, bfw, bfh - - -- Adjust the transform accordingly first - local consoleFillScale = (resx * 0.550) / (bfw * distBetweenKnobs) - gfx.Scale(consoleFillScale, consoleFillScale); - - -- Actually draw the fill - gfx.FillColor(255, 255, 255) - gfx.DrawRect(bottomFill, io_x, io_y, io_w, io_h) - - -- Then draw the details which need to be colored to match the lasers - -- for i = 1, 2 do - -- gfx.FillLaserColor(i) - -- gfx.DrawRect(ioConsoleDetails[i], io_x, io_y, io_w, io_h) - -- end - - -- Draw the button press animations by overlaying transparent images - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - for i = 1, 6 do - -- While a button is held, increment a timer - -- If not held, that timer is set back to 0 - if game.GetButton(buttonsInOrder[i]) then - consoleAnimTimers[i] = consoleAnimTimers[i] + deltaTime * consoleAnimSpeed * 3.14 * 2 - else - consoleAnimTimers[i] = 0 - end - - -- If the timer is active, flash based on a sin wave - local timer = consoleAnimTimers[i] - if timer ~= 0 then - local image = consoleAnimImages[i] - local alpha = (math.sin(timer) * 0.5 + 0.5) * 0.5 + 0.25 - gfx.FillColor(255, 255, 255, alpha * 255); - gfx.DrawRect(image, io_x, io_y, io_w, io_h) - end - end - gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER) - - -- Undo those modifications - gfx.Restore(); - end - - local cw, ch = gfx.ImageSize(laserCursor) - local cursorWidth = 60 * scale - local cursorHeight = cursorWidth * (ch / cw) - - -- draw each laser cursor - for i = 1, 2 do - local cursor = gameplay.critLine.cursors[i - 1] - local pos, skew = cursor.pos, cursor.skew - - gfx.Save(); - -- Add a kinda-perspective effect with a horizontal skew - gfx.SkewX(skew) - - --Add the tail, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 255) - gfx.DrawRect(laserTail, pos - cursorWidth / 2 - 64, -cursorHeight / 2 - 5, cursorWidth * 5, cursorHeight * 5) - end - - -- Draw the SDVX Icon eye and tails below the overlay - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorText, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored background with the appropriate laser color - gfx.FillLaserColor(i, cursor.alpha * 130) - gfx.DrawRect(laserCursor, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - - --Add the top wave effect, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 180) - gfx.DrawRect(laserTopWave, pos - cursorWidth / 2 - 80, -cursorHeight / 2 - 24, cursorWidth * 6, cursorHeight * 6) - end - - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorOverlay, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored glow on top of the pointer - gfx.FillLaserColor(i, cursor.alpha * 160) - gfx.DrawRect(laserCursorGlow, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 150) - gfx.DrawRect(laserCursorShine, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - - - -- Un-skew - gfx.SkewX(-skew) - gfx.Restore(); - end - - -- We're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- draw_banner: -- --- Renders the banner across the top of the screen in portrait. -- --- This function expects no graphics transform except the design scale. -- -function draw_banner(deltaTime) - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - local actualHeight = desw * (bannerHeight / bannerWidth) - - gfx.FillColor(255, 255, 255) - gfx.DrawRect(topFill, 0, 0, desw, actualHeight) - - return actualHeight -end --- -------------------------------------------------------------------------- -- --- draw_stat: -- --- Draws a formatted name + value combination at x, y over w, h area. -- -function draw_stat(x, y, w, h, name, value, format, r, g, b) - gfx.Save() - - -- Translate from the parent transform, wherever that may be - gfx.Translate(x, y) - - -- Draw the `name` top-left aligned at `h` size - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FontSize(h) - gfx.Text(name .. ":", 0, 0) -- 0, 0, is x, y after translation - - -- Realign the text and draw the value, formatted - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format(format, value), w, 0) - -- This draws an underline beneath the text - -- The line goes from 0, h to w, h - gfx.BeginPath() - gfx.MoveTo(0, h) - gfx.LineTo(w, h) -- only defines the line, does NOT draw it yet - - -- If a color is provided, set it - if r then gfx.StrokeColor(r, g, b) - -- otherwise, default to a light grey - else gfx.StrokeColor(200, 200, 200) end - - -- Stroke out the line - gfx.StrokeWidth(1) - gfx.Stroke() - -- Undo our transform changes - gfx.Restore() - - -- Return the next `y` position, for easier vertical stacking - return y + h + 5 -end --- -------------------------------------------------------------------------- -- --- draw_song_info: -- --- Draws current song information at the top left of the screen. -- --- This function expects no graphics transform except the design scale. -- -local songBack = gfx.CreateSkinImage("song_back.png", 0) -local numberDot = gfx.CreateSkinImage("number/dot.png", 0) -local diffImages = { - 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) -} -local memo = Memo.new() - -function draw_song_info(deltaTime) - local jacketWidth = 105 - - -- Check to see if there's a jacket to draw, and attempt to load one if not - if jacket == nil or jacket == jacketFallback then - jacket = gfx.LoadImageJob(gameplay.jacketPath, jacketFallback) - end - gfx.Save() - - if not portrait then - gfx.Translate(0, 112) - end - - -- Ensure the font has been loaded - gfx.LoadSkinFont("segoeui.ttf") - - -- Draw the background - local tw, th = gfx.ImageSize(songBack) - gfx.FillColor(255,255,255) - gfx.BeginPath() - gfx.ImageRect(-2, -71, tw * 0.855, th * 0.855, songBack, 1, 0) - - -- Draw the jacket - gfx.BeginPath() - gfx.ImageRect(31, -39, jacketWidth, jacketWidth, jacket, 1, 0) - - -- Draw level name - local diffIdx = GetDisplayDifficulty(gameplay.jacketPath, gameplay.difficulty) - gfx.BeginPath() - tw, th = gfx.ImageSize(diffImages[diffIdx]) - gfx.ImageRect(28, 71, tw * 0.85, th * 0.85, diffImages[diffIdx], 1, 0) - - -- Draw level number - draw_number(110, 84, 1.0, gameplay.level, 2, numberImages, false) - - -- Draw the song title, scaled to fit as best as possible - local title = memo:memoize("title", function () - local titleText = gameplay.title .. " / " .. gameplay.artist - local titleWidth = 520 - gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf") - return gfx.CreateLabel(titleText, 18, 0) - end) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE) - gfx.FillColor(255, 255, 255, 255) - gfx.DrawLabel(title, desw / 2.77, portrait and -23 or -90, 470) - - -- Draw the BPM - gfx.FillColor(255,255,255) - draw_number(220, 178, 1.0, gameplay.bpm, 3, numberImages, false) - - -- Draw the hi-speed - gfx.FontSize(16) - draw_number(213 + 20, 212, 1.0, math.floor((gameplay.hispeed + 0.05) * 10) % 10, 1, numberImages, false) - tw, th = gfx.ImageSize(numberDot) - gfx.BeginPath() - gfx.ImageRect(213 + 5, 206, tw, th, numberDot, 1, 0) - draw_number(213, 212, 1.0, math.floor(gameplay.hispeed), 1, numberImages, false) - -- gfx.Text(string.format("%.1f", gameplay.hispeed), 208, 9) - - -- Fill the progress bar - gfx.BeginPath() - gfx.FillColor(244, 204, 101) - gfx.Rect(233, 11, 625 * gameplay.progress, 3) - gfx.Fill() - - -- When the player is holding Start, the hispeed can be changed - -- Shows the current hispeed values - if game.GetButton(game.BUTTON_STA) then - gfx.BeginPath() - gfx.FillColor(255,255,255) - gfx.Text(string.format("HiSpeed: %.0f x %.1f = %.0f", - gameplay.bpm, gameplay.hispeed, gameplay.bpm * gameplay.hispeed), - 0, 115) - end - gfx.Restore() -end --- -------------------------------------------------------------------------- -- --- draw_best_diff: -- --- If there are other saved scores, this displays the difference between -- --- the current play and your best. -- -function draw_best_diff(deltaTime, x, y) - -- Don't do anything if there's nothing to do - if not gameplay.scoreReplays[1] then return end - - -- Calculate the difference between current and best play - local difference = score - gameplay.scoreReplays[1].currentScore - local prefix = "" -- used to properly display negative values - - gfx.BeginPath() - gfx.FontSize(26) - - gfx.FillColor(255, 255, 255) - if difference < 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(255, 90, 70) - difference = math.abs(difference) - prefix = "- " - - elseif difference > 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(170, 160, 255) - difference = math.abs(difference) - prefix = "+ " - end - - -- %08d formats a number to 8 characters - -- This includes the minus sign, so we do that separately - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format("%s%08d", prefix, difference), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - --- -------------------------------------------------------------------------- -- --- draw_score: -- -local scoreBack = gfx.CreateSkinImage("score_back.png", 0) -local scoreNumber = load_number_image("score_num") -local maxCombo = 0 -function draw_score(deltaTime) - local tw, th = gfx.ImageSize(scoreBack) - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - tw = tw * 0.61; - th = th * 0.61; - gfx.ImageRect(desw - tw + 12, portrait and 50 or 0, tw, th, scoreBack, 1, 0) - - gfx.FillColor(255, 255, 255) - draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.40, 1.12) - draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.3, 1.12) - - -- Draw max combo - gfx.FillColor(255, 255, 255) - draw_number(desw - 300, portrait and 207 or 110, 1.0, maxCombo, 4, numberImages, true) -end --- -------------------------------------------------------------------------- -- --- draw_gauge: -- -local gaugeNumBack = gfx.CreateSkinImage("gauge_num_back.png", 0) -gfx.SetGaugeColor(0, 47, 244, 255) --Normal gauge fail -gfx.SetGaugeColor(1, 252, 76, 171) --Normal gauge clear -gfx.SetGaugeColor(2, 255, 255, 255) --Hard gauge low (<30%) -gfx.SetGaugeColor(3, 255, 255, 255) --Hard gauge high (>30%) - -function draw_gauge(deltaTime) - local height = 702 * scale / 1 - local width = 82 * scale / 1 - local posy = resy / 2 - height / 2 + 60 - local posx = resx - width - if portrait then - posy = posy - 90 - posx = resx - width - end - gfx.DrawGauge(gameplay.gauge, posx, posy, width, height, deltaTime) - - --draw gauge % label - posx = posx / scale - posx = posx + (-20 * 0.5) - -- 630 = 0% position - height = 960 * 0.5 - posy = posy / scale - - local tw, th = gfx.ImageSize(gaugeNumBack) - -- 80 = 100% position - posy = posy + (95 * 0.5) + height - height * gameplay.gauge - -- Draw the background - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.ImageRect(posx - 44, posy - 10, tw, th, gaugeNumBack, 1, 0) - - gfx.BeginPath() - -- gfx.FillColor(250, 228, 112) - draw_number(posx - 24, posy + 4, 1.0, math.floor(gameplay.gauge * 100), 3, numberImages, true) - -- gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) - -- gfx.FontSize(18) - -- gfx.Text(string.format("%d", math.floor(gameplay.gauge * 100)), posx, posy + 4) -end --- -------------------------------------------------------------------------- -- --- draw_combo: -- -local comboBottom = gfx.CreateSkinImage("chain/chain.png", 0) -local comboPUC = load_number_image("chain/puc") -local comboUC = load_number_image("chain/uc") -local comboREG = load_number_image("chain/reg") -local comboTimer = 0 -local combo = 0 -local comboCurrent -function draw_combo(deltaTime) - if combo == 0 then return end - comboTimer = comboTimer + deltaTime - local posx = desw / 2 + 5 - local posy = desh * critLinePos[1] - 100 - if portrait then posy = desh * critLinePos[2] - 150 end - if gameplay.comboState == 2 then - comboCurrent = comboPUC --puc - elseif gameplay.comboState == 1 then - comboCurrent = comboUC --uc - else - comboCurrent = comboREG --regular - end - local alpha = math.floor(comboTimer * 20) % 2 - alpha = (alpha * 100 + 155) / 255 - - -- \_ chain _/ - local tw, th - tw, th = gfx.ImageSize(comboBottom) - gfx.BeginPath() - gfx.ImageRect(posx - tw / 2 - 5, posy - th / 2 - 270, tw, th, comboBottom, alpha, 0) - - tw, th = gfx.ImageSize(comboCurrent[1]) - posy = posy - th - - local digit = combo % 10 - gfx.BeginPath() - gfx.ImageRect(posx + 70, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], alpha, 0) - - digit = math.floor(combo / 10) % 10 - gfx.BeginPath() - gfx.ImageRect(posx, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 10 and alpha or 0.2, 0) - - digit = math.floor(combo / 100) % 10 - gfx.BeginPath() - gfx.ImageRect(posx - 70, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 100 and alpha or 0.2, 0) - - digit = math.floor(combo / 1000) % 10 - gfx.BeginPath() - gfx.ImageRect(posx - 140, posy - th / 2, tw * 0.5, th * 0.5, comboCurrent[digit + 1], combo >= 1000 and alpha or 0.2, 0) -end --- -------------------------------------------------------------------------- -- --- draw_earlate: -- -function draw_earlate(deltaTime) - earlateTimer = math.max(earlateTimer - deltaTime,0) - if earlateTimer == 0 then return nil end - local alpha = math.floor(earlateTimer * 20) % 2 - alpha = (alpha * 100 + 155) / 255 - gfx.BeginPath() - - local xpos = desw / 2 - local ypos = desh * critLinePos[1] - 220 - if portrait then ypos = desh * critLinePos[2] - 240 end - local tw, th - if late then - tw, th = gfx.ImageSize(scoreLate) - gfx.ImageRect(xpos - tw / 2, ypos - th / 2, tw, th, scoreLate, alpha, 0) - else - tw, th = gfx.ImageSize(scoreEarly) - gfx.ImageRect(xpos - tw / 2, ypos - th / 2, tw, th, scoreEarly, alpha, 0) - end -end --- -------------------------------------------------------------------------- -- --- draw_alerts: -- -local alertTimers = {-2,-2} -local alertBgR = Image.skin("alert_bg.png") -local alertBgL = Image.skin("alert_bg2.png") -local alertL = Image.skin("alert_l.png") -local alertR = Image.skin("alert_r.png") - -function draw_alerts(deltaTime) - alertTimers[1] = math.max(alertTimers[1] - deltaTime,-2) - alertTimers[2] = math.max(alertTimers[2] - deltaTime,-2) - if alertTimers[1] > 0 then --draw left alert - gfx.Save() - local posx = desw / 2 - 220 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 240 - posx = 105 - end - gfx.Translate(posx,posy) - local r,g,b = game.GetLaserColor(0) - local alertScale = (-(alertTimers[1] ^ 2.0) + (1.5 * alertTimers[1])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.FillColor(r, g, b) - alertBgL:draw({ x = 0, y = 0 }) - gfx.FillColor(255, 255, 255) - alertL:draw({ x = 0, y = 0 }) - gfx.Restore() - end - if alertTimers[2] > 0 then --draw right alert - gfx.Save() - local posx = desw / 2 + 220 - local posy = desh * critLinePos[1] - 40 - if portrait then - posy = desh * critLinePos[2] - 240 - posx = desw - 105 - end - gfx.Translate(posx,posy) - local r,g,b = game.GetLaserColor(1) - local alertScale = (-(alertTimers[2] ^ 2.0) + (1.5 * alertTimers[2])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.FillColor(r, g, b) - alertBgR:draw({ x = 0, y = 0 }) - gfx.FillColor(255, 255, 255) - alertR:draw({ x = 0, y = 0 }) - gfx.Restore() - end -end --- -------------------------------------------------------------------------- -- --- draw_status: -- -local statusBack = Image.skin("status_back.png") -local apealCard = Image.skin("appeal_card.png") -local dan = Image.skin("dan.png") -local volforce = Image.skin ("volforce.png") -function draw_status(deltaTime) - -- Draw the background - gfx.FillColor(255, 255, 255) - statusBack:draw({ x = 0, y = desh / 2 - 195, w = statusBack.w * 0.85, h = statusBack.h * 0.85, anchor_h = Image.ANCHOR_LEFT }) - - -- Draw the apeal card - apealCard:draw({ x = 12, y = desh / 2 - 220, w = apealCard.w * 0.62, h = apealCard.h * 0.62, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP }) - - -- Draw the dan - dan:draw({ x = 164, y = desh / 2 - 117, w = dan.w * 0.32, h = dan.h * 0.32 }) - - -- Draw the Volforce - volforce:draw({ x = 240, y = desh / 2 - 119, w = volforce.w * 0.12, h = volforce.h * 0.12 }) - - -- Draw the best difference - draw_best_diff(deltaTime, 145, desh / 2 - 175) - - -- Draw the username - draw_username(deltatime, 145, desh / 2 - 198) -end - --- -------------------------------------------------------------------------- -- --- render_intro: -- -function render_intro(deltaTime) - if not game.GetButton(game.BUTTON_STA) then - introTimer = introTimer - deltaTime - end - introTimer = math.max(introTimer, 0) - return introTimer <= 0 -end --- -------------------------------------------------------------------------- -- --- render_outro: -- -function render_outro(deltaTime, clearState) - if clearState == 0 then return true end - gfx.ResetTransform() - gfx.BeginPath() - gfx.Rect(0,0,resx,resy) - gfx.FillColor(0,0,0, math.floor(127 * math.min(outroTimer, 1))) - gfx.Fill() - gfx.Scale(scale,scale) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FillColor(255,255,255, math.floor(255 * math.min(outroTimer, 1))) - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70) - gfx.Text(clearTexts[clearState], desw / 2, desh / 2) - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - outroTimer -end --- -------------------------------------------------------------------------- -- --- update_score: -- -function update_score(newScore) - score = newScore -end --- -------------------------------------------------------------------------- -- --- update_combo: -- -function update_combo(newCombo) - combo = newCombo - if combo > maxCombo then - maxCombo = combo - end -end --- -------------------------------------------------------------------------- -- --- near_hit: -- -function near_hit(wasLate) --for updating early/late display - late = wasLate - earlateTimer = 0.75 -end --- -------------------------------------------------------------------------- -- --- laser_alert: -- -function laser_alert(isRight) --for starting laser alert animations - if isRight and alertTimers[2] < -1.5 then - alertTimers[2] = 1.5 - elseif alertTimers[1] < -1.5 then - alertTimers[1] = 1.5 - end -end \ No newline at end of file diff --git a/scripts/gameplay.lua b/scripts/gameplay.lua index 19161fc..a67e380 100644 --- a/scripts/gameplay.lua +++ b/scripts/gameplay.lua @@ -143,8 +143,6 @@ end -- -------------------------------------------------------------------------- -- -- Global data used by many things: -- local resx, resy -- The resolution of the window -local resx_old = 0 -local resy_old = 0 local portrait -- whether the window is in portrait orientation local desw, desh -- The resolution of the deisign local scale -- the scale to get from design to actual units @@ -226,14 +224,8 @@ function render(deltaTime) -- make sure that our transform is cleared, clean working space -- TODO: this shouldn't be necessary!!! gfx.ResetTransform() - - -- While the intro timer is running, we fade in from black - if introTimer > 0 then - gfx.FillColor(0, 0, 0, math.floor(255 * math.min(introTimer, 1))) - gfx.DrawRect(RECT_FILL, 0, 0, resx, resy) - end - gfx.Scale(scale, scale) + local yshift = 0 -- In portrait, we draw a banner across the top @@ -311,7 +303,7 @@ function render_crit_base(deltaTime) -- get the scaled dimensions of the crit line pieces local clw, clh = gfx.ImageSize(critAnim) - local critAnimHeight = 9 * scale + local critAnimHeight = 12 * scale local critAnimWidth = critAnimHeight * (clw / clh) local cbw, cbh = gfx.ImageSize(critBar) @@ -345,7 +337,7 @@ function render_crit_base(deltaTime) end -- Draw the critical bar - gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale, critWidth, critBarHeight) + gfx.DrawRect(critBar, -critWidth / 2, -critBarHeight / 2 - 5 * scale + 24, critWidth, critBarHeight) -- Draw back portion of the console if portrait then diff --git a/scripts/gameplay2.lua b/scripts/gameplay2.lua deleted file mode 100644 index ab2a7cb..0000000 --- a/scripts/gameplay2.lua +++ /dev/null @@ -1,1436 +0,0 @@ --- The following code slightly simplifies the render/update code, making it easier to explain in the comments --- It replaces a few of the functions built into USC and changes behaviour slightly --- Ideally, this should be in the common.lua file, but the rest of the skin does not support it --- I'll be further refactoring and documenting the default skin and making it more easy to --- modify for those who either don't know how to skin well or just want to change a few images --- or behaviours of the default to better suit them. --- Skinning should be easy and fun! - - --- Animation functions begin -function clamp(x, min, max) - if x < min then - x = min - end - if x > max then - x = max - end - - return x -end - -function smootherstep(edge0, edge1, x) - -- Scale, and clamp x to 0..1 range - x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0) - -- Evaluate polynomial - return x * x * x * (x * (x * 6 - 15) + 10) -end - -function to_range(val, start, stop) - return start + (stop - start) * val -end - -Animation = { - start = 0, - stop = 0, - progress = 0, - duration = 1, - smoothStart = false -} - -function Animation:new(o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o -end - -function Animation:restart(start, stop, duration) - self.progress = 0 - self.start = start - self.stop = stop - self.duration = duration -end - -function Animation:tick(deltaTime) - self.progress = math.min(1, self.progress + deltaTime / self.duration) - if self.progress == 1 then return self.stop end - if self.smoothStart then - return to_range(smootherstep(0, 1, self.progress), self.start, self.stop) - else - return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop) - end -end ---- Animation Functions end - - -local RECT_FILL = "fill" -local RECT_STROKE = "stroke" -local RECT_FILL_STROKE = RECT_FILL .. RECT_STROKE - -gfx._ImageAlpha = 1 -if gfx._FillColor == nil then - gfx._FillColor = gfx.FillColor - gfx._StrokeColor = gfx.StrokeColor - gfx._SetImageTint = gfx.SetImageTint -end - --- we aren't even gonna overwrite it here, it's just dead to us -gfx.SetImageTint = nil - -function gfx.FillColor(r, g, b, a) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - a = math.floor(a or 255) - - gfx._ImageAlpha = a / 255 - gfx._FillColor(r, g, b, a) - gfx._SetImageTint(r, g, b) -end - -function gfx.StrokeColor(r, g, b) - r = math.floor(r or 255) - g = math.floor(g or 255) - b = math.floor(b or 255) - - gfx._StrokeColor(r, g, b) -end - -function gfx.DrawRect(kind, x, y, w, h) - local doFill = kind == RECT_FILL or kind == RECT_FILL_STROKE - local doStroke = kind == RECT_STROKE or kind == RECT_FILL_STROKE - - local doImage = not (doFill or doStroke) - - gfx.BeginPath() - - if doImage then - gfx.ImageRect(x, y, w, h, kind, gfx._ImageAlpha, 0) - else - gfx.Rect(x, y, w, h) - if doFill then gfx.Fill() end - if doStroke then gfx.Stroke() end - end -end - -local buttonStates = { } -local buttonsInOrder = { - game.BUTTON_BTA, - game.BUTTON_BTB, - game.BUTTON_BTC, - game.BUTTON_BTD, - - game.BUTTON_FXL, - game.BUTTON_FXR, - - game.BUTTON_STA, -} - -function UpdateButtonStatesAfterProcessed() - for i = 1, 6 do - local button = buttonsInOrder[i] - buttonStates[button] = game.GetButton(button) - end -end - -function game.GetButtonPressed(button) - return game.GetButton(button) and not buttonStates[button] -end --- -------------------------------------------------------------------------- -- --- game.IsUserInputActive: -- --- Used to determine if (valid) controller input is happening. -- --- Valid meaning that laser motion will not return true unless the laser is -- --- active in gameplay as well. -- --- This restriction is not applied to buttons. -- --- The player may press their buttons whenever and the function returns true. -- --- Lane starts at 1 and ends with 8. -- -function game.IsUserInputActive(lane) - if lane < 7 then - return game.GetButton(buttonsInOrder[lane]) - end - return gameplay.IsLaserHeld(lane - 7) -end --- -------------------------------------------------------------------------- -- --- gfx.FillLaserColor: -- --- Sets the current fill color to the laser color of the given index. -- --- An optional alpha value may be given as well. -- --- Index may be 1 or 2. -- -function gfx.FillLaserColor(index, alpha) - alpha = math.floor(alpha or 255) - local r, g, b = game.GetLaserColor(index - 1) - gfx.FillColor(r, g, b, alpha) -end --- -------------------------------------------------------------------------- -- --- load_number_image: -- --- Loads numbers from files to allow usage of multi-colored numbers, custom -- --- fonts, and many other things -- -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 --- -------------------------------------------------------------------------- -- --- draw_number: -- --- Draws numbers from images in the skin. -- --- Additional values control scaling and spacing. -- -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 - --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- The actual gameplay script starts here! -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- Global data used by many things: -- -local resx, resy -- The resolution of the window -local resx_old = 0 -local resy_old = 0 -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- All images used by the script: -- -local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0) -local bottomFill = gfx.CreateSkinImage("console/console.png", 0) -local topFill = gfx.CreateSkinImage("fill_top.png", 0) -local critAnimImg = gfx.CreateSkinImage("crit_anim.png", gfx.IMAGE_REPEATX) -local critAnim = gfx.ImagePattern(0,-50,100,100,0,critAnimImg,1) -local critBar = gfx.CreateSkinImage("crit_bar.png", 0) -local critConsole = gfx.CreateSkinImage("console/crit_console.png", 0) -local critCap = gfx.CreateSkinImage("crit_cap.png", 0) -local critCapBack = gfx.CreateSkinImage("crit_cap_back.png", 0) -local laserTail = gfx.CreateSkinImage("laser_tail.png", 0) -local laserCursor = gfx.CreateSkinImage("pointer.png", 0) -local laserCursorText = gfx.CreateSkinImage("pointer_bottom.png", 0) -local laserCursorOverlay = gfx.CreateSkinImage("pointer_overlay.png", 0) -local laserCursorGlow = gfx.CreateSkinImage("pointer_glow.png", 0) -local laserCursorShine = gfx.CreateSkinImage("pointer_shine.png", 0) -local laserTopWave = gfx.CreateSkinImage("laser_top_wave.png", 0) -local scoreEarly = gfx.CreateSkinImage("score_early.png", 0) -local scoreLate = gfx.CreateSkinImage("score_late.png", 0) -local numberImages = load_number_image("number") - -local prevGaugeType = nil -local gaugeTransition = nil - ---Skin Settings info -local username = game.GetSkinSetting('username') or ''; - -local ioConsoleDetails = { - gfx.CreateSkinImage("console/detail_left.png", 0), - gfx.CreateSkinImage("console/detail_right.png", 0), -} - -local consoleAnimImages = { - gfx.CreateSkinImage("console/glow_bta.png", 0), - gfx.CreateSkinImage("console/glow_btb.png", 0), - gfx.CreateSkinImage("console/glow_btc.png", 0), - gfx.CreateSkinImage("console/glow_btd.png", 0), - - gfx.CreateSkinImage("console/glow_fxl.png", 0), - gfx.CreateSkinImage("console/glow_fxr.png", 0), - - gfx.CreateSkinImage("console/glow_voll.png", 0), - gfx.CreateSkinImage("console/glow_volr.png", 0), -} --- -------------------------------------------------------------------------- -- -local resx, resy -- The resolution of the window -local portrait -- whether the window is in portrait orientation -local desw, desh -- The resolution of the deisign -local scale -- the scale to get from design to actual units --- -------------------------------------------------------------------------- -- --- Timers, used for animations: -- -if introTimer == nil then - introTimer = 2 - outroTimer = 0 -end -local alertTimers = {-2,-2} - -local earlateTimer = 0 -local critAnimTimer = 0 - -local consoleAnimSpeed = 10 -local consoleAnimTimers = { 0, 0, 0, 0, 0, 0, 0, 0 } --- -------------------------------------------------------------------------- -- --- Miscelaneous, currently unsorted: -- -local score = 0 -local combo = 0 -local jacket = nil -local critLinePos = { 0.95, 0.75 }; -local comboScale = 1.0 -local late = false -local diffNames = {"NOV", "ADV", "EXH", "MXM", "INF", "GRV", "HVN", "VVD"} -local clearTexts = {"TRACK FAILED", "TRACK COMPLETE", "TRACK COMPLETE", "FULL COMBO", "PERFECT" } --- -------------------------------------------------------------------------- -- --- Cached calculations -- -local song_info = {} -local gauge_info = {} -local crit_base_info = {} -local combo_info = {} -local practice_info = {} - - -function LoadGauge(type) - - local name = type == 0 and "normal" or "hard" - local gauge_verts = { - {{gauge_info.posx, gauge_info.posy}, {0,1}}, - {{gauge_info.posx + gauge_info.width, gauge_info.posy}, {1,1}}, - {{gauge_info.posx + gauge_info.width, gauge_info.posy + gauge_info.height}, {1,0}}, - {{gauge_info.posx, gauge_info.posy + gauge_info.height}, {0,0}}, - } - local meshes = {} - meshes.front = gfx.CreateShadedMesh() - meshes.front:SetPrimitiveType(meshes.front.PRIM_TRIFAN) - meshes.front:SetData(gauge_verts) - meshes.front:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_front.png") - - meshes.back = gfx.CreateShadedMesh() - meshes.back:SetPrimitiveType(meshes.back.PRIM_TRIFAN) - meshes.back:SetData(gauge_verts) - meshes.back:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_back.png") - - meshes.fill = gfx.CreateShadedMesh("gauge") - meshes.fill:SetPrimitiveType(meshes.fill.PRIM_TRIFAN) - meshes.fill:SetData(gauge_verts) - meshes.fill:AddSkinTexture("mainTex", "gauges/" .. name .. "/gauge_fill.png") - meshes.fill:AddSkinTexture("maskTex", "gauges/" .. name .. "/gauge_mask.png") - - return meshes -end - --- -------------------------------------------------------------------------- -- --- ResetLayoutInformation: -- --- Resets the layout values used by the skin. -- -function ResetLayoutInformation() - portrait = resy > resx - desw = portrait and 720 or 1280 - desh = desw * (resy / resx) - scale = resx / desw - - do --update song_info - local songInfoWidth = 400 - local jacketWidth = 100 - song_info.songInfoWidth = songInfoWidth - song_info.jacketWidth = jacketWidth - - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.FontSize(30) - - song_info.textX = jacketWidth + 10 - local titleWidth = songInfoWidth - jacketWidth - 20 - local x1, y1, x2, y2 = gfx.TextBounds(0, 0, gameplay.title) - song_info.title_textscale = math.min(titleWidth / x2, 1) - x1,y1,x2,y2 = gfx.TextBounds(0,0,gameplay.artist) - song_info.artist_textscale = math.min(titleWidth / x2, 1) - end - - do --update gauge_info - gauge_info.height = 1024 * 0.35 - gauge_info.width = 512 * 0.35 - gauge_info.posy = desh / 2 - gauge_info.height / 2 - gauge_info.posx = desw - gauge_info.width - if portrait then - gauge_info.width = gauge_info.width * 0.8 - gauge_info.height = gauge_info.height * 0.8 - gauge_info.posy = gauge_info.posy - 30 - gauge_info.posx = desw - gauge_info.width - end - - gauge_info.label_posx = gauge_info.posx + (100 * 0.35) - gauge_info.label_height = 880 * 0.35 - if portrait then - gauge_info.label_height = gauge_info.label_height * 0.8; - end - gauge_info.label_posy = gauge_info.posy + (70 * 0.35) + gauge_info.label_height - - gauge_info.meshes = {} - gauge_info.meshes[0] = LoadGauge(0) - gauge_info.meshes[1] = LoadGauge(1) - end - - do --update crit_base_info - -- The absolute width of the crit line itself - -- we check to see if we're playing in portrait mode and - -- change the width accordingly - crit_base_info.critWidth = resx * (portrait and 1 or 0.8) - crit_base_info.half_critWidth = crit_base_info.critWidth / 2 - - -- get the scaled dimensions of the crit line pieces - local clw, clh = gfx.ImageSize(critAnimImg) - crit_base_info.critAnimHeight = 15 * scale - crit_base_info.critAnimWidth = crit_base_info.critAnimHeight * (clw / clh) - - local ccw, cch = gfx.ImageSize(critCap) - crit_base_info.critCapHeight = crit_base_info.critAnimHeight * (cch / clh) - crit_base_info.critCapWidth = crit_base_info.critCapHeight * (ccw / cch) - - crit_base_info.half_critAnimHeight = crit_base_info.critAnimHeight / 2 - crit_base_info.half_critAnimWidth = crit_base_info.critAnimWidth / 2 - crit_base_info.half_critCapHeight = crit_base_info.critCapHeight / 2 - crit_base_info.half_critCapWidth = crit_base_info.critCapWidth / 2 - end - - do --update combo_info - combo_info.posx = desw / 2 - combo_info.posy = desh * critLinePos[1] - 100 - if portrait then combo_info.posy = desh * critLinePos[2] - 150 end - end - - do-- update practice_info - practice_info.posy = 120 - practice_info.posx = 10 - end -end --- -------------------------------------------------------------------------- -- --- render: -- --- The primary & final render call. -- --- Use this to render basically anything that isn't the crit line or the -- --- intro/outro transitions. -- -function render(deltaTime) - -- make sure that our transform is cleared, clean working space - -- TODO: this shouldn't be necessary!!! - gfx.ResetTransform() - - gfx.Scale(scale, scale) - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - -- TODO: this isn't how it'll work in the long run, I don't think - if portrait then yshift = draw_banner(deltaTime) end - - gfx.Translate(0, yshift - 150 * math.max(introTimer - 1, 0)) - draw_song_info(deltaTime) - draw_score(deltaTime) - - - if prevGaugeType ~= nil then - if gameplay.gauge.type ~= prevGaugeType and gaugeTransition == nil then - gaugeTransition = Animation:new() - gaugeTransition.smoothStart = true - gaugeTransition:restart(0, 1, 1 / 3) - end - end - gfx.Translate(0, -yshift + 150 * math.max(introTimer - 1, 0)) - - gfx.Save() - if gaugeTransition ~= nil then - local v = gaugeTransition:tick(deltaTime) - if v < 1 then - local awayGauge = {} - awayGauge.type = prevGaugeType - awayGauge.value = 0.0 - gfx.Save() - gfx.Translate(v * gauge_info.width, 0) - draw_gauge(awayGauge) - gfx.Restore() - gfx.Translate((1-v) * gauge_info.width, 0) - else - prevGaugeType = gameplay.gauge.type - gaugeTransition = nil - end - else - prevGaugeType = gameplay.gauge.type - end - draw_gauge(gameplay.gauge) - gfx.Restore() - - - if earlatePos ~= "off" then - draw_earlate(deltaTime) - end - draw_combo(deltaTime) - draw_alerts(deltaTime) - - if gameplay.practice_setup ~= nil then - draw_practice(deltaTime); - end - - local play_mode = "" - - if gameplay.practice_setup - then play_mode = "Practice Setup" - elseif gameplay.autoplay - then play_mode = "Autoplay" - elseif gameplay.playbackSpeed ~= nil and gameplay.playbackSpeed < 1 - then play_mode = string.format("Speed: x%.2f", gameplay.playbackSpeed) - elseif gameplay.hitWindow ~= nil and gameplay.hitWindow.type == 0 - then play_mode = "Expand Judge" - end - - if play_mode ~= "" then - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.FontSize(30) - gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER) - gfx.FillColor(255,255,255) - gfx.Text(play_mode, desw/2, yshift) - end -end --- -------------------------------------------------------------------------- -- --- SetUpCritTransform: -- --- Utility function which aligns the graphics transform to the center of the -- --- crit line on screen, rotation include. -- --- This function resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function SetUpCritTransform() - -- start us with a clean empty transform - gfx.ResetTransform() - -- translate and rotate accordingly - gfx.Translate(gameplay.critLine.x, gameplay.critLine.y) - gfx.Rotate(-gameplay.critLine.rotation) -end --- -------------------------------------------------------------------------- -- --- GetCritLineCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- crit line on the screen based on its rotation. -- -function GetCritLineCenteringOffset() - return gameplay.critLine.xOffset * 10 -end --- -------------------------------------------------------------------------- -- --- GetConsoleCenteringOffset: -- --- Utility function which returns the magnitude of an offset to center the -- --- console on the screen based on its position and rotation. -- -function GetConsoleCenteringOffset() - return (resx / 2 - gameplay.critLine.x) * (5 / 6) -end --- -------------------------------------------------------------------------- -- --- render_crit_base: -- --- Called after rendering the highway and playable objects, but before -- --- the built-in hit effects. -- --- This is the first render function to be called each frame. -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_base(deltaTime) - -- Kind of a hack, but here (since this is the first render function - -- that gets called per frame) we update the layout information. - -- This means that the player can resize their window and - -- not break everything - resx, resy = game.GetResolution() - if resx ~= resx_old or resy ~= resy_old then - ResetLayoutInformation() - resx_old = resx - resy_old = resy - end - - critAnimTimer = critAnimTimer + deltaTime - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen - local xOffset = GetCritLineCenteringOffset() - gfx.Translate(xOffset, 0) - - -- Draw a transparent black overlay below the crit line - -- This darkens the play area as it passes - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, -resx, 0, resx * 2, resy) - - -- draw the back half of the caps at each end - do - gfx.FillColor(255, 255, 255) - -- left side - gfx.DrawRect(critCapBack, - -crit_base_info.half_critWidth -crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- scale to flip horizontally - -- right side - gfx.DrawRect(critCapBack, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- unflip horizontally - end - - -- render the core of the crit line - do - -- The crit line is made up of two rects with a pattern that scrolls in opposite directions on each rect - local startOffset = crit_base_info.critAnimWidth * ((critAnimTimer * 1.5) % 1) - - -- left side - -- Use a scissor to limit the drawable area to only what should be visible - gfx.UpdateImagePattern(critAnim, - -startOffset, - -crit_base_info.half_critAnimHeight, - crit_base_info.critAnimWidth, - crit_base_info.critAnimHeight, - 0, 1) - - gfx.Scissor(-crit_base_info.half_critWidth, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - - gfx.BeginPath() - gfx.Rect(-crit_base_info.half_critWidth, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.FillPaint(critAnim) - gfx.Fill() - gfx.ResetScissor() - - - -- right side - -- exactly the same, but in reverse - gfx.UpdateImagePattern(critAnim, - startOffset, - -crit_base_info.half_critAnimHeight, - crit_base_info.critAnimWidth, - crit_base_info.critAnimHeight, - 0, 1) - - gfx.Scissor(0, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.BeginPath() - gfx.Rect(0, - -crit_base_info.half_critAnimHeight, - crit_base_info.half_critWidth, - crit_base_info.critAnimHeight) - gfx.FillPaint(critAnim) - gfx.Fill() - gfx.ResetScissor() - end - - -- Draw the front half of the caps at each end - do - gfx.FillColor(255, 255, 255) - -- left side - gfx.DrawRect(critCap, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- scale to flip horizontally - -- right side - gfx.DrawRect(critCap, - -crit_base_info.half_critWidth - crit_base_info.half_critCapWidth, - -crit_base_info.half_critCapHeight, - crit_base_info.critCapWidth, - crit_base_info.critCapHeight) - gfx.Scale(-1, 1) -- unflip horizontally - end - - -- we're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- render_crit_overlay: -- --- Called after rendering built-int crit line effects. -- --- Use this to render laser cursors or an IO Console in portrait mode! -- --- This call resets the graphics transform, it's up to the caller to -- --- save the transform if needed. -- -function render_crit_overlay(deltaTime) - SetUpCritTransform() - - -- Figure out how to offset the center of the crit line to remain - -- centered on the players screen. - local xOffset = GetConsoleCenteringOffset() - - -- When in portrait, we can draw the console at the bottom - if portrait then - -- We're going to make temporary modifications to the transform - gfx.Save() - gfx.Translate(xOffset, 0) - - local bfw, bfh = gfx.ImageSize(bottomFill) - - local distBetweenKnobs = 0.446 - local distCritVertical = 0.098 - - local ioFillTx = bfw / 2 - local ioFillTy = bfh * distCritVertical -- 0.098 - - -- The total dimensions for the console image - local io_x, io_y, io_w, io_h = -ioFillTx, -ioFillTy, bfw, bfh - - -- Adjust the transform accordingly first - local consoleFillScale = (resx * 0.775) / (bfw * distBetweenKnobs) - gfx.Scale(consoleFillScale, consoleFillScale); - - -- Actually draw the fill - gfx.FillColor(255, 255, 255) - gfx.DrawRect(bottomFill, io_x, io_y, io_w, io_h) - - -- Then draw the details which need to be colored to match the lasers - for i = 1, 2 do - gfx.FillLaserColor(i) - gfx.DrawRect(ioConsoleDetails[i], io_x, io_y, io_w, io_h) - end - - -- Draw the button press animations by overlaying transparent images - gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER) - for i = 1, 6 do - -- While a button is held, increment a timer - -- If not held, that timer is set back to 0 - if game.GetButton(buttonsInOrder[i]) then - consoleAnimTimers[i] = consoleAnimTimers[i] + deltaTime * consoleAnimSpeed * 3.14 * 2 - else - consoleAnimTimers[i] = 0 - end - - -- If the timer is active, flash based on a sin wave - local timer = consoleAnimTimers[i] - if timer ~= 0 then - local image = consoleAnimImages[i] - local alpha = (math.sin(timer) * 0.5 + 0.5) * 0.5 + 0.25 - gfx.FillColor(255, 255, 255, alpha * 255); - gfx.DrawRect(image, io_x, io_y, io_w, io_h) - end - end - gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER) - - -- Undo those modifications - gfx.Restore(); - end - - local cw, ch = gfx.ImageSize(laserCursor) - local cursorWidth = 40 * scale - local cursorHeight = cursorWidth * (ch / cw) - - -- draw each laser cursor - for i = 1, 2 do - local cursor = gameplay.critLine.cursors[i - 1] - local pos, skew = cursor.pos, cursor.skew - - -- Add a kinda-perspective effect with a horizontal skew - gfx.SkewX(skew) - - --Add the tail, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 255) - gfx.DrawRect(laserTail, pos - cursorWidth / 2 - 64, -cursorHeight / 2 - 5, cursorWidth * 5, cursorHeight * 5) - end - - -- Draw the SDVX Icon eye and tails below the overlay - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorText, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored background with the appropriate laser color - gfx.FillLaserColor(i, cursor.alpha * 130) - gfx.DrawRect(laserCursor, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - - --Add the top wave effect, only active in critical zone - if (gameplay.laserActive[i]) then - gfx.FillLaserColor(i, cursor.alpha * 180) - gfx.DrawRect(laserTopWave, pos - cursorWidth / 2 - 80, -cursorHeight / 2 - 24, cursorWidth * 6, cursorHeight * 6) - end - - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 255) - gfx.DrawRect(laserCursorOverlay, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 18, cursorWidth * 2, cursorHeight * 2) - -- Draw the colored glow on top of the pointer - gfx.FillLaserColor(i, cursor.alpha * 160) - gfx.DrawRect(laserCursorGlow, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Draw the uncolored overlay on top of the color - gfx.FillColor(255, 255, 255, cursor.alpha * 150) - gfx.DrawRect(laserCursorShine, pos - cursorWidth / 2 - 18, -cursorHeight / 2 - 20, cursorWidth * 2, cursorHeight * 2) - -- Un-skew - gfx.SkewX(-skew) - end - - -- We're done, reset graphics stuffs - gfx.FillColor(255, 255, 255) - gfx.ResetTransform() -end --- -------------------------------------------------------------------------- -- --- draw_banner: -- --- Renders the banner across the top of the screen in portrait. -- --- This function expects no graphics transform except the design scale. -- -function draw_banner(deltaTime) - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - local actualHeight = desw * (bannerHeight / bannerWidth) - - gfx.FillColor(255, 255, 255) - gfx.DrawRect(topFill, 0, 0, desw, actualHeight) - - return actualHeight -end --- -------------------------------------------------------------------------- -- --- draw_stat: -- --- Draws a formatted name + value combination at x, y over w, h area. -- -function draw_stat(x, y, w, h, name, value, format, r, g, b) - gfx.Save() - - -- Translate from the parent transform, wherever that may be - gfx.Translate(x, y) - - -- Draw the `name` top-left aligned at `h` size - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FontSize(h) - gfx.Text(name .. ":", 0, 0) -- 0, 0, is x, y after translation - - -- Realign the text and draw the value, formatted - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format(format, value), w, 0) - -- This draws an underline beneath the text - -- The line goes from 0, h to w, h - gfx.BeginPath() - gfx.MoveTo(0, h) - gfx.LineTo(w, h) -- only defines the line, does NOT draw it yet - - -- If a color is provided, set it - if r then gfx.StrokeColor(r, g, b) - -- otherwise, default to a light grey - else gfx.StrokeColor(200, 200, 200) end - - -- Stroke out the line - gfx.StrokeWidth(1) - gfx.Stroke() - -- Undo our transform changes - gfx.Restore() - - -- Return the next `y` position, for easier vertical stacking - return y + h + 5 -end --- -------------------------------------------------------------------------- -- --- draw_song_info: -- --- Draws current song information at the top left of the screen. -- --- This function expects no graphics transform except the design scale. -- -local songBack = gfx.CreateSkinImage("song_back.png", 0) -local numberDot = gfx.CreateSkinImage("number/dot.png", 0) -local diffImages = { - 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) -} -local memo = Memo.new() - -function draw_song_info(deltaTime) - local jacketWidth = 105 - - -- Check to see if there's a jacket to draw, and attempt to load one if not - if jacket == nil or jacket == jacketFallback then - jacket = gfx.LoadImageJob(gameplay.jacketPath, jacketFallback) - end - - gfx.Save() - - -- Add a small margin at the edge - gfx.Translate(5,5) - -- There's less screen space in portrait, the playable area is effectively a square - -- We scale down to take up less space - if portrait then gfx.Scale(0.7, 0.7) end - - -- Ensure the font has been loaded - gfx.LoadSkinFont("segoeui.ttf") - - -- Draw the background - local tw, th = gfx.ImageSize(songBack) - gfx.FillColor(255,255,255) - gfx.BeginPath() - gfx.ImageRect(-2, -71, tw * 0.855, th * 0.855, songBack, 1, 0) - -- Draw the jacket - gfx.FillColor(255, 255, 255) - gfx.DrawRect(jacket, 31, -39, song_info.jacketWidth, song_info.jacketWidth) - -- Draw a background for the following level stat - gfx.FillColor(0, 0, 0, 200) - gfx.DrawRect(RECT_FILL, 0, 85, 60, 15) - -- Level Name : Level Number - gfx.FillColor(255, 255, 255) - draw_stat(0, 85, 55, 15, diffNames[gameplay.difficulty + 1], gameplay.level, "%02d") - -- Reset some text related stuff that was changed in draw_state - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) - gfx.FontSize(30) - - gfx.FillColor(255, 255, 255) - - local textscale = song_info.title_textscale - local textX = song_info.textX - - gfx.Save() - do -- Draw the song title, scaled to fit as best as possible - gfx.Translate(textX, 30) - gfx.Scale(textscale, textscale) - gfx.Text(gameplay.title, 0, 0) - end - gfx.Restore() - - textscale = song_info.artist_textscale - - gfx.Save() - do -- Draw the song artist, scaled to fit as best as possible - gfx.Translate(textX, 60) - gfx.Scale(textscale, textscale) - gfx.Text(gameplay.artist, 0, 0) - end - gfx.Restore() - - -- Draw the BPM - gfx.FontSize(20) - gfx.Text(string.format("BPM: %.1f", gameplay.bpm), textX, 85) - - -- Fill the progress bar - gfx.FillColor(0, 150, 255) - gfx.DrawRect(RECT_FILL, song_info.jacketWidth, song_info.jacketWidth - 10, (song_info.songInfoWidth - song_info.jacketWidth) * gameplay.progress, 10) - - -- When the player is holding Start, the hispeed can be changed - -- Shows the current hispeed values - if game.GetButton(game.BUTTON_STA) then - gfx.FillColor(20, 20, 20, 200); - gfx.DrawRect(RECT_FILL, 100, 100, song_info.songInfoWidth - 100, 20) - gfx.FillColor(255, 255, 255) - if game.GetButton(game.BUTTON_BTB) then - gfx.Text(string.format("Hid/Sud Cutoff: %.1f%% / %.1f%%", - gameplay.hiddenCutoff * 100, gameplay.suddenCutoff * 100), - textX, 115) - - elseif game.GetButton(game.BUTTON_BTC) then - gfx.Text(string.format("Hid/Sud Fade: %.1f%% / %.1f%%", - gameplay.hiddenFade * 100, gameplay.suddenFade * 100), - textX, 115) - else - gfx.Text(string.format("HiSpeed: %.0f x %.1f = %.0f", - gameplay.bpm, gameplay.hispeed, gameplay.bpm * gameplay.hispeed), - textX, 115) - end - end - - -- aaaand, scene! - gfx.Restore() -end --- -------------------------------------------------------------------------- -- --- draw_best_diff: -- --- If there are other saved scores, this displays the difference between -- --- the current play and your best. -- -function draw_best_diff(deltaTime, x, y) - -- Don't do anything if there's nothing to do - if not gameplay.scoreReplays[1] then return end - - -- Calculate the difference between current and best play - local difference = score - gameplay.scoreReplays[1].currentScore - local prefix = "" -- used to properly display negative values - - gfx.BeginPath() - gfx.FontSize(26) - - gfx.FillColor(255, 255, 255) - if difference < 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(255, 90, 70) - difference = math.abs(difference) - prefix = "- " - - elseif difference > 0 then - -- If we're behind the best score, separate the minus sign and change the color - gfx.FillColor(170, 160, 255) - difference = math.abs(difference) - prefix = "+ " - end - - -- %08d formats a number to 8 characters - -- This includes the minus sign, so we do that separately - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.format("%s%08d", prefix, difference), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - -function draw_username(deltaTime, x, y) - gfx.BeginPath() - gfx.FillColor(255, 255, 255) - gfx.LoadSkinFont("Digital-Serial-Bold.ttf") - gfx.FontSize(26) - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.Text(string.sub(username, 1, 8), x, y) -end - -local score_animation = Animation:new() --- -------------------------------------------------------------------------- -- --- draw_score: -- -local scoreBack = gfx.CreateSkinImage("score_back.png", 0) -local scoreNumber = load_number_image("score_num") -local maxCombo = 0 -function draw_score(deltaTime) - local tw, th = gfx.ImageSize(scoreBack) - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - tw = tw * 0.61; - th = th * 0.61; - gfx.ImageRect(desw - tw + 12, portrait and 50 or 0, tw, th, scoreBack, 1, 0) - - gfx.FillColor(255, 255, 255) - draw_number(desw - 305, portrait and 132 or 64, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.40, 1.12) - draw_number(desw - 110, portrait and 137 or 68, 1.0, score, 4, scoreNumber, true, 0.3, 1.12) - - -- Draw max combo - gfx.FillColor(255, 255, 255) - draw_number(desw - 300, portrait and 207 or 110, 1.0, maxCombo, 4, numberImages, true) -end --- -------------------------------------------------------------------------- -- --- draw_gauge: -- -function draw_gauge(gauge) - local c = nil - if gauge.type == 0 then - if gauge.value > 0.7 then - c = {r = 1, g = 0, b = 1} - else - c = {r = 0, g = 0.5, b = 1} - end - else - c = {r = 1, g = 0.5, b = 0} - end - - gauge_info.meshes[gauge.type].fill:SetParamVec4("barColor", c.r, c.g, c.b, 1) - gauge_info.meshes[gauge.type].fill:SetParam("rate", gauge.value) - - gauge_info.meshes[gauge.type].back:Draw() - gauge_info.meshes[gauge.type].fill:Draw() - gauge_info.meshes[gauge.type].front:Draw() - - --draw gauge % label - local posy = gauge_info.label_posy - gauge_info.label_height * gauge.value - gfx.BeginPath() - gfx.Rect(gauge_info.label_posx-35, posy-10, 40, 20) - gfx.FillColor(0,0,0,200) - gfx.Fill() - gfx.FillColor(255,255,255) - gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(20) - gfx.Text(string.format("%d%%", math.floor(gauge.value * 100)), gauge_info.label_posx, posy ) -end --- -------------------------------------------------------------------------- -- --- draw_combo: -- -function draw_combo(deltaTime) - if combo == 0 then return end - gfx.BeginPath() - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - if gameplay.comboState == 2 then - gfx.FillColor(100,255,0) --puc - elseif gameplay.comboState == 1 then - gfx.FillColor(255,200,0) --uc - else - gfx.FillColor(255,255,255) --regular - end - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70 * math.max(comboScale, 1)) - comboScale = comboScale - deltaTime * 3 - gfx.Text(tostring(combo), combo_info.posx, combo_info.posy) -end --- -------------------------------------------------------------------------- -- --- draw_earlate: -- -function draw_earlate(deltaTime) - earlateTimer = math.max(earlateTimer - deltaTime,0) - if earlateTimer == 0 then return nil end - local alpha = math.floor(earlateTimer * 20) % 2 - alpha = alpha * 200 + 55 - gfx.BeginPath() - gfx.FontSize(35) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE) - local ypos = desh * critLinePos[1] - 150 - if portrait then ypos = desh * critLinePos[2] - 200 end - if earlatePos == "middle" then - ypos = ypos - 200 - elseif earlatePos == "top" then - ypos = ypos - 400 - end - - if late then - gfx.FillColor(0,255,255, alpha) - gfx.Text("LATE", desw / 2, ypos) - else - gfx.FillColor(255,0,255, alpha) - gfx.Text("EARLY", desw / 2, ypos) - end -end --- -------------------------------------------------------------------------- -- --- draw_alerts: -- -function draw_alerts(deltaTime) - alertTimers[1] = math.max(alertTimers[1] - deltaTime,-2) - alertTimers[2] = math.max(alertTimers[2] - deltaTime,-2) - if alertTimers[1] > 0 then --draw left alert - gfx.Save() - local posx = desw / 2 - 350 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 135 - posx = 65 - end - gfx.Translate(posx,posy) - r,g,b = game.GetLaserColor(0) - local alertScale = (-(alertTimers[1] ^ 2.0) + (1.5 * alertTimers[1])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.BeginPath() - gfx.RoundedRectVarying(-50,-50,100,100,20,0,20,0) - gfx.StrokeColor(r,g,b) - gfx.FillColor(20,20,20) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.BeginPath() - gfx.FillColor(r,g,b) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(90) - gfx.Text("L",0,0) - gfx.Restore() - end - if alertTimers[2] > 0 then --draw right alert - gfx.Save() - local posx = desw / 2 + 350 - local posy = desh * critLinePos[1] - 135 - if portrait then - posy = desh * critLinePos[2] - 135 - posx = desw - 65 - end - gfx.Translate(posx,posy) - r,g,b = game.GetLaserColor(1) - local alertScale = (-(alertTimers[2] ^ 2.0) + (1.5 * alertTimers[2])) * 5.0 - alertScale = math.min(alertScale, 1) - gfx.Scale(1, alertScale) - gfx.BeginPath() - gfx.RoundedRectVarying(-50,-50,100,100,0,20,0,20) - gfx.StrokeColor(r,g,b) - gfx.FillColor(20,20,20) - gfx.StrokeWidth(2) - gfx.Fill() - gfx.Stroke() - gfx.BeginPath() - gfx.FillColor(r,g,b) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FontSize(90) - gfx.Text("R",0,0) - gfx.Restore() - end -end - -function change_earlatepos() - if earlatePos == "top" then - earlatePos = "off" - elseif earlatePos == "off" then - earlatePos = "bottom" - elseif earlatePos == "bottom" then - earlatePos = "middle" - elseif earlatePos == "middle" then - earlatePos = "top" - end - game.SetSkinSetting("earlate_position", earlatePos) -end --- -------------------------------------------------------------------------- -- --- draw_status: -- -local statusBack = Image.skin("status_back.png") -local apealCard = Image.skin("appeal_card.png") -local dan = Image.skin("dan.png") -local volforce = Image.skin ("volforce.png") -function draw_status(deltaTime) - -- Draw the background - gfx.FillColor(255, 255, 255) - gfx.BeginPath() - statusBack:draw({ x = 0, y = desh / 2 - 195, w = statusBack.w * 0.85, h = statusBack.h * 0.85, anchor_h = Image.ANCHOR_LEFT }) - gfx.Fill() - - -- Draw the apeal card - apealCard:draw({ x = 12, y = desh / 2 - 220, w = apealCard.w * 0.62, h = apealCard.h * 0.62, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP }) - - -- Draw the dan - dan:draw({ x = 164, y = desh / 2 - 117, w = dan.w * 0.32, h = dan.h * 0.32 }) - - -- Draw the Volforce - volforce:draw({ x = 240, y = desh / 2 - 119, w = volforce.w * 0.12, h = volforce.h * 0.12 }) - - -- Draw the best difference - draw_best_diff(deltaTime, 145, desh / 2 - 175) - - -- Draw the username - draw_username(deltatime, 145, desh / 2 - 198) -end - --- -------------------------------------------------------------------------- -- --- render_intro: -- -local bta_last = false -function render_intro(deltaTime) - if gameplay.demoMode then - introTimer = 0 - return true - end - if not game.GetButton(game.BUTTON_STA) then - introTimer = introTimer - deltaTime - earlateTimer = 0 - else - earlateTimer = 1 - if (not bta_last) and game.GetButton(game.BUTTON_BTA) then - change_earlatepos() - end - end - bta_last = game.GetButton(game.BUTTON_BTA) - introTimer = math.max(introTimer, 0) - - return introTimer <= 0 -end --- -------------------------------------------------------------------------- -- --- render_outro: -- -function render_outro(deltaTime, clearState) - if clearState == 0 then return true end - if not gameplay.demoMode then - gfx.ResetTransform() - gfx.BeginPath() - gfx.Rect(0,0,resx,resy) - gfx.FillColor(0,0,0, math.floor(127 * math.min(outroTimer, 1))) - gfx.Fill() - gfx.Scale(scale,scale) - gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE) - gfx.FillColor(255,255,255, math.floor(255 * math.min(outroTimer, 1))) - gfx.LoadSkinFont("NovaMono.ttf") - gfx.FontSize(70) - gfx.Text(clearTexts[clearState], desw / 2, desh / 2) - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - outroTimer - else - outroTimer = outroTimer + deltaTime - return outroTimer > 2, 1 - end - -end --- -------------------------------------------------------------------------- -- --- update_score: -- -function update_score(newScore) - if newScore ~= score then - score_animation:restart(score_animation:tick(0), newScore, 0.33) - score = newScore - end -end --- -------------------------------------------------------------------------- -- --- update_combo: -- -function update_combo(newCombo) - combo = newCombo - comboScale = 1.5 -end --- -------------------------------------------------------------------------- -- --- near_hit: -- -function near_hit(wasLate) --for updating early/late display - late = wasLate - earlateTimer = 0.75 -end --- -------------------------------------------------------------------------- -- --- laser_alert: -- -function laser_alert(isRight) --for starting laser alert animations - if isRight and alertTimers[2] < -1.5 then - alertTimers[2] = 1.5 - elseif alertTimers[1] < -1.5 then - alertTimers[1] = 1.5 - end -end - - --- ======================== Start mutliplayer ======================== - -json = require "json" - -local normal_font = game.GetSkinSetting('multi.normal_font') -if normal_font == nil then - normal_font = 'NotoSans-Regular.ttf' -end -local mono_font = game.GetSkinSetting('multi.mono_font') -if mono_font == nil then - mono_font = 'NovaMono.ttf' -end - -local users = nil - -function init_tcp() - Tcp.SetTopicHandler("game.scoreboard", function(data) - users = {} - for i, u in ipairs(data.users) do - table.insert(users, u) - end - end) -end - - --- Hook the render function and draw the scoreboard -local real_render = render -render = function(deltaTime) - real_render(deltaTime) - draw_users(deltaTime) -end - --- Update the users in the scoreboard -function score_callback(response) - if response.status ~= 200 then - error() - return - end - local jsondata = json.decode(response.text) - users = {} - for i, u in ipairs(jsondata.users) do - table.insert(users, u) - end -end - --- Render scoreboard -function draw_users(detaTime) - if (users == nil) then - return - end - - local yshift = 0 - - -- In portrait, we draw a banner across the top - -- The rest of the UI needs to be drawn below that banner - if portrait then - local bannerWidth, bannerHeight = gfx.ImageSize(topFill) - yshift = desw * (bannerHeight / bannerWidth) - gfx.Scale(0.7, 0.7) - end - - gfx.Save() - - -- Add a small margin at the edge - gfx.Translate(5,yshift+200) - - -- Reset some text related stuff that was changed in draw_state - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT) - gfx.FontSize(35) - gfx.FillColor(255, 255, 255) - local yoff = 0 - if portrait then - yoff = 75; - end - local rank = 0 - for i, u in ipairs(users) do - gfx.FillColor(255, 255, 255) - local score_big = string.format("%04d",math.floor(u.common/grades/1000)); - local score_small = string.format("%03d",u.score%1000); - local user_text = '('..u.name..')'; - - local size_big = 40; - local size_small = 28; - local size_name = 30; - - if u.id == gameplay.user_id then - size_big = 48 - size_small = 32 - size_name = 40 - rank = i; - end - - gfx.LoadSkinFont(mono_font) - gfx.FontSize(size_big) - gfx.Text(score_big, 0, yoff); - local xmin,ymin,xmax,ymax_big = gfx.TextBounds(0, yoff, score_big); - xmax = xmax + 7 - - gfx.FontSize(size_small) - gfx.Text(score_small, xmax, yoff); - xmin,ymin,xmax,ymax = gfx.TextBounds(xmax, yoff, score_small); - xmax = xmax + 7 - - if u.id == gameplay.user_id then - gfx.FillColor(237, 240, 144) - end - - gfx.LoadSkinFont(normal_font) - gfx.FontSize(size_name) - gfx.Text(user_text, xmax, yoff) - - yoff = ymax_big + 15 - end - - gfx.Restore() -end - --- ======================== Start practice mode ======================== -local is_playing_practice = false -local mission_str = "" - -local count_play = 0 -local count_success = 0 -local last_run = "" -local last_timing = "" - --- Called when the practice starts -function practice_start(mission_type, mission_threshold, mission_description) - is_playing_practice = true - - mission_str = string.format("Mission: %s", mission_description) -end - --- Called when a run for the practice is finished -function practice_end_run(playCount, successCount, isSuccessful, scoring) - count_play = playCount - count_success = successCount - last_run = string.format("Last run: %d (%d-%d)", scoring.score, scoring.goods, scoring.misses) - last_timing = string.format("Hit delta: %d±%dms", scoring.meanHitDelta, scoring.meanHitDeltaAbs) -end - --- Called when user enters the setup again -function practice_end(playCount, successCount) - is_playing_practice = false - - count_play = playCount - count_success = successCount -end - -function draw_practice(deltaTime) - if not is_playing_practice then - return - end - - gfx.Save() - gfx.Translate(practice_info.posx, practice_info.posy) - gfx.LoadSkinFont("NotoSans-Regular.ttf") - gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP) - gfx.FillColor(255, 255, 255) - - gfx.FontSize(25) - gfx.Text(mission_str, 0, 0) - - if count_play > 0 then - local play_stat = string.format("Clear rate: %d/%d (%.1f%%)", count_success, count_play, (100.0 * count_success / count_play)) - gfx.Text(play_stat, 0, 30) - - gfx.FontSize(20) - gfx.Text(last_run, 0, 55) - gfx.Text(last_timing, 0, 75) - end - - gfx.Restore() -end