Merge branch 'titlescreen-refactor' of git.hersi.changeip.net:hersi/ExperimentalGear into titlescreen-refactor

This commit is contained in:
Hersi 2024-02-02 02:54:59 +01:00
commit ca2d979fcf
11 changed files with 255 additions and 237 deletions

View File

@ -18,23 +18,34 @@ HitStat = {}
---@field type integer -- `1 = Normal` default, `2 = Hard` default values halved ---@field type integer -- `1 = Normal` default, `2 = Hard` default values halved
HitWindow = {} HitWindow = {}
---@class Score ---@class HiddenSudden
---@field auto_flags integer # Autoplay flag ---@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 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 number # Ending gauge percentage, `0.0` to `1.0`
---@field gauge_option integer # Gauge option e.g. ARS ---@field gauge_option integer # Only Singleplayer, Gauge option e.g. ARS
---@field gauge_type integer # `0` = Normal, `1` = Hard, `2` = Permissive, `3` = Blastive ---@field gauge_type integer # Only Singleplayer, `0` = Normal, `1` = Hard, `2` = Permissive, `3` = Blastive
---@field goods integer # Total near hits ---@field goods integer # Total near hits
---@field hitWindow HitWindow|nil # Hit windows of the score, only for singleplayer results screen ---@field hitWindow nil|HitWindow # Only Singleplayer, Hit windows of the score
---@field mirror integer # Mirror mode flag ---@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 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 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 score integer # Result score
---@field timestamp integer # Unix timestamp of the score ---@field timestamp integer # Unix timestamp of the score
---@field uid nil|string # Only for multiplayer results, UID of the player ---@field uid nil|string # Only for multiplayer results, UID of the player
Score = {} HighScore = {};
---@class ChartResult : result ---@class ChartResult : result
---@field passed boolean # Whether or not challenge requirements were met for this chart ---@field passed boolean # Whether or not challenge requirements were met for this chart
@ -42,27 +53,30 @@ Score = {}
ChartResult = {} ChartResult = {}
---@class ServerScoreOptions ---@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 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 mirror boolean # Mirror mode enabled
---@field random boolean # Note shuffle 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 = {} ServerScoreOptions = {}
---@class ServerScore ---@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 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 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 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 windows table # {perfect, good, hold, miss, slam} hit windows in milliseconds
---@field yours boolean # This score belongs to the current player ---@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 = {} ServerScore = {}
---@class result ---@class result
@ -92,16 +106,17 @@ ServerScore = {}
---@field gaugeSamples number[] # Gauge values sampled (256 total) throughout the play ---@field gaugeSamples number[] # Gauge values sampled (256 total) throughout the play
---@field goods integer # Total near hits ---@field goods integer # Total near hits
---@field grade string # Result grade ---@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 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 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 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 irDescription nil|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 irScores nil|ServerScore[] # Score submission result, nil if irState != 20
---@field isSelf boolean # Only for multiplayer, `false` if score is of another player ---@field isSelf boolean # `false` if score is of another player
---@field jacketPath string # Full filepath to the jacket image on the disk ---@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 lates integer # Total late hits
---@field level integer # Chart or challenge level ---@field level integer # Chart or challenge level
---@field maxCombo integer # Result max chain ---@field maxCombo integer # Result max chain
@ -112,7 +127,7 @@ ServerScore = {}
---@field mirror boolean # Mirror mode bool ---@field mirror boolean # Mirror mode bool
---@field misses integer # Total errors ---@field misses integer # Total errors
---@field mission string # Only for practice mode ---@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 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 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 ---@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 requirement_text string # Only for challenge results, the challenge requirements separated by newline character `"\n"`
---@field retryCount integer # Only for practice mode ---@field retryCount integer # Only for practice mode
---@field score integer # Result score ---@field score integer # Result score
---@field speedModType integer # Only for singleplayer, `0` = XMOD, `1` = MMOD, `2` = CMOD ---@field speedModType nil|integer # Only for singleplayer, `0` = XMOD, `1` = MMOD, `2` = CMOD
---@field speedModValue number # Only for singleplayer, `HiSpeed` for `XMOD`, `ModSpeed` otherwise ---@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 title string # Chart (with player name in multiplayer) or challenge title
---@field uid nil|string # Only for multiplayer, UID of the player ---@field uid nil|string # Only for multiplayer, UID of the player
result = {} result = {}

