diff --git a/docs/lua_api/result.lua b/docs/lua_api/result.lua index d72ee06..3c2c30e 100644 --- a/docs/lua_api/result.lua +++ b/docs/lua_api/result.lua @@ -18,23 +18,34 @@ HitStat = {} ---@field type integer -- `1 = Normal` default, `2 = Hard` default values halved HitWindow = {} ----@class Score ----@field auto_flags integer # Autoplay flag +---@class HiddenSudden +---@field hiddenCutoff number +---@field hiddenFade number +---@field showCover boolean # Cover beatmap +---@field suddenCutoff number +---@field suddenFade number +HiddenSudden = {} + +---@class HighScore +---@field auto_flags integer # Only Singleplayer, Autoplay flag ---@field badge integer # `0` = Manual Exit, `1` = Played, `2` = Cleared, `3` = Hard Cleared, `4` = Full Chain, `5` = Perfect Chain +---@field flags integer # Only Multiplayer, Autoplay flag ---@field gauge number # Ending gauge percentage, `0.0` to `1.0` ----@field gauge_option integer # Gauge option e.g. ARS ----@field gauge_type integer # `0` = Normal, `1` = Hard, `2` = Permissive, `3` = Blastive +---@field gauge_option integer # Only Singleplayer, Gauge option e.g. ARS +---@field gauge_type integer # Only Singleplayer, `0` = Normal, `1` = Hard, `2` = Permissive, `3` = Blastive ---@field goods integer # Total near hits ----@field hitWindow HitWindow|nil # Hit windows of the score, only for singleplayer results screen ----@field mirror integer # Mirror mode flag +---@field hitWindow nil|HitWindow # Only Singleplayer, Hit windows of the score +---@field isLocal boolean # Only Singleplayer, Whether this score was set locally +---@field mirror integer # Only Singleplayer, Mirror mode flag ---@field misses integer # Total errors ----@field name nil|string # Only for multiplayer results, name of the player +---@field name string # Only Multiplayer, player name ---@field perfects integer # Total critical hits ----@field random integer # Random mode flag +---@field playerName string # Only Singleplayer, Player name +---@field random integer # Only Singleplayer, Random mode flag ---@field score integer # Result score ---@field timestamp integer # Unix timestamp of the score ---@field uid nil|string # Only for multiplayer results, UID of the player -Score = {} +HighScore = {}; ---@class ChartResult : result ---@field passed boolean # Whether or not challenge requirements were met for this chart @@ -42,27 +53,30 @@ Score = {} ChartResult = {} ---@class ServerScoreOptions ----@field gaugeType integer # An enum value representing the gauge type used. 0 = normal, 1 = hard. Further values are not currently specified. +---@field autoFlags integer # A bitfield of elements of the game that are automated. Any non-zero value means that the score was at least partially auto. ---@field gaugeOpt integer # Reserved +---@field gaugeType integer # An enum value representing the gauge type used. 0 = normal, 1 = hard. Further values are not currently specified. ---@field mirror boolean # Mirror mode enabled ---@field random boolean # Note shuffle enabled ----@field autoFlags integer # A bitfield of elements of the game that are automated. Any non-zero value means that the score was at least partially auto. ServerScoreOptions = {} ---@class ServerScore ----@field score integer # Submitted score ----@field gauge number # Submitted Gauge result ----@field timestamp integer # Unix timestamp of the score ----@field crit integer # Hits inside the critical window ----@field near integer # Hits inside the near window ----@field early integer # Hits inside the near window which were early ----@field late integer # Hits inside the near window which were late ---@field combo integer # Best combo reached +---@field crit integer # Hits inside the critical window +---@field early integer # Hits inside the near window which were early ---@field error integer # Missed notes +---@field gauge number # Submitted Gauge result +---@field justSet boolean # This score belongs to the current player, and is the score that was just achieved +---@field lamp integer # `badge` +---@field late integer # Hits inside the near window which were late +---@field near integer # Hits inside the near window ---@field options ServerScoreOptions # The options in use. Includes gauge type, etc. +---@field ranking integer # Online leaderboard ranking +---@field score integer # Submitted score +---@field timestamp integer # Unix timestamp of the score +---@field username string # Online player name ---@field windows table # {perfect, good, hold, miss, slam} hit windows in milliseconds ---@field yours boolean # This score belongs to the current player ----@field justSet boolean # This score belongs to the current player, and is the score that was just achieved ServerScore = {} ---@class result @@ -92,16 +106,17 @@ ServerScore = {} ---@field gaugeSamples number[] # Gauge values sampled (256 total) throughout the play ---@field goods integer # Total near hits ---@field grade string # Result grade ----@field highScores Score[] # All scores +---@field hidsud nil|HiddenSudden # Hidden-Sudden options +---@field highScores HighScore[] # All scores ---@field hitWindow HitWindow # Result hit windows ----@field holdHitStats HitStat[]|nil # Hit stats for every hold object, only available for singleplayer if `isSelf = true` +---@field holdHitStats nil|HitStat[] # Hit stats for every hold object, only available for singleplayer if `isSelf = true` ---@field illustrator string # Chart jacket illustrator ---@field irState integer # Current state of the IR score submission request (a USC-IR code, including extensions 0/10/60) ----@field irDescription string # The description in the IR response (nil if irState is 0 or 10) ----@field irScores ServerScore[]|nil # Score submission result, nil if irState != 20 ----@field isSelf boolean # Only for multiplayer, `false` if score is of another player +---@field irDescription nil|string # The description in the IR response (nil if irState is 0 or 10) +---@field irScores nil|ServerScore[] # Score submission result, nil if irState != 20 +---@field isSelf boolean # `false` if score is of another player ---@field jacketPath string # Full filepath to the jacket image on the disk ----@field laserHitStats HitStat[]|nil # Hit stats for every laser object, only available for singleplayer if `isSelf = true` +---@field laserHitStats nil|HitStat[] # Hit stats for every laser object, only available for singleplayer if `isSelf = true` ---@field lates integer # Total late hits ---@field level integer # Chart or challenge level ---@field maxCombo integer # Result max chain @@ -112,7 +127,7 @@ ServerScore = {} ---@field mirror boolean # Mirror mode bool ---@field misses integer # Total errors ---@field mission string # Only for practice mode ----@field noteHitStats HitStat[]|nil # Hit stats for every chip hit, only available for singleplayer if `isSelf = true` +---@field noteHitStats nil|HitStat[] # Hit stats for every chip hit, only available for singleplayer if `isSelf = true` ---@field overallCrits integer # Only for challenge results, total number of critical hits across the charts ---@field overallErrors integer # Only for challenge results, total number of error hits across the charts ---@field overallNears integer # Only for challenge results, total number of near hits across the charts @@ -125,8 +140,8 @@ ServerScore = {} ---@field requirement_text string # Only for challenge results, the challenge requirements separated by newline character `"\n"` ---@field retryCount integer # Only for practice mode ---@field score integer # Result score ----@field speedModType integer # Only for singleplayer, `0` = XMOD, `1` = MMOD, `2` = CMOD ----@field speedModValue number # Only for singleplayer, `HiSpeed` for `XMOD`, `ModSpeed` otherwise +---@field speedModType nil|integer # Only for singleplayer, `0` = XMOD, `1` = MMOD, `2` = CMOD +---@field speedModValue nil|number # Only for singleplayer, `HiSpeed` for `XMOD`, `ModSpeed` otherwise ---@field title string # Chart (with player name in multiplayer) or challenge title ---@field uid nil|string # Only for multiplayer, UID of the player result = {} diff --git a/docs/lua_api/songwheel.lua b/docs/lua_api/songwheel.lua index 42e609a..b8bf0d2 100644 --- a/docs/lua_api/songwheel.lua +++ b/docs/lua_api/songwheel.lua @@ -1,7 +1,17 @@ -- songwheel `songwheel` table +---@class Song +---@field artist string # Chart artist +---@field difficulties Difficulty[] # Array of difficulties for the current song +---@field bpm string # Chart BPM +---@field id integer # Song id, unique static identifier +---@field path string # Full filepath to the chart folder on the disk +---@field title string # Chart title +Song = {}; + ---@class Difficulty ---@field difficulty integer # Difficulty index +---@field effector string # Effector name ---@field hash string # Difficulty hash ---@field id integer # Difficulty id, unique static identifier ---@field illustrator string # Difficulty jacket illustrator @@ -11,31 +21,30 @@ ---@field topBadge integer # `0 = Never Played`, `1 = Played`, `2 = Cleared`, `3 = Hard Cleared`, `4 = Full Chain`, `5 = Perfect Chain` Difficulty = {} ----@class Song ----@field artist string # Chart artist ----@field difficulties Difficulty[] # Array of difficulties for the current song ----@field bpm number # Chart BPM ----@field id integer # Song id, unique static identifier ----@field path string # Full filepath to the chart folder on the disk ----@field title string # Chart title -Song = {} +---@class Score +---@field auto_flags integer # Autoplay flag +---@field badge integer # `0` = Manual Exit, `1` = Played, `2` = Cleared, `3` = Hard Cleared, `4` = Full Chain, `5` = Perfect Chain +---@field combo integer # Longest combo +---@field earlies integer # Early hits +---@field gauge number # Ending gauge percentage, `0.0` to `1.0` +---@field gauge_option integer # Gauge option e.g. ARS +---@field gauge_type integer # `0` = Normal, `1` = Hard, `2` = Permissive, `3` = Blastive +---@field goods integer # Total near hits +---@field isLocal boolean # Whether this score was set locally +---@field lates integer # Late hits +---@field mirror integer # Mirror mode flag +---@field misses integer # Total errors +---@field perfects integer # Total critical hits +---@field playerName string # Player name +---@field random integer # Random mode flag +---@field score integer # Result score +---@field timestamp integer # Unix timestamp of the score +Score = {}; ---@class songwheel ---@field allSongs Song[] # Array of all available songs ----@field searchInputActive boolean # Search status +---@field searchInputActive boolean # Whether the user is currently inputting search text ---@field searchStatus string # Current song database status ----@field searchText string # Search input text +---@field searchText string # Current string used by the song search ---@field songs Song[] # Array of songs with the current filters/sorting applied -songwheel = {} - ----Set difficulty index ----@param diff integer diff index -function set_diff(diff) end - ----Function called by the game when ``songs`` or ``allSongs`` (if withAll == true) is changed. ----@param allSongs boolean -function songs_changed(allSongs) end - ----Function called by the game to get how much to scroll when page up or page down are pressed. ----Needs to be defined for the game to work properly. -function get_page_size() end +songwheel = {}; diff --git a/scripts/core/platform/filesys.lua b/scripts/core/platform/filesys.lua index 2e94256..eb638f5 100644 --- a/scripts/core/platform/filesys.lua +++ b/scripts/core/platform/filesys.lua @@ -1,4 +1,6 @@ ---@diagnostic disable: duplicate-set-field +require "common.globals" +require "api.logging" require("core.string") @@ -35,7 +37,7 @@ end function filesys.exists(path) local ok, err, code = os.rename(path, path) if not ok then - game.Log("err: "..err..", code: "..code, game.LOGGER_DEBUG) + DetailedLog("err: "..err..", code: "..code, game.LOGGER_DEBUG) if code == 13 then -- Permission denied, but it exists return true @@ -44,6 +46,20 @@ function filesys.exists(path) return ok, err end +---Returns a path relative to the current working directory from a texture path +---@param path string # must be a path accepted by game.CreateSkinImage and such +---@return string +function FileSys.fromTexturePath(path) + --skins//textures/ + local relpath = FileSys.join("skins", game.GetSkin(), "textures", FileSys.normpath(path)) + + if not FileSys.exists(FileSys.join(FileSys.getcwd(), relpath)) then + DetailedLog("'"..relpath.."' does not exist", game.LOGGER_ERROR) + end + + return relpath +end + ---Return a string representing the current working directory ---@return string function filesys.getcwd() @@ -68,9 +84,10 @@ end ---Normalize `path`, collapse redundant separators and up references ---@param path string +---@param overrideSep? string ---@return string -function filesys.normpath(path) - local sep = filesys.sep +function filesys.normpath(path, overrideSep) + local sep = overrideSep or filesys.sep --remove multiple slashes path = path:gsub("("..sep..")"..sep.."+", "%1") @@ -83,7 +100,7 @@ function filesys.normpath(path) local upRefPattern = "%w+"..sep.."%.%."..sep repeat path, count = path:gsub(upRefPattern, "") - until count ~= 0 + until count == 0 --remove last slash path = path:gsub("(.-)"..sep.."$", "%1") diff --git a/scripts/graphics/frameanimation.lua b/scripts/graphics/frameanimation.lua index 1d23c24..40edf61 100644 --- a/scripts/graphics/frameanimation.lua +++ b/scripts/graphics/frameanimation.lua @@ -1,25 +1,16 @@ +require "api.logging" require "common.class" +require "api.filesys" + require "api.graphics" local Image = require "scripts.graphics.image" ----@class AnimationParams ----@field fps number? ----@field loop boolean? ----@field loopPoint integer? ----@field width number? ----@field height number? ----@field x number? ----@field y number? ----@field scaleX number? ----@field scaleY number? ----@field centered boolean? ----@field blendOp integer? ----@field color number[]? ----@field alpha number? ----@field stroke StrokeParams? +---@class AnimationParam: Animation +---@field animPath string +---@field animFPS number ---@class Animation ---@field frames Image[] @@ -38,7 +29,7 @@ local Image = require "scripts.graphics.image" ---@field color number[]? ---@field alpha number? ---@field stroke StrokeParams? -local Animation = { }; +local Animation = { } ---@class AnimationState ---@field animation Animation # The animation data this state is playing through @@ -46,72 +37,61 @@ local Animation = { }; ---@field timer number # Timer used to determine when to change to the next frame ---@field running boolean # Is the animation currently running and accepting updates? ---@field callback function? # Called when the animation completes -local AnimationState = { }; +local AnimationState = { } +---Load Animation Frames from path +---@param animPath string +---@return Image[] +---@return integer local function loadSequentialAnimationFrames(animPath) - local frames = { }; - local count = 0; + local frames = { } ---@type Image[] + local count = 0 - local detectedFormat = nil; + local anim_files = filesys.scandir(filesys.fromTexturePath(animPath)) - while (true) do - local frame = nil; - if (detectedFormat) then - frame = Image.new(detectedFormat:format(animPath, count + 1), true); - else - for i = 1, 4 do - local format = '%s/%0' .. i .. 'd.png'; - frame = Image.new(format:format(animPath, count + 1), true); + for index, frame_path in ipairs(anim_files) do + frame_path = filesys.join(filesys.normpath(animPath), frame_path) + DetailedLog("Frame "..index..": '"..frame_path.."'", game.LOGGER_DEBUG) + local frame = Image.new(frame_path, true) - if (frame) then - detectedFormat = format; - break; - end - end + if not frame then + DetailedLog("Could not load frame image '"..frame_path.."'", game.LOGGER_ERROR) + break end - if (not frame) then - break; - end - - count = count + 1; - frames[count] = frame; + frames[index] = frame + count = count + 1 end - return frames, count; + + return frames, count end ---Animation constructor ----@param animPath string ----@param params AnimationParams +---@param params AnimationParam ---@return Animation -function Animation.new(animPath, params) +function Animation.new(params) local self = CreateInstance(Animation, params) - local frames, frameCount = loadSequentialAnimationFrames(animPath); + self.frames, self.frameCount = loadSequentialAnimationFrames(params.animPath) - local instance = { - frames = frames, - frameCount = frameCount, + self.frameTime = 1 / (params.animFPS or 30) + self.loop = params.loop or false + self.loopPoint = params.loopPoint or 1 - frameTime = 1 / (params.fps or 30), - loop = params.loop or false, - loopPoint = params.loopPoint or 1, - }; + self.width = params.width + self.height = params.height + self.x = params.x + self.y = params.y + self.scaleX = params.scaleX + self.scaleY = params.scaleY + self.centered = params.centered + self.blendOp = params.blendOp + self.color = params.color + self.alpha = params.alpha + self.stroke = params.stroke - if (params.width ~= nil) then instance.width = params.width; end - if (params.height ~= nil) then instance.height = params.height; end - if (params.x ~= nil) then instance.x = params.x; end - if (params.y ~= nil) then instance.y = params.y; end - if (params.scaleX ~= nil) then instance.scaleX = params.scaleX; end - if (params.scaleY ~= nil) then instance.scaleY = params.scaleY; end - if (params.centered ~= nil) then instance.centered = params.centered; end - if (params.blendOp ~= nil) then instance.blendOp = params.blendOp; end - if (params.color ~= nil) then instance.color = params.color; end - if (params.alpha ~= nil) then instance.alpha = params.alpha; end - if (params.stroke ~= nil) then instance.stroke = params.stroke; end - - return CreateInstance(Animation, instance); + return self end ---Create an AnimationState to play this animation. @@ -120,85 +100,85 @@ end ---@return AnimationState function Animation:createState(callback) ---@type AnimationState - local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }; - return CreateInstance(AnimationState, state); + local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false } + return CreateInstance(AnimationState, state) end ---Create an AnimationState to play this animation and start it. ---@param callback function? ---@return AnimationState function Animation:start(callback) - local state = self:createState(callback); - state:start(); + local state = self:createState(callback) + state:start() - return state; + return state end ---Start this AnimationState. ---Does nothing if it's already running. function AnimationState:start() - self.running = true; + self.running = true end ---Restart this AnimationState. ---The frame index is reset to 1. function AnimationState:restart() - self.running = true; - self.frameIndex = 1; - self.timer = 0; + self.running = true + self.frameIndex = 1 + self.timer = 0 end ---Stop this AnimationState. function AnimationState:stop() - self.running = false; + self.running = false end ---Updates this AnimationState and then renders it, passing on the given ImageParams to each frame. ---@param deltaTime number ---@param params? ImageParams function AnimationState:render(deltaTime, params) - if (not self.running) then return; end; + if (not self.running) then return end - self.timer = self.timer + deltaTime; + self.timer = self.timer + deltaTime while (self.timer > self.animation.frameTime) do - self.timer = self.timer - self.animation.frameTime; - self.frameIndex = self.frameIndex + 1; + self.timer = self.timer - self.animation.frameTime + self.frameIndex = self.frameIndex + 1 if (self.frameIndex > self.animation.frameCount) then if (self.animation.loop) then - self.frameIndex = self.animation.loopPoint; + self.frameIndex = self.animation.loopPoint else - self.running = false; + self.running = false if (self.callback) then - self.callback(); + self.callback() end - return; + return end end end if (params) then - if (params.width == nil) then params.width = self.animation.width; end - if (params.height == nil) then params.height = self.animation.height; end - if (params.x == nil) then params.x = self.animation.x; end - if (params.y == nil) then params.y = self.animation.y; end - if (params.scaleX == nil) then params.scaleX = self.animation.scaleX; end - if (params.scaleY == nil) then params.scaleY = self.animation.scaleY; end - if (params.centered == nil) then params.centered = self.animation.centered; end - if (params.blendOp == nil) then params.blendOp = self.animation.blendOp; end - if (params.alpha == nil) then params.alpha = self.animation.alpha; end - if (params.stroke == nil) then params.stroke = self.animation.stroke; end + if (params.width == nil) then params.width = self.animation.width end + if (params.height == nil) then params.height = self.animation.height end + if (params.x == nil) then params.x = self.animation.x end + if (params.y == nil) then params.y = self.animation.y end + if (params.scaleX == nil) then params.scaleX = self.animation.scaleX end + if (params.scaleY == nil) then params.scaleY = self.animation.scaleY end + if (params.centered == nil) then params.centered = self.animation.centered end + if (params.blendOp == nil) then params.blendOp = self.animation.blendOp end + if (params.alpha == nil) then params.alpha = self.animation.alpha end + if (params.stroke == nil) then params.stroke = self.animation.stroke end end - local frame = self.animation.frames[self.frameIndex]; + local frame = self.animation.frames[self.frameIndex] if (not frame) then -- TODO(local): what do else - frame:render(params); + frame:render(params) end end -return Animation; +return Animation diff --git a/scripts/titlescreen.lua b/scripts/titlescreen.lua index 0c927f3..50a0f33 100644 --- a/scripts/titlescreen.lua +++ b/scripts/titlescreen.lua @@ -4,50 +4,18 @@ local Display = require "scripts.graphics.display" local Wallpaper = require "components.wallpaper" local PageView = require "api.page.pageview" -local PageManager = require "api.page.pagemanager" +local PageRegistry = require "api.page.pageregistry" local BootPage = require "titlescreen.boot" -local CheckUpdatePage = require "titlescreen.boot.checkupdatepage" - local SplashPage = require "titlescreen.splash" -local KShootManiaPage = require "titlescreen.splash.kshootmaniapage" -local USCPage = require "titlescreen.splash.uscpage" -local TeamExceedPage = require "titlescreen.splash.teamexceedpage" -local CreditsPage = require "titlescreen.splash.creditspage" - local TitlePage = require "titlescreen.title" - local MainMenuPage = require "titlescreen.mainmenu" local ServiceMenuPage = require "titlescreen.service" -local InputCheckPage = require "titlescreen.service.inputcheckpage" -local ScreenCheckPage = require "titlescreen.service.screencheckpage" -local ColorCheckPage = require "titlescreen.service.colorcheckpage" local VersionInfoPage = require "titlescreen.service.versioninfopage" local pageView = PageView.new() -local pageManager = PageManager.get() - -pageManager:storePage(CheckUpdatePage.new()) -pageManager:storePage(BootPage.new()) - -pageManager:storePage(KShootManiaPage.new()) -pageManager:storePage(USCPage.new()) -pageManager:storePage(TeamExceedPage.new()) -pageManager:storePage(CreditsPage.new()) -pageManager:storePage(SplashPage.new()) - -pageManager:storePage(TitlePage.new()) - -pageManager:storePage(MainMenuPage.new()) - -pageManager:storePage(InputCheckPage.new()) -pageManager:storePage(ScreenCheckPage.new()) -pageManager:storePage(ColorCheckPage.new()) -pageManager:storePage(VersionInfoPage.new()) -pageManager:storePage(ServiceMenuPage.new()) - pageView.onNavigated = function(self, back) game.Log(tostring(self) .. " navigated " .. (back and "back " or "") .. "to: " .. tostring(pageView:get()), game.LOGGER_INFO @@ -58,41 +26,40 @@ pageView.onEmptied = function(self) game.Log(tostring(self) .. " empty!", game.LOGGER_WARNING) end -local pages = PageManager.get().pages -pageManager:getPage(BootPage).onInvalidation = function(self, toVersionPage) +PageRegistry:getOrCreatePage(BootPage).onInvalidation = function(self, toVersionPage) if toVersionPage then - pageView:replace(pageManager:getPage(ServiceMenuPage)) + pageView:replace(PageRegistry:getOrCreatePage(ServiceMenuPage)) pageView:get():init() - pageView:navigate(pageManager:getPage(VersionInfoPage)) + pageView:navigate(PageRegistry:getOrCreatePage(VersionInfoPage)) pageView:get():init() else - pageView:replace(pageManager:getPage(SplashPage)) + pageView:replace(PageRegistry:getOrCreatePage(SplashPage)) pageView:get():init() end end -pageManager:getPage(SplashPage).onInvalidation = function(self) - pageView:replace(pageManager:getPage(TitlePage)) +PageRegistry:getOrCreatePage(SplashPage).onInvalidation = function(self) + pageView:replace(PageRegistry:getOrCreatePage(TitlePage)) pageView:get():init() end -pageManager:getPage(TitlePage).onInvalidation = function(self, toServiceMenu) +PageRegistry:getOrCreatePage(TitlePage).onInvalidation = function(self, toServiceMenu) if toServiceMenu then - pageView:replace(pageManager:getPage(ServiceMenuPage)) + pageView:replace(PageRegistry:getOrCreatePage(ServiceMenuPage)) pageView:get():init() else - pageView:replace(pageManager:getPage(MainMenuPage)) + pageView:replace(PageRegistry:getOrCreatePage(MainMenuPage)) pageView:get():init() end end -pageManager:getPage(ServiceMenuPage).onInvalidation = function(self) - pageView:replace(pageManager:getPage(SplashPage)) +PageRegistry:getOrCreatePage(ServiceMenuPage).onInvalidation = function(self) + pageView:replace(PageRegistry:getOrCreatePage(SplashPage)) pageView:get():init() end --local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set -pageView:replace(pages[BootPage.__name]) +pageView:replace(PageRegistry:getOrCreatePage(BootPage)) pageView:get():init() local function deltaKnob(delta) diff --git a/scripts/titlescreen/boot.lua b/scripts/titlescreen/boot.lua index d98756a..439d532 100644 --- a/scripts/titlescreen/boot.lua +++ b/scripts/titlescreen/boot.lua @@ -4,7 +4,7 @@ require "common.filereader" local Display = require "scripts.graphics.display" local Version = require "common.version" -local PageManager = require "api.page.pagemanager" +local PageRegistry = require "api.page.pageregistry" local Page = require "api.page.page" local CheckUpdatePage = require "titlescreen.boot.checkupdatepage" @@ -26,10 +26,10 @@ function BootPage.new(params) local self = CreateInstance(BootPage, params, Page) - local pageManager = PageManager.get() + local checkUpdatePage = PageRegistry:getOrCreatePage(CheckUpdatePage) self._networkResult = {} - pageManager:getPage(CheckUpdatePage).onInvalidation = function (page_self, reason) + checkUpdatePage.onInvalidation = function (page_self, reason) self:onInvalidation(reason) end @@ -87,7 +87,7 @@ function BootPage.new(params) IR.Heartbeat(function(res) self._networkResult = res end) -- IR doesn't like being called in a coroutine elseif status == SelfTestStatus.PASS or status == SelfTestStatus.OK then if self.viewHandler then - self.viewHandler:navigate(pageManager:getPage(CheckUpdatePage)) + self.viewHandler:navigate(checkUpdatePage) self.viewHandler:get():init() end end diff --git a/scripts/titlescreen/common/logging.lua b/scripts/titlescreen/common/logging.lua new file mode 100644 index 0000000..720e518 --- /dev/null +++ b/scripts/titlescreen/common/logging.lua @@ -0,0 +1,34 @@ +---Return a string representation of where this function was called from +---@param affix? string +---@param stack_level? integer +---@return string +function Where(affix, stack_level) + stack_level = stack_level or 2 + + local fun_name = debug.getinfo(stack_level, "n").name + + local current_file = debug.getinfo(stack_level, "S").source + local current_line = debug.getinfo(stack_level, "l").currentline + + --remove redundant path components + current_file = current_file:gsub("([@=]?).*[\\/]skins[\\/]", "%1") + + local where = current_file..":"..current_line + + if fun_name then + where = where.." ("..fun_name..")" + end + + if affix then + where = where..affix + end + + return where +end + +---Same as game.Log, but prefixed with where the call happened +---@param message string +---@param severity integer +function DetailedLog(message, severity) + game.Log(Where(": ", 3)..message, severity) +end diff --git a/scripts/titlescreen/common/page/pageregistry.lua b/scripts/titlescreen/common/page/pageregistry.lua index 67c0925..5e5addf 100644 --- a/scripts/titlescreen/common/page/pageregistry.lua +++ b/scripts/titlescreen/common/page/pageregistry.lua @@ -1,36 +1,21 @@ require "common.globals" require "common.class" ----@type PageRegistry -local instance = nil +require "api.logging" ---@class PageRegistry ---@field pages Page[] local PageRegistry = { - __name = "PageManager" + __name = "PageManager", + pages = {}, } ----Create new PageManager instance +---Reset and initialize PageManager global ---@param params? PageRegistry ----@return PageRegistry -function PageRegistry.new(params) +function PageRegistry:init(params) params = params or {} - local self = CreateInstance(PageRegistry, params) - self.pages = params.pages or {} - - return self -end - ----Get PageManager instance ----@return PageRegistry -function PageRegistry.get() - if not instance then - instance = PageRegistry.new() - end - - return instance end ---Store page with default name @@ -65,4 +50,17 @@ function PageRegistry:getPage(page) return nil end +---Get page by class +--- +---Ensure valid page instance by creating and registering one, if not found +---@param page Page +function PageRegistry:getOrCreatePage(page) + if not self:getPage(page) then + page = page.new() + self:register(page) + end + + return page +end + return PageRegistry diff --git a/scripts/titlescreen/mainmenu.lua b/scripts/titlescreen/mainmenu.lua index 2789027..8c01d4a 100644 --- a/scripts/titlescreen/mainmenu.lua +++ b/scripts/titlescreen/mainmenu.lua @@ -485,11 +485,12 @@ local crew = game.GetSkinSetting("single_idol") local MainMenuPage = { __name = "MainMenuPage", ANIM = { - idolAnimation = Animation.new("crew/anim/" .. crew, { - fps = 30, loop = true, + idolAnimation = Animation.new{ + animPath = "crew/anim/" .. crew, + animFPS = 30, loop = true, centered = true, x = Display.design.width / 2, y = Display.design.height / 2, width = Display.design.width, height = Display.design.height, - }) + } }, AUDIO = { bgm = AudioSample.new{path = "titlescreen/bgm.wav", exclusive = true, loop = true}, diff --git a/scripts/titlescreen/service.lua b/scripts/titlescreen/service.lua index fcf94e2..27452be 100644 --- a/scripts/titlescreen/service.lua +++ b/scripts/titlescreen/service.lua @@ -1,6 +1,6 @@ require("common.class") -local PageManager = require "api.page.pagemanager" +local PageRegistry = require "api.page.pageregistry" local ServicePage = require("api.page.servicepage") local InputCheckPage = require("titlescreen.service.inputcheckpage") @@ -18,7 +18,7 @@ local ServiceMenuPage = { ---Create a new MainMenuPage instance ---@param params? MainMenuPage # initial parameters ----@return MainMenuPage +---@return ServiceMenuPage function ServiceMenuPage.new(params) params = params or {} @@ -26,12 +26,11 @@ function ServiceMenuPage.new(params) local self = CreateInstance(ServiceMenuPage, params, ServicePage) - local pageManager = PageManager.get() local list = ListField.new() - list:addField(ServiceLinkField.new{label = "INPUT CHECK", link = pageManager:getPage(InputCheckPage)}) - list:addField(ServiceLinkField.new{label = "SCREEN CHECK", link = pageManager:getPage(ScreenCheckPage)}) - list:addField(ServiceLinkField.new{label = "COLOR CHECK", link = pageManager:getPage(ColorCheckPage)}) - list:addField(ServiceLinkField.new{label = "VERSION INFORMATION", link = pageManager:getPage(VersionInfoPage)}) + list:addField(ServiceLinkField.new{label = "INPUT CHECK", link = PageRegistry:getOrCreatePage(InputCheckPage)}) + list:addField(ServiceLinkField.new{label = "SCREEN CHECK", link = PageRegistry:getOrCreatePage(ScreenCheckPage)}) + list:addField(ServiceLinkField.new{label = "COLOR CHECK", link = PageRegistry:getOrCreatePage(ColorCheckPage)}) + list:addField(ServiceLinkField.new{label = "VERSION INFORMATION", link = PageRegistry:getOrCreatePage(VersionInfoPage)}) list:refreshFields() self:addField(list) diff --git a/scripts/titlescreen/splash.lua b/scripts/titlescreen/splash.lua index 08e774c..5c41b0d 100644 --- a/scripts/titlescreen/splash.lua +++ b/scripts/titlescreen/splash.lua @@ -4,7 +4,7 @@ local Display = require("scripts.graphics.display") require "common.globals" require "common.class" -local PageManager = require "api.page.pagemanager" +local PageRegistry = require "api.page.pageregistry" local Page = require "api.page.page" local KShootManiaPage = require "titlescreen.splash.kshootmaniapage" @@ -29,14 +29,12 @@ local SplashPage = { function SplashPage.new(params) local self = CreateInstance(SplashPage, params, Page) - local pageManager = PageManager.get() - self.currentPage = 0 -- start on nil page self.content = { - pageManager:getPage(KShootManiaPage), - pageManager:getPage(USCPage), - pageManager:getPage(TeamExceedPage), - pageManager:getPage(CreditsPage), + PageRegistry:getOrCreatePage(KShootManiaPage), + PageRegistry:getOrCreatePage(USCPage), + PageRegistry:getOrCreatePage(TeamExceedPage), + PageRegistry:getOrCreatePage(CreditsPage), } self._isTransitioning = true -- immediately transition to first page