View File

@ -1,7 +1,17 @@
-- songwheel `songwheel` table -- 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 ---@class Difficulty
---@field difficulty integer # Difficulty index ---@field difficulty integer # Difficulty index
---@field effector string # Effector name
---@field hash string # Difficulty hash ---@field hash string # Difficulty hash
---@field id integer # Difficulty id, unique static identifier ---@field id integer # Difficulty id, unique static identifier
---@field illustrator string # Difficulty jacket illustrator ---@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` ---@field topBadge integer # `0 = Never Played`, `1 = Played`, `2 = Cleared`, `3 = Hard Cleared`, `4 = Full Chain`, `5 = Perfect Chain`
Difficulty = {} Difficulty = {}
---@class Song ---@class Score
---@field artist string # Chart artist ---@field auto_flags integer # Autoplay flag
---@field difficulties Difficulty[] # Array of difficulties for the current song ---@field badge integer # `0` = Manual Exit, `1` = Played, `2` = Cleared, `3` = Hard Cleared, `4` = Full Chain, `5` = Perfect Chain
---@field bpm number # Chart BPM ---@field combo integer # Longest combo
---@field id integer # Song id, unique static identifier ---@field earlies integer # Early hits
---@field path string # Full filepath to the chart folder on the disk ---@field gauge number # Ending gauge percentage, `0.0` to `1.0`
---@field title string # Chart title ---@field gauge_option integer # Gauge option e.g. ARS
Song = {} ---@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 ---@class songwheel
---@field allSongs Song[] # Array of all available songs ---@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 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 ---@field songs Song[] # Array of songs with the current filters/sorting applied
songwheel = {} 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

View File

@ -1,4 +1,6 @@
---@diagnostic disable: duplicate-set-field ---@diagnostic disable: duplicate-set-field
require "common.globals"
require "api.logging"
require("core.string") require("core.string")
@ -35,7 +37,7 @@ end
function filesys.exists(path) function filesys.exists(path)
local ok, err, code = os.rename(path, path) local ok, err, code = os.rename(path, path)
if not ok then 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 if code == 13 then
-- Permission denied, but it exists -- Permission denied, but it exists
return true return true
@ -44,6 +46,20 @@ function filesys.exists(path)
return ok, err return ok, err
end 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/<skin>/textures/<path>
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 a string representing the current working directory
---@return string ---@return string
function filesys.getcwd() function filesys.getcwd()
@ -68,9 +84,10 @@ end
---Normalize `path`, collapse redundant separators and up references ---Normalize `path`, collapse redundant separators and up references
---@param path string ---@param path string
---@param overrideSep? string
---@return string ---@return string
function filesys.normpath(path) function filesys.normpath(path, overrideSep)
local sep = filesys.sep local sep = overrideSep or filesys.sep
--remove multiple slashes --remove multiple slashes
path = path:gsub("("..sep..")"..sep.."+", "%1") path = path:gsub("("..sep..")"..sep.."+", "%1")
@ -83,7 +100,7 @@ function filesys.normpath(path)
local upRefPattern = "%w+"..sep.."%.%."..sep local upRefPattern = "%w+"..sep.."%.%."..sep
repeat repeat
path, count = path:gsub(upRefPattern, "") path, count = path:gsub(upRefPattern, "")
until count ~= 0 until count == 0
--remove last slash --remove last slash
path = path:gsub("(.-)"..sep.."$", "%1") path = path:gsub("(.-)"..sep.."$", "%1")

View File

@ -1,25 +1,16 @@
require "api.logging"
require "common.class" require "common.class"
require "api.filesys"
require "api.graphics" require "api.graphics"
local Image = require "scripts.graphics.image" local Image = require "scripts.graphics.image"
---@class AnimationParams ---@class AnimationParam: Animation
---@field fps number? ---@field animPath string
---@field loop boolean? ---@field animFPS number
---@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 Animation ---@class Animation
---@field frames Image[] ---@field frames Image[]
@ -38,7 +29,7 @@ local Image = require "scripts.graphics.image"
---@field color number[]? ---@field color number[]?
---@field alpha number? ---@field alpha number?
---@field stroke StrokeParams? ---@field stroke StrokeParams?
local Animation = { }; local Animation = { }
---@class AnimationState ---@class AnimationState
---@field animation Animation # The animation data this state is playing through ---@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 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 running boolean # Is the animation currently running and accepting updates?
---@field callback function? # Called when the animation completes ---@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 function loadSequentialAnimationFrames(animPath)
local frames = { }; local frames = { } ---@type Image[]
local count = 0; local count = 0
local detectedFormat = nil; local anim_files = filesys.scandir(filesys.fromTexturePath(animPath))
while (true) do for index, frame_path in ipairs(anim_files) do
local frame = nil; frame_path = filesys.join(filesys.normpath(animPath), frame_path)
if (detectedFormat) then DetailedLog("Frame "..index..": '"..frame_path.."'", game.LOGGER_DEBUG)
frame = Image.new(detectedFormat:format(animPath, count + 1), true); local frame = Image.new(frame_path, true)
else
for i = 1, 4 do
local format = '%s/%0' .. i .. 'd.png';
frame = Image.new(format:format(animPath, count + 1), true);
if (frame) then if not frame then
detectedFormat = format; DetailedLog("Could not load frame image '"..frame_path.."'", game.LOGGER_ERROR)
break; break
end
end
end end
if (not frame) then frames[index] = frame
break; count = count + 1
end
count = count + 1;
frames[count] = frame;
end end
return frames, count;
return frames, count
end end
---Animation constructor ---Animation constructor
---@param animPath string ---@param params AnimationParam
---@param params AnimationParams
---@return Animation ---@return Animation
function Animation.new(animPath, params) function Animation.new(params)
local self = CreateInstance(Animation, params) local self = CreateInstance(Animation, params)
local frames, frameCount = loadSequentialAnimationFrames(animPath); self.frames, self.frameCount = loadSequentialAnimationFrames(params.animPath)
local instance = { self.frameTime = 1 / (params.animFPS or 30)
frames = frames, self.loop = params.loop or false
frameCount = frameCount, self.loopPoint = params.loopPoint or 1
frameTime = 1 / (params.fps or 30), self.width = params.width
loop = params.loop or false, self.height = params.height
loopPoint = params.loopPoint or 1, 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 return self
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);
end end
---Create an AnimationState to play this animation. ---Create an AnimationState to play this animation.
@ -120,85 +100,85 @@ end
---@return AnimationState ---@return AnimationState
function Animation:createState(callback) function Animation:createState(callback)
---@type AnimationState ---@type AnimationState
local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }; local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }
return CreateInstance(AnimationState, state); return CreateInstance(AnimationState, state)
end end
---Create an AnimationState to play this animation and start it. ---Create an AnimationState to play this animation and start it.
---@param callback function? ---@param callback function?
---@return AnimationState ---@return AnimationState
function Animation:start(callback) function Animation:start(callback)
local state = self:createState(callback); local state = self:createState(callback)
state:start(); state:start()
return state; return state
end end
---Start this AnimationState. ---Start this AnimationState.
---Does nothing if it's already running. ---Does nothing if it's already running.
function AnimationState:start() function AnimationState:start()
self.running = true; self.running = true
end end
---Restart this AnimationState. ---Restart this AnimationState.
---The frame index is reset to 1. ---The frame index is reset to 1.
function AnimationState:restart() function AnimationState:restart()
self.running = true; self.running = true
self.frameIndex = 1; self.frameIndex = 1
self.timer = 0; self.timer = 0
end end
---Stop this AnimationState. ---Stop this AnimationState.
function AnimationState:stop() function AnimationState:stop()
self.running = false; self.running = false
end end
---Updates this AnimationState and then renders it, passing on the given ImageParams to each frame. ---Updates this AnimationState and then renders it, passing on the given ImageParams to each frame.
---@param deltaTime number ---@param deltaTime number
---@param params? ImageParams ---@param params? ImageParams
function AnimationState:render(deltaTime, params) 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 while (self.timer > self.animation.frameTime) do
self.timer = self.timer - self.animation.frameTime; self.timer = self.timer - self.animation.frameTime
self.frameIndex = self.frameIndex + 1; self.frameIndex = self.frameIndex + 1
if (self.frameIndex > self.animation.frameCount) then if (self.frameIndex > self.animation.frameCount) then
if (self.animation.loop) then if (self.animation.loop) then
self.frameIndex = self.animation.loopPoint; self.frameIndex = self.animation.loopPoint
else else
self.running = false; self.running = false
if (self.callback) then if (self.callback) then
self.callback(); self.callback()
end end
return; return
end end
end end
end end
if (params) then if (params) then
if (params.width == nil) then params.width = self.animation.width; 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.height == nil) then params.height = self.animation.height end
if (params.x == nil) then params.x = self.animation.x; 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.y == nil) then params.y = self.animation.y end
if (params.scaleX == nil) then params.scaleX = self.animation.scaleX; 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.scaleY == nil) then params.scaleY = self.animation.scaleY end
if (params.centered == nil) then params.centered = self.animation.centered; 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.blendOp == nil) then params.blendOp = self.animation.blendOp end
if (params.alpha == nil) then params.alpha = self.animation.alpha; 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.stroke == nil) then params.stroke = self.animation.stroke end
end end
local frame = self.animation.frames[self.frameIndex]; local frame = self.animation.frames[self.frameIndex]
if (not frame) then if (not frame) then
-- TODO(local): what do -- TODO(local): what do
else else
frame:render(params); frame:render(params)
end end
end end
return Animation; return Animation

View File

@ -4,50 +4,18 @@ local Display = require "scripts.graphics.display"
local Wallpaper = require "components.wallpaper" local Wallpaper = require "components.wallpaper"
local PageView = require "api.page.pageview" local PageView = require "api.page.pageview"
local PageManager = require "api.page.pagemanager" local PageRegistry = require "api.page.pageregistry"
local BootPage = require "titlescreen.boot" local BootPage = require "titlescreen.boot"
local CheckUpdatePage = require "titlescreen.boot.checkupdatepage"
local SplashPage = require "titlescreen.splash" 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 TitlePage = require "titlescreen.title"
local MainMenuPage = require "titlescreen.mainmenu" local MainMenuPage = require "titlescreen.mainmenu"
local ServiceMenuPage = require "titlescreen.service" 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 VersionInfoPage = require "titlescreen.service.versioninfopage"
local pageView = PageView.new() 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) pageView.onNavigated = function(self, back)
game.Log(tostring(self) .. " navigated " .. (back and "back " or "") .. "to: " .. tostring(pageView:get()), game.Log(tostring(self) .. " navigated " .. (back and "back " or "") .. "to: " .. tostring(pageView:get()),
game.LOGGER_INFO game.LOGGER_INFO
@ -58,41 +26,40 @@ pageView.onEmptied = function(self)
game.Log(tostring(self) .. " empty!", game.LOGGER_WARNING) game.Log(tostring(self) .. " empty!", game.LOGGER_WARNING)
end end
local pages = PageManager.get().pages PageRegistry:getOrCreatePage(BootPage).onInvalidation = function(self, toVersionPage)
pageManager:getPage(BootPage).onInvalidation = function(self, toVersionPage)
if toVersionPage then if toVersionPage then
pageView:replace(pageManager:getPage(ServiceMenuPage)) pageView:replace(PageRegistry:getOrCreatePage(ServiceMenuPage))
pageView:get():init() pageView:get():init()
pageView:navigate(pageManager:getPage(VersionInfoPage)) pageView:navigate(PageRegistry:getOrCreatePage(VersionInfoPage))
pageView:get():init() pageView:get():init()
else else
pageView:replace(pageManager:getPage(SplashPage)) pageView:replace(PageRegistry:getOrCreatePage(SplashPage))
pageView:get():init() pageView:get():init()
end end
end end
pageManager:getPage(SplashPage).onInvalidation = function(self) PageRegistry:getOrCreatePage(SplashPage).onInvalidation = function(self)
pageView:replace(pageManager:getPage(TitlePage)) pageView:replace(PageRegistry:getOrCreatePage(TitlePage))
pageView:get():init() pageView:get():init()
end end
pageManager:getPage(TitlePage).onInvalidation = function(self, toServiceMenu) PageRegistry:getOrCreatePage(TitlePage).onInvalidation = function(self, toServiceMenu)
if toServiceMenu then if toServiceMenu then
pageView:replace(pageManager:getPage(ServiceMenuPage)) pageView:replace(PageRegistry:getOrCreatePage(ServiceMenuPage))
pageView:get():init() pageView:get():init()
else else
pageView:replace(pageManager:getPage(MainMenuPage)) pageView:replace(PageRegistry:getOrCreatePage(MainMenuPage))
pageView:get():init() pageView:get():init()
end end
end end
pageManager:getPage(ServiceMenuPage).onInvalidation = function(self) PageRegistry:getOrCreatePage(ServiceMenuPage).onInvalidation = function(self)
pageView:replace(pageManager:getPage(SplashPage)) pageView:replace(PageRegistry:getOrCreatePage(SplashPage))
pageView:get():init() pageView:get():init()
end end
--local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set --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() pageView:get():init()
local function deltaKnob(delta) local function deltaKnob(delta)

View File

@ -4,7 +4,7 @@ require "common.filereader"
local Display = require "scripts.graphics.display" local Display = require "scripts.graphics.display"
local Version = require "common.version" local Version = require "common.version"
local PageManager = require "api.page.pagemanager" local PageRegistry = require "api.page.pageregistry"
local Page = require "api.page.page" local Page = require "api.page.page"
local CheckUpdatePage = require "titlescreen.boot.checkupdatepage" local CheckUpdatePage = require "titlescreen.boot.checkupdatepage"
@ -26,10 +26,10 @@ function BootPage.new(params)
local self = CreateInstance(BootPage, params, Page) local self = CreateInstance(BootPage, params, Page)
local pageManager = PageManager.get() local checkUpdatePage = PageRegistry:getOrCreatePage(CheckUpdatePage)
self._networkResult = {} self._networkResult = {}
pageManager:getPage(CheckUpdatePage).onInvalidation = function (page_self, reason) checkUpdatePage.onInvalidation = function (page_self, reason)
self:onInvalidation(reason) self:onInvalidation(reason)
end 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 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 elseif status == SelfTestStatus.PASS or status == SelfTestStatus.OK then
if self.viewHandler then if self.viewHandler then
self.viewHandler:navigate(pageManager:getPage(CheckUpdatePage)) self.viewHandler:navigate(checkUpdatePage)
self.viewHandler:get():init() self.viewHandler:get():init()
end end
end end

View File

@ -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

View File

@ -1,36 +1,21 @@
require "common.globals" require "common.globals"
require "common.class" require "common.class"
---@type PageRegistry require "api.logging"
local instance = nil
---@class PageRegistry ---@class PageRegistry
---@field pages Page[] ---@field pages Page[]
local PageRegistry = { local PageRegistry = {
__name = "PageManager" __name = "PageManager",
pages = {},
} }
---Create new PageManager instance ---Reset and initialize PageManager global
---@param params? PageRegistry ---@param params? PageRegistry
---@return PageRegistry function PageRegistry:init(params)
function PageRegistry.new(params)
params = params or {} params = params or {}
local self = CreateInstance(PageRegistry, params)
self.pages = params.pages or {} 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 end
---Store page with default name ---Store page with default name
@ -65,4 +50,17 @@ function PageRegistry:getPage(page)
return nil return nil
end 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 return PageRegistry

View File

@ -485,11 +485,12 @@ local crew = game.GetSkinSetting("single_idol")
local MainMenuPage = { local MainMenuPage = {
__name = "MainMenuPage", __name = "MainMenuPage",
ANIM = { ANIM = {
idolAnimation = Animation.new("crew/anim/" .. crew, { idolAnimation = Animation.new{
fps = 30, loop = true, animPath = "crew/anim/" .. crew,
animFPS = 30, loop = true,
centered = true, x = Display.design.width / 2, y = Display.design.height / 2, centered = true, x = Display.design.width / 2, y = Display.design.height / 2,
width = Display.design.width, height = Display.design.height, width = Display.design.width, height = Display.design.height,
}) }
}, },
AUDIO = { AUDIO = {
bgm = AudioSample.new{path = "titlescreen/bgm.wav", exclusive = true, loop = true}, bgm = AudioSample.new{path = "titlescreen/bgm.wav", exclusive = true, loop = true},

View File

@ -1,6 +1,6 @@
require("common.class") require("common.class")
local PageManager = require "api.page.pagemanager" local PageRegistry = require "api.page.pageregistry"
local ServicePage = require("api.page.servicepage") local ServicePage = require("api.page.servicepage")
local InputCheckPage = require("titlescreen.service.inputcheckpage") local InputCheckPage = require("titlescreen.service.inputcheckpage")
@ -18,7 +18,7 @@ local ServiceMenuPage = {
---Create a new MainMenuPage instance ---Create a new MainMenuPage instance
---@param params? MainMenuPage # initial parameters ---@param params? MainMenuPage # initial parameters
---@return MainMenuPage ---@return ServiceMenuPage
function ServiceMenuPage.new(params) function ServiceMenuPage.new(params)
params = params or {} params = params or {}
@ -26,12 +26,11 @@ function ServiceMenuPage.new(params)
local self = CreateInstance(ServiceMenuPage, params, ServicePage) local self = CreateInstance(ServiceMenuPage, params, ServicePage)
local pageManager = PageManager.get()
local list = ListField.new() local list = ListField.new()
list:addField(ServiceLinkField.new{label = "INPUT CHECK", link = pageManager:getPage(InputCheckPage)}) list:addField(ServiceLinkField.new{label = "INPUT CHECK", link = PageRegistry:getOrCreatePage(InputCheckPage)})
list:addField(ServiceLinkField.new{label = "SCREEN CHECK", link = pageManager:getPage(ScreenCheckPage)}) list:addField(ServiceLinkField.new{label = "SCREEN CHECK", link = PageRegistry:getOrCreatePage(ScreenCheckPage)})
list:addField(ServiceLinkField.new{label = "COLOR CHECK", link = pageManager:getPage(ColorCheckPage)}) list:addField(ServiceLinkField.new{label = "COLOR CHECK", link = PageRegistry:getOrCreatePage(ColorCheckPage)})
list:addField(ServiceLinkField.new{label = "VERSION INFORMATION", link = pageManager:getPage(VersionInfoPage)}) list:addField(ServiceLinkField.new{label = "VERSION INFORMATION", link = PageRegistry:getOrCreatePage(VersionInfoPage)})
list:refreshFields() list:refreshFields()
self:addField(list) self:addField(list)

View File

@ -4,7 +4,7 @@ local Display = require("scripts.graphics.display")
require "common.globals" require "common.globals"
require "common.class" require "common.class"
local PageManager = require "api.page.pagemanager" local PageRegistry = require "api.page.pageregistry"
local Page = require "api.page.page" local Page = require "api.page.page"
local KShootManiaPage = require "titlescreen.splash.kshootmaniapage" local KShootManiaPage = require "titlescreen.splash.kshootmaniapage"
@ -29,14 +29,12 @@ local SplashPage = {
function SplashPage.new(params) function SplashPage.new(params)
local self = CreateInstance(SplashPage, params, Page) local self = CreateInstance(SplashPage, params, Page)
local pageManager = PageManager.get()
self.currentPage = 0 -- start on nil page self.currentPage = 0 -- start on nil page
self.content = { self.content = {
pageManager:getPage(KShootManiaPage), PageRegistry:getOrCreatePage(KShootManiaPage),
pageManager:getPage(USCPage), PageRegistry:getOrCreatePage(USCPage),
pageManager:getPage(TeamExceedPage), PageRegistry:getOrCreatePage(TeamExceedPage),
pageManager:getPage(CreditsPage), PageRegistry:getOrCreatePage(CreditsPage),
} }
self._isTransitioning = true -- immediately transition to first page self._isTransitioning = true -- immediately transition to first page