Compare commits
65 Commits
8bbea73be5
...
master
Author | SHA1 | Date |
---|---|---|
Hersi | 8101e2737b | |
Hersi | d846014d18 | |
Hersi | ebc01d1eeb | |
Hersi | 6b51ddd739 | |
Hersi | 841ca2892a | |
Hersi | d0b1bcfb79 | |
Hersi | a2bb617959 | |
Hersi | 4f3131a388 | |
Hersi | 0951fc029c | |
Hersi | bc191e45f0 | |
Hersi | 2363b381ed | |
Hersi | a0ceb0bd01 | |
Hersi | e9d848d92f | |
Hersi | cba1ebd790 | |
Hersi | 52cc1beb7d | |
Hersi | e876def36f | |
Hersi | d6e4c84a43 | |
Hersi | 48b503ac83 | |
Hersi | 7768ae2175 | |
Hersi | d7e5dbd2d1 | |
Hersi | 256ebc3c0f | |
Hersi | 430678ad23 | |
Hersi | 79a0325e20 | |
Hersi | ac4ba5b1a4 | |
Hersi | 65ed417b26 | |
Hersi | 52f948508c | |
Hersi | a04890dab9 | |
Hersi | 9d04551ef3 | |
Hersi | b68a8022b6 | |
Hersi | 30214f58fa | |
Hersi | 7694a23368 | |
Hersi | 7f7b6da7d9 | |
Hersi | 9274c216d6 | |
Hersi | 2d7dbe74f1 | |
Hersi | f1e75e07b1 | |
Hersi | b766b99324 | |
Hersi | 9dc6f598ae | |
Hersi | 529da2d440 | |
Hersi | 3785898e40 | |
Hersi | 590662c9a9 | |
Hersi | 20600b9ca5 | |
Hersi | 028a1e9f74 | |
Hersi | 9c4b7fdbe9 | |
doczi-dominik | bbc74a8d7e | |
Hersi | dc1b444c92 | |
Hersi | 0b2257fd27 | |
domdoc | 35ac51be7d | |
Hersi | 313b9819dd | |
Katoski | 6d342c883f | |
Hersi | 0d36dbebdc | |
domdoc | 614dcfe9b7 | |
domdoc | c3ad3b6323 | |
domdoc | 1eedcbe5cd | |
Hersi | 47372bf14f | |
Hersi | 2c2068b5f0 | |
domdoc | 96f331e1e3 | |
domdoc | e5594c5978 | |
domdoc | b4f7709a3e | |
domdoc | d1d620a599 | |
domdoc | 39631eb320 | |
domdoc | 962f2c27d7 | |
Local Atticus | 7f3dc98cbf | |
gskbladez | 2d8fc019c8 | |
fdigl | 3617862364 | |
fdigl | 2188f4dbce |
|
@ -33,7 +33,7 @@
|
|||
|
||||
"Crew": { "type": "label" },
|
||||
"single_idol": {
|
||||
"label": "!!!ALWAYS MATCH THE NAME!!!",
|
||||
"label": "Crew idol animations, folder name in `crew/anim/`",
|
||||
"type": "text",
|
||||
"default": "nothing"
|
||||
},
|
||||
|
@ -97,5 +97,28 @@
|
|||
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_g": {},
|
||||
"Experimental features": { "type": "label" },
|
||||
|
||||
"songselect_showEffectRadar": {
|
||||
"label": "Show Effect Radar for compatible songs (VERY WIP)",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"songselect_enableTimer": {
|
||||
"label": "Display a countdown timer until a demo is played, when available (Buggy)",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"songselect_freezeTimer": {
|
||||
"label": "Freeze timer on value (in seconds, -1 to disable)",
|
||||
"type": "int",
|
||||
"default": -1,
|
||||
"min": -1,
|
||||
"max": 5999
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,47 +23,53 @@ IRData = {}
|
|||
---@field serverTime integer
|
||||
---@field serverName string
|
||||
---@field irVersion string
|
||||
IRHeartbeatResponseBody = {}
|
||||
|
||||
---@class IRRecordResponseBody
|
||||
---@field record ServerScore
|
||||
IRRecordResponseBody = {}
|
||||
|
||||
---@class IRLeaderboardResponseBody
|
||||
---@field scores ServerScore[]
|
||||
IRLeaderboardResponseBody = {}
|
||||
---@alias IRLeaderboardResponseBody ServerScore[]
|
||||
|
||||
---@class IRResponse
|
||||
---@field statusCode integer
|
||||
---@field description string
|
||||
---@field body nil|IRHeartbeatResponseBody|IRRecordResponseBody|IRLeaderboardResponseBody
|
||||
IRResponse = {}
|
||||
|
||||
---@class IRHeartbeatResponse : IRResponse
|
||||
---@field body IRHeartbeatResponseBody
|
||||
|
||||
---@class IRChartTrackedResponse : IRResponse
|
||||
---@field body {}
|
||||
|
||||
---@class IRRecordResponse : IRResponse
|
||||
---@field body IRRecordResponseBody
|
||||
|
||||
---@class IRLeaderboardResponse : IRResponse
|
||||
---@field body ServerScore[]
|
||||
|
||||
-- Performs a Heartbeat request.
|
||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
||||
---@param callback fun(res: IRHeartbeatResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Heartbeat(callback) end
|
||||
|
||||
-- Performs a Chart Tracked request for the chart with the provided hash.
|
||||
---@param hash string # song hash
|
||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
||||
---@param callback fun(res: IRChartTrackedResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function ChartTracked(hash, callback) end
|
||||
|
||||
-- Performs a Record request for the chart with the provided hash.
|
||||
---@param hash string # song hash
|
||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
||||
---@param callback fun(res: IRRecordResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Record(hash, callback) end
|
||||
|
||||
-- Performs a Leaderboard request for the chart with the provided hash, with parameters mode and n.
|
||||
---@param hash string # song hash
|
||||
---@param mode "best"|"rivals" # request leaderboard mode
|
||||
---@param n integer # limit the number of requested scores
|
||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
||||
---@param callback fun(res: IRLeaderboardResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Leaderboard(hash, mode, n, callback) end
|
||||
|
||||
---@type table
|
||||
---@class IR
|
||||
IR = {
|
||||
Heartbeat = Heartbeat,
|
||||
ChartTracked = ChartTracked,
|
||||
Record = Record,
|
||||
Leaderboard = Leaderboard
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
-- challengeresult `result` table
|
||||
|
||||
---@diagnostic disable:lowercase-global
|
||||
---@diagnostic disable:missing-return
|
||||
|
||||
---@class ChallengeHitStat
|
||||
---@field timeFrac number -- Fraction of when in the chart the note was hit, `0.0` to `1.0`
|
||||
---@field lane integer -- `0` = A, `1` = B, `2` = C, `3` = D, `4` = L, `5` = R, `6` = Left Laser, `7` = Right Laser
|
||||
---@field time integer -- When in the chart the note was hit, in milliseconds
|
||||
---@field delta integer -- Delta value of the hit from 0
|
||||
---@field rating integer -- `0 = Miss`, `1 = Near`, `2 = Crit`
|
||||
|
||||
---@class ChallengeHitWindow
|
||||
---@field good integer # Near window, default `92`
|
||||
---@field hold integer -- Hold window, default `138`
|
||||
---@field miss integer -- Miss window, default `250`
|
||||
---@field perfect integer -- Critical window, default `46`
|
||||
---@field slam integer -- Slam window, default `84`
|
||||
---@field type integer -- `1 = Normal` default, `2 = Hard` default values halved
|
||||
|
||||
---@class ChartResultScore
|
||||
---@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 # Best combo reached
|
||||
---@field earlies integer # Total 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 hitWindow ChallengeHitWindow # Hit windows of the score
|
||||
---@field lates integer # Total late hits
|
||||
---@field mirror integer # Mirror mode flag
|
||||
---@field misses integer # Total errors
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field random integer # Random mode flag
|
||||
---@field score integer # Result score
|
||||
---@field timestamp integer # Unix timestamp of the score
|
||||
|
||||
---@class ChartResult
|
||||
---@field artist string # Chart artist
|
||||
---@field autoplay boolean # Autoplay bool, always false
|
||||
---@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 bpm number # Chart BPM
|
||||
---@field difficulty integer # Difficulty index
|
||||
---@field duration integer # Chart duration, in milliseconds
|
||||
---@field earlies integer # Total early hits
|
||||
---@field effector string # Chart effector
|
||||
---@field failReason string # Reason for failing the chart
|
||||
---@field gauge number # Ending gauge percentage, `0.0` to `1.0`
|
||||
---@field gaugeSamples number[] # Gauge values sampled (256 total) throughout the play
|
||||
---@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 grade string # Result grade
|
||||
---@field highScores ChartResultScore[] # All scores
|
||||
---@field hitWindow ChallengeHitWindow # Result hit windows
|
||||
---@field illustrator string # Chart jacket illustrator
|
||||
---@field isSelf boolean # Always true
|
||||
---@field jacketPath string # Full filepath to the jacket image on the disk
|
||||
---@field lates integer # Total late hits
|
||||
---@field level integer # Chart level
|
||||
---@field maxCombo integer # Result max chain
|
||||
---@field meanHitDelta number # Mean hit delta
|
||||
---@field meanHitDeltaAbs number # Absolute value of mean hit delta
|
||||
---@field medianHitDelta integer # Median hit delta
|
||||
---@field medianHitDeltaAbs integer # Absolute value of median hit delta
|
||||
---@field mirror boolean # Mirror mode bool
|
||||
---@field misses integer # Total errors
|
||||
---@field mission string # Always empty string
|
||||
---@field noteHitStats ChallengeHitStat[] # Hit stats for every chip hit
|
||||
---@field passed boolean # Chart passed
|
||||
---@field percent integer # Chart challenge percent
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field playbackSpeed number # Always 1.0
|
||||
---@field random boolean # Random mode bool,
|
||||
---@field realTitle string # Chart title, always without player name
|
||||
---@field retryCount integer # Always 0
|
||||
---@field score integer # Result score
|
||||
---@field speedModType integer # `0` = XMOD, `1` = MMOD, `2` = CMOD
|
||||
---@field speedModValue number # `HiSpeed` for `XMOD`, `ModSpeed` otherwise
|
||||
---@field title string # Chart title
|
||||
|
||||
---@class ChallengeResult
|
||||
---@field avgCrits integer # Average number of critical hits across the charts
|
||||
---@field avgErrors integer # Average number of error hits across the charts
|
||||
---@field avgGauge number # Average gauge percentage across the charts
|
||||
---@field avgNears integer # Average number of near hits of the charts
|
||||
---@field avgPercentage integer # Average completion percentage across the charts
|
||||
---@field avgScore integer # Average score across the charts
|
||||
---@field badge integer # `0` = Manual Exit, `1` = Played, `2` = Cleared, `3` = Hard Cleared, `4` = Full Chain, `5` = Perfect Chain
|
||||
---@field charts ChartResult[] # array of result information for all played charts (note: might not be all charts in course)
|
||||
---@field failReason string # Reason for failing the challenge
|
||||
---@field grade string # Result grade
|
||||
---@field isSelf boolean # Always true
|
||||
---@field level integer # Chart or challenge level
|
||||
---@field overallCrits integer # Total number of critical hits across the charts
|
||||
---@field overallErrors integer # Total number of error hits across the charts
|
||||
---@field overallNears integer # Total number of near hits across the charts
|
||||
---@field passed boolean # Whether or not the challenge was passed
|
||||
---@field requirement_text string # The challenge requirements separated by newline character `"\n"`
|
||||
---@field title string # Challenge title
|
||||
|
||||
---Render, called every frame
|
||||
---@param deltaTime number # time in seconds between frames
|
||||
---@param showStats boolean # true when left FX is pressed
|
||||
render = function (deltaTime, showStats) end
|
||||
|
||||
---This is called right after result is set, either for initial display or when the player whose score is being displayed is changed.
|
||||
result_set = function () end
|
||||
|
||||
---The region of the screen to be saved in score screenshots.
|
||||
---@return number x # top left X coordinate
|
||||
---@return number y # top left Y coordinate
|
||||
---@return number w # width
|
||||
---@return number h # height
|
||||
get_capture_rect = function () end
|
||||
|
||||
---Called when a screenshot has been captured successfully.
|
||||
---@param path string # path to the saved screenshot
|
||||
screenshot_captured = function (path) end
|
|
@ -267,7 +267,7 @@ LoadSharedSkinTexture = function(name, path) end
|
|||
|
||||
-- Loads a font fromt the specified filename
|
||||
-- Sets it as the current font if it is already loaded
|
||||
---@param name? string
|
||||
---@param name string
|
||||
---@param filename string
|
||||
LoadFont = function(name, filename) end
|
||||
|
||||
|
@ -280,11 +280,10 @@ LoadFont = function(name, filename) end
|
|||
---@return any # returns `placeholder` until the image is loaded
|
||||
LoadImageJob = function(filepath, placeholder, w, h) end
|
||||
|
||||
-- Loads a font from `skins/<skin>/textures/<path>`
|
||||
-- Loads a font from `skins/<skin>/fonts/<name>`
|
||||
-- Sets it as the current font if it is already loaded
|
||||
---@param name? string
|
||||
---@param filename string
|
||||
LoadSkinFont = function(name, filename) end
|
||||
---@param name string
|
||||
LoadSkinFont = function(name) end
|
||||
|
||||
-- Loads an image outside of the main thread to prevent rendering lock-up
|
||||
-- Image will be loaded at original size unless `w` and `h` are provided
|
||||
|
@ -472,7 +471,7 @@ UpdateImagePattern = function(pattern, sx, sy, ix, iy, angle, alpha) end
|
|||
---@param size? integer
|
||||
UpdateLabel = function(label, text, size) end
|
||||
|
||||
---@type table
|
||||
---@class gfx
|
||||
gfx = {
|
||||
BLEND_ZERO = 1,
|
||||
BLEND_ONE = 2,
|
||||
|
@ -595,4 +594,4 @@ gfx = {
|
|||
Translate = Translate,
|
||||
UpdateImagePattern = UpdateImagePattern,
|
||||
UpdateLabel = UpdateLabel,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
-- result and challengeresult `result` table
|
||||
-- result `result` table
|
||||
|
||||
---@diagnostic disable:lowercase-global
|
||||
---@diagnostic disable:missing-return
|
||||
|
||||
---@class HitStat
|
||||
---@field timeFrac number -- Fraction of when in the chart the note was hit, `0.0` to `1.0`
|
||||
|
@ -7,7 +10,6 @@
|
|||
---@field delta integer -- Delta value of the hit from 0
|
||||
---@field hold integer -- `0` for chip/laser, otherwise `# Ticks` of hold
|
||||
---@field rating integer -- `0 = Miss`, `1 = Near`, `2 = Crit`
|
||||
HitStat = {};
|
||||
|
||||
---@class HitWindow
|
||||
---@field good integer # Near window, default `92`
|
||||
|
@ -16,7 +18,6 @@ HitStat = {};
|
|||
---@field perfect integer -- Critical window, default `46`
|
||||
---@field slam integer -- Slam window, default `84`
|
||||
---@field type integer -- `1 = Normal` default, `2 = Hard` default values halved
|
||||
HitWindow = {};
|
||||
|
||||
---@class Score
|
||||
---@field auto_flags integer # Autoplay flag
|
||||
|
@ -25,21 +26,25 @@ HitWindow = {};
|
|||
---@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 hitWindow HitWindow|nil # Hit windows of the score, only for singleplayer results screen
|
||||
---@field hitWindow HitWindow # Hit windows of the score
|
||||
---@field mirror integer # Mirror mode flag
|
||||
---@field misses integer # Total errors
|
||||
---@field name nil|string # Only for multiplayer results, name of the player
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field random integer # 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 = {};
|
||||
|
||||
---@class ChartResult : result
|
||||
---@field passed boolean # Whether or not challenge requirements were met for this chart
|
||||
---@field failReason string # Fail reason if a challenge requirement was not met
|
||||
ChartResult = {};
|
||||
---@class MultiplayerScore
|
||||
---@field badge integer # `0` = Manual Exit, `1` = Played, `2` = Cleared, `3` = Hard Cleared, `4` = Full Chain, `5` = Perfect Chain
|
||||
---@field flags integer # Autoplay flag
|
||||
---@field gauge number # Ending gauge percentage, `0.0` to `1.0`
|
||||
---@field goods integer # Total near hits
|
||||
---@field misses integer # Total errors
|
||||
---@field name string # Name of the player
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field score integer # Result score
|
||||
---@field timestamp integer # Unix timestamp of the score
|
||||
---@field uid string # UID of the player
|
||||
|
||||
---@class ServerScoreOptions
|
||||
---@field gaugeType integer # An enum value representing the gauge type used. 0 = normal, 1 = hard. Further values are not currently specified.
|
||||
|
@ -47,7 +52,6 @@ ChartResult = {};
|
|||
---@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
|
||||
|
@ -63,28 +67,19 @@ ServerScoreOptions = {}
|
|||
---@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
|
||||
---@class Result
|
||||
---@field artist string # Chart artist
|
||||
---@field auto_flags integer # Autoplay flag
|
||||
---@field autoplay boolean # Autoplay bool
|
||||
---@field avgCrits integer # Only for challenge results, average number of critical hits across the charts
|
||||
---@field avgErrors integer # Only for challenge results, average number of error hits across the charts
|
||||
---@field avgGauge number # Only for challenge results, average gauge percentage across the charts
|
||||
---@field avgNears integer # Only for challenge results, average number of near hits of the charts
|
||||
---@field avgPercentage integer # Only for challenge results, average completion percentage across the charts
|
||||
---@field avgScore integer # Only for challenge results, average score across the charts
|
||||
---@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 charts ChartResult[] # Only for challenge results, array of chart results
|
||||
---@field chartHash string # Chart hash
|
||||
---@field difficulty integer # Difficulty index
|
||||
---@field displayIndex nil|integer # Only for multiplayer results, the index of the score being viewed
|
||||
---@field duration integer # Chart duration, in milliseconds
|
||||
---@field earlies integer # Total early hits
|
||||
---@field effector string # Chart effector
|
||||
---@field failReason string # Reason for failing the challenge
|
||||
---@field flags integer # Gameplay option flags e.g. gauge type, mirror/random mode
|
||||
---@field gauge number # Ending gauge percentage, `0.0` to `1.0`
|
||||
---@field gauge_option integer # Gauge option e.g. ARS
|
||||
|
@ -92,7 +87,7 @@ 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 highScores (Score|MultiplayerScore)[] # 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 illustrator string # Chart jacket illustrator
|
||||
|
@ -103,7 +98,7 @@ ServerScore = {}
|
|||
---@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 lates integer # Total late hits
|
||||
---@field level integer # Chart or challenge level
|
||||
---@field level integer # Chart level
|
||||
---@field maxCombo integer # Result max chain
|
||||
---@field meanHitDelta number # Mean hit delta
|
||||
---@field meanHitDeltaAbs number # Absolute value of mean hit delta
|
||||
|
@ -113,20 +108,34 @@ ServerScore = {}
|
|||
---@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 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
|
||||
---@field passed boolean # Only for challenge results, whether or not the challenge was passed
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field playbackSpeed number # Only for practice mode, percentage from 0.25 to 1.0
|
||||
---@field playerName nil|string # Only for multiplayer
|
||||
---@field random boolean # Random mode bool,
|
||||
---@field realTitle string # Chart title, always without player name
|
||||
---@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 title string # Chart (with player name in multiplayer) or challenge title
|
||||
---@field uid nil|string # Only for multiplayer, UID of the player
|
||||
result = {};
|
||||
---@field title string # Chart (with player name in multiplayer)
|
||||
---@field uid nil|string # Only for multiplayer, UID of the __viewer__
|
||||
result = {}
|
||||
|
||||
---Render, called every frame
|
||||
---@param deltaTime number # time in seconds between frames
|
||||
---@param showStats boolean # true when left FX is pressed
|
||||
render = function (deltaTime, showStats) end
|
||||
|
||||
---This is called right after result is set, either for initial display or when the player whose score is being displayed is changed.
|
||||
result_set = function () end
|
||||
|
||||
---The region of the screen to be saved in score screenshots.
|
||||
---@return number x # top left X coordinate
|
||||
---@return number y # top left Y coordinate
|
||||
---@return number w # width
|
||||
---@return number h # height
|
||||
get_capture_rect = function () end
|
||||
|
||||
---Called when a screenshot has been captured successfully.
|
||||
---@param path string # path to the saved screenshot
|
||||
screenshot_captured = function (path) end
|
||||
|
|
|
@ -1,117 +1,4 @@
|
|||
-- Adds a texture that was loaded with `gfx.LoadSharedTexture` to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param textureName string
|
||||
AddSharedTexture = function(uniformName, textureName) end
|
||||
|
||||
-- Adds a texture to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param path string # prepended with `skins/<skin>/textures/`
|
||||
AddSkinTexture = function(uniformName, path) end
|
||||
|
||||
-- Adds a texture to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param path string
|
||||
AddTexture = function(uniformName, path) end
|
||||
|
||||
-- Gets the translation of the mesh
|
||||
---@return number x, number y, number z
|
||||
GetPosition = function() end
|
||||
|
||||
-- Gets the rotation (in degrees) of the mesh
|
||||
---@return number roll, number yaw, number pitch
|
||||
GetRotation = function() end
|
||||
|
||||
-- Gets the scale of the mesh
|
||||
---@return number x, number y, number z
|
||||
GetScale = function() end
|
||||
|
||||
-- Sets the blending mode
|
||||
---@param mode integer # options also available as fields of the object prefixed with `BLEND`
|
||||
-- `Normal` = 0 (default)
|
||||
-- `Additive` = 1
|
||||
-- `Multiply` = 2
|
||||
SetBlendMode = function(mode) end
|
||||
|
||||
-- Sets the geometry data
|
||||
---@param data table # array of vertices in clockwise order starting from the top left e.g.
|
||||
-- ```
|
||||
-- {
|
||||
-- { { 0, 0 }, { 0, 0 } },
|
||||
-- { { 50, 0 }, { 1, 0 } },
|
||||
-- { { 50, 50 }, { 1, 1 } },
|
||||
-- { { 0, 50 }, { 0, 1 } },
|
||||
-- }
|
||||
-- ```
|
||||
SetData = function(data) end
|
||||
|
||||
-- Sets the material is opaque or non-opaque (default)
|
||||
---@param opaque boolean
|
||||
SetOpaque = function(opaque) end
|
||||
|
||||
-- Sets the value of the specified uniform
|
||||
---@param uniformName string
|
||||
---@param value number # `float`
|
||||
SetParam = function(uniformName, value) end
|
||||
|
||||
-- Sets the value of the specified 2d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
SetParamVec2 = function(uniformName, x, y) end
|
||||
|
||||
-- Sets the value of the specified 3d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
---@param z number # `float`
|
||||
SetParamVec3 = function(uniformName, x, y, z) end
|
||||
|
||||
-- Sets the value of the specified 4d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
---@param z number # `float`
|
||||
---@param w number # `float`
|
||||
SetParamVec4 = function(uniformName, x, y, z, w) end
|
||||
|
||||
-- Sets the translation for the mesh
|
||||
-- Relative to the screen for `ShadedMesh`
|
||||
-- Relative to the center of the crit line for `ShadedMeshOnTrack`
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z? number # Default `0`
|
||||
SetPosition = function(x, y, z) end
|
||||
|
||||
-- Sets the format for geometry data provided by `SetData`
|
||||
---@param type integer # options also available as fields of the object prefixed with `PRIM`
|
||||
-- `TriangleList` = 0 (default)
|
||||
-- `TriangleStrip` = 1
|
||||
-- `TriangleFan` = 2
|
||||
-- `LineList` = 3
|
||||
-- `LineStrip` = 4
|
||||
-- `PointList` = 5
|
||||
SetPrimitiveType = function(type) end
|
||||
|
||||
-- Sets the rotation (in degrees) of the mesh
|
||||
-- **WARNING:** For `ShadedMesh`, pitch and yaw may clip, rendering portions or the entire mesh invisible
|
||||
---@param roll number
|
||||
---@param yaw? number # Default `0`
|
||||
---@param pitch? number # Default `0`
|
||||
SetRotation = function(roll, yaw, pitch) end
|
||||
|
||||
-- Sets the scale of the mesh
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z? number # Default `0`
|
||||
SetScale = function(x, y, z) end
|
||||
|
||||
-- Sets the wireframe mode of the object (does not render texture)
|
||||
-- Useful for debugging models or geometry shaders
|
||||
---@param useWireframe boolean
|
||||
SetWireframe = function(useWireframe) end
|
||||
|
||||
-- Renders the `ShadedMesh` object
|
||||
Draw = function() end
|
||||
---@diagnostic disable:missing-return
|
||||
|
||||
---@class ShadedMesh
|
||||
ShadedMesh = {
|
||||
|
@ -125,57 +12,148 @@ ShadedMesh = {
|
|||
PRIM_LINELIST = 3,
|
||||
PRIM_LINESTRIP = 4,
|
||||
PRIM_POINTLIST = 5,
|
||||
|
||||
AddSharedTexture = AddSharedTexture,
|
||||
AddSkinTexture = AddSkinTexture,
|
||||
AddTexture = AddTexture,
|
||||
Draw = Draw,
|
||||
GetPosition = GetPosition,
|
||||
GetRotation = GetRotation,
|
||||
GetScale = GetScale,
|
||||
SetBlendMode = SetBlendMode,
|
||||
SetData = SetData,
|
||||
SetOpaque = SetOpaque,
|
||||
SetParam = SetParam,
|
||||
SetParamVec2 = SetParamVec2,
|
||||
SetParamVec3 = SetParamVec3,
|
||||
SetParamVec4 = SetParamVec4,
|
||||
SetPosition = SetPosition,
|
||||
SetPrimitiveType = SetPrimitiveType,
|
||||
SetRotation = SetRotation,
|
||||
SetScale = SetScale,
|
||||
SetWireframe = SetWireframe,
|
||||
};
|
||||
|
||||
-- Gets the length of the mesh
|
||||
---@return number length
|
||||
GetLength = function() end
|
||||
-- Adds a texture that was loaded with `gfx.LoadSharedTexture` to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param textureName string
|
||||
function ShadedMesh:AddSharedTexture(uniformName, textureName) end
|
||||
|
||||
-- Sets the y-scale of the mesh based on its length
|
||||
-- Useful for creating fake buttons which may have variable length based on duration
|
||||
---@param length number
|
||||
ScaleToLength = function(length) end
|
||||
-- Adds a texture to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param path string # prepended with `skins/<skin>/textures/`
|
||||
function ShadedMesh:AddSkinTexture(uniformName, path) end
|
||||
|
||||
-- Stops meshes beyond the track from being rendered if `doClip`
|
||||
---@param doClip boolean
|
||||
SetClipWithTrack = function(doClip) end
|
||||
-- Adds a texture to the material that can be used in the shader code
|
||||
---@param uniformName string
|
||||
---@param path string
|
||||
function ShadedMesh:AddTexture(uniformName, path) end
|
||||
|
||||
-- Sets the length (in the y-direction relative to the track) of the mesh
|
||||
---@param length number # Optional constants: `BUTTON_TEXTURE_LENGTH`, `FXBUTTON_TEXTURE_LENGTH`, and `TRACK_LENGTH`
|
||||
SetLength = function(length) end
|
||||
-- Gets the translation of the mesh
|
||||
---@return number x, number y, number z
|
||||
function ShadedMesh:GetPosition() end
|
||||
|
||||
-- Gets the rotation (in degrees) of the mesh
|
||||
---@return number roll, number yaw, number pitch
|
||||
function ShadedMesh:GetRotation() end
|
||||
|
||||
-- Gets the scale of the mesh
|
||||
---@return number x, number y, number z
|
||||
function ShadedMesh:GetScale() end
|
||||
|
||||
-- Sets the blending mode
|
||||
---@param mode integer # options also available as fields of the object prefixed with `BLEND`
|
||||
-- `Normal` = 0 (default)
|
||||
-- `Additive` = 1
|
||||
-- `Multiply` = 2
|
||||
function ShadedMesh:SetBlendMode(mode) end
|
||||
|
||||
-- Sets the geometry data
|
||||
---@param data table # array of vertices in clockwise order starting from the top left e.g.
|
||||
-- ```
|
||||
-- {
|
||||
-- { { 0, 0 }, { 0, 0 } },
|
||||
-- { { 50, 0 }, { 1, 0 } },
|
||||
-- { { 50, 50 }, { 1, 1 } },
|
||||
-- { { 0, 50 }, { 0, 1 } },
|
||||
-- }
|
||||
-- ```
|
||||
function ShadedMesh:SetData(data) end
|
||||
|
||||
-- Sets the material is opaque or non-opaque (default)
|
||||
---@param opaque boolean
|
||||
function ShadedMesh:SetOpaque(opaque) end
|
||||
|
||||
-- Sets the value of the specified uniform
|
||||
---@param uniformName string
|
||||
---@param value number # `float`
|
||||
function ShadedMesh:SetParam(uniformName, value) end
|
||||
|
||||
-- Sets the value of the specified 2d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
function ShadedMesh:SetParamVec2(uniformName, x, y) end
|
||||
|
||||
-- Sets the value of the specified 3d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
---@param z number # `float`
|
||||
function ShadedMesh:SetParamVec3(uniformName, x, y, z) end
|
||||
|
||||
-- Sets the value of the specified 4d vector uniform
|
||||
---@param uniformName string
|
||||
---@param x number # `float`
|
||||
---@param y number # `float`
|
||||
---@param z number # `float`
|
||||
---@param w number # `float`
|
||||
function ShadedMesh:SetParamVec4(uniformName, x, y, z, w) end
|
||||
|
||||
-- Sets the translation for the mesh
|
||||
-- Relative to the screen for `ShadedMesh`
|
||||
-- Relative to the center of the crit line for `ShadedMeshOnTrack`
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z? number # Default `0`
|
||||
function ShadedMesh:SetPosition(x, y, z) end
|
||||
|
||||
-- Sets the format for geometry data provided by `SetData`
|
||||
---@param type integer # options also available as fields of the object prefixed with `PRIM`
|
||||
-- `TriangleList` = 0 (default)
|
||||
-- `TriangleStrip` = 1
|
||||
-- `TriangleFan` = 2
|
||||
-- `LineList` = 3
|
||||
-- `LineStrip` = 4
|
||||
-- `PointList` = 5
|
||||
function ShadedMesh:SetPrimitiveType(type) end
|
||||
|
||||
-- Sets the rotation (in degrees) of the mesh
|
||||
-- **WARNING:** For `ShadedMesh`, pitch and yaw may clip, rendering portions or the entire mesh invisible
|
||||
---@param roll number
|
||||
---@param yaw? number # Default `0`
|
||||
---@param pitch? number # Default `0`
|
||||
function ShadedMesh:SetRotation(roll, yaw, pitch) end
|
||||
|
||||
-- Sets the scale of the mesh
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z? number # Default `0`
|
||||
function ShadedMesh:SetScale(x, y, z) end
|
||||
|
||||
-- Sets the wireframe mode of the object (does not render texture)
|
||||
-- Useful for debugging models or geometry shaders
|
||||
---@param useWireframe boolean
|
||||
function ShadedMesh:SetWireframe(useWireframe) end
|
||||
|
||||
-- Renders the `ShadedMesh` object
|
||||
function ShadedMesh:Draw() end
|
||||
|
||||
-- Uses an existing game mesh
|
||||
---@param meshName string # Options: `'button'`, `'fxbutton'`, and `'track'`
|
||||
UseGameMesh = function(meshName) end
|
||||
|
||||
---@class ShadedMeshOnTrack : ShadedMesh
|
||||
---@field BUTTON_TEXTURE_LENGTH number
|
||||
---@field FXBUTTON_TEXTURE_LENGTH number
|
||||
---@field TRACK_LENGTH number
|
||||
ShadedMeshOnTrack = {
|
||||
GetLength = GetLength,
|
||||
UseGameMesh = UseGameMesh,
|
||||
ScaleToLength = ScaleToLength,
|
||||
SetClipWithTrack = SetClipWithTrack,
|
||||
SetLength = SetLength,
|
||||
};
|
||||
};
|
||||
|
||||
-- Gets the length of the mesh
|
||||
---@return number length
|
||||
function ShadedMeshOnTrack:GetLength() end
|
||||
|
||||
-- Sets the y-scale of the mesh based on its length
|
||||
-- Useful for creating fake buttons which may have variable length based on duration
|
||||
---@param length number
|
||||
function ShadedMeshOnTrack:ScaleToLength(length) end
|
||||
|
||||
-- Stops meshes beyond the track from being rendered if `doClip`
|
||||
---@param doClip boolean
|
||||
function ShadedMeshOnTrack:SetClipWithTrack(doClip) end
|
||||
|
||||
-- Sets the length (in the y-direction relative to the track) of the mesh
|
||||
---@param length number # Optional constants: `BUTTON_TEXTURE_LENGTH`, `FXBUTTON_TEXTURE_LENGTH`, and `TRACK_LENGTH`
|
||||
function ShadedMeshOnTrack:SetLength(length) end
|
||||
|
||||
-- Uses an existing game mesh
|
||||
---@param meshName string # Options: `'button'`, `'fxbutton'`, and `'track'`
|
||||
function ShadedMeshOnTrack:UseGameMesh(meshName) end
|
||||
|
|
|
@ -1,29 +1,63 @@
|
|||
---@diagnostic disable: lowercase-global
|
||||
-- songwheel `songwheel` table
|
||||
|
||||
---@class Difficulty
|
||||
---@class SongWheelScore
|
||||
---@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 # Max combo
|
||||
---@field earlies integer # Total 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 integer # `0` = false, `1` = true
|
||||
---@field lates integer # Total late hits
|
||||
---@field mirror integer # Mirror mode flag
|
||||
---@field misses integer # Total errors
|
||||
---@field playerName string # Name of the player
|
||||
---@field perfects integer # Total critical hits
|
||||
---@field random integer # Random mode flag
|
||||
---@field score integer # Result score
|
||||
---@field timestamp integer # Unix timestamp of the score
|
||||
SongWheelScore = {}
|
||||
|
||||
---@class SongWheelDifficulty
|
||||
---@field difficulty integer # Difficulty index
|
||||
---@field effector string # Name of charter
|
||||
---@field hash string # Difficulty hash
|
||||
---@field id integer # Difficulty id, unique static identifier
|
||||
---@field illustrator string # Difficulty jacket illustrator
|
||||
---@field jacketPath string # Full filepath to the jacket image on the disk
|
||||
---@field level integer # Difficulty level
|
||||
---@field scores Score[] # Scores for the current difficulty
|
||||
---@field scores SongWheelScore[] # Scores for the current difficulty
|
||||
---@field topBadge integer # `0 = Never Played`, `1 = Played`, `2 = Cleared`, `3 = Hard Cleared`, `4 = Full Chain`, `5 = Perfect Chain`
|
||||
Difficulty = {};
|
||||
SongWheelDifficulty = {}
|
||||
|
||||
---@class Song
|
||||
---@class SongWheelSong
|
||||
---@field artist string # Chart artist
|
||||
---@field difficulties Difficulty[] # Array of difficulties for the current song
|
||||
---@field bpm number # Chart BPM
|
||||
---@field difficulties SongWheelDifficulty[] # 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 = {};
|
||||
SongWheelSong = {}
|
||||
|
||||
---@class songwheel
|
||||
---@field allSongs Song[] # Array of all available songs
|
||||
---@field allSongs SongWheelSong[] # Array of all available songs
|
||||
---@field searchInputActive boolean # Search status
|
||||
---@field searchStatus string # Current song database status
|
||||
---@field searchText string # Search input text
|
||||
---@field songs Song[] # Array of songs with the current filters/sorting applied
|
||||
songwheel = {};
|
||||
---@field songs SongWheelSong[] # Array of songs with the current filters/sorting applied
|
||||
songwheel = {}
|
||||
|
||||
---Render, called every frame
|
||||
---@param deltaTime number # time in seconds between frames
|
||||
render = function (deltaTime) end
|
||||
|
||||
---Called when selected difficulty changes
|
||||
---@param diff integer # Difficulty level
|
||||
set_diff = function (diff) end
|
||||
|
||||
---Called when song database changes
|
||||
---@param withAll boolean # Reload all songs
|
||||
songs_changed = function (withAll) end
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"realTitle": "Brain Power",
|
||||
"duration": 106678,
|
||||
"medianHitDelta": 0,
|
||||
"goods": 0,
|
||||
"medianHitDeltaAbs": 0,
|
||||
"bpm": "170-173",
|
||||
"autoplay": false,
|
||||
"misses": 1538,
|
||||
"earlies": 0,
|
||||
"score": 405489,
|
||||
"artist": "ノマ",
|
||||
"jacketPath": "<hidden>\\USC\\songs\\SDVX II Infinite Infection\\brain_power_noma\\nov_jacket.png",
|
||||
"irScores": [
|
||||
{
|
||||
"lamp": 3,
|
||||
"near": 30,
|
||||
"crit": 1563,
|
||||
"ranking": 1,
|
||||
"timestamp": 1639691098,
|
||||
"username": "Hersi",
|
||||
"justSet": true,
|
||||
"yours": true,
|
||||
"error": 10,
|
||||
"score": 9844042
|
||||
},
|
||||
{
|
||||
"error": 16,
|
||||
"near": 32,
|
||||
"crit": 1555,
|
||||
"ranking": 2,
|
||||
"timestamp": 1590326862,
|
||||
"score": 9800374,
|
||||
"username": "joksulainen",
|
||||
"lamp": 2
|
||||
},
|
||||
{
|
||||
"error": 33,
|
||||
"near": 10,
|
||||
"crit": 1560,
|
||||
"ranking": 3,
|
||||
"timestamp": 1644462117,
|
||||
"score": 9762944,
|
||||
"username": "Aidrestan",
|
||||
"lamp": 3
|
||||
},
|
||||
{
|
||||
"error": 19,
|
||||
"near": 48,
|
||||
"crit": 1536,
|
||||
"ranking": 4,
|
||||
"timestamp": 1637049756,
|
||||
"score": 9731752,
|
||||
"username": "Kag",
|
||||
"lamp": 2
|
||||
}
|
||||
],
|
||||
"hitWindow": {
|
||||
"good": 150,
|
||||
"type": 1,
|
||||
"perfect": 46,
|
||||
"hold": 150,
|
||||
"slam": 84,
|
||||
"miss": 300
|
||||
},
|
||||
"random": false,
|
||||
"auto_flags": 0,
|
||||
"playerName": "test",
|
||||
"chartHash": "0d33f1f26df67cac253a5a44bc018e5eff27af0c",
|
||||
"displayIndex": 1,
|
||||
"lates": 0,
|
||||
"highScores": [
|
||||
{
|
||||
"misses": 1414,
|
||||
"timestamp": 0,
|
||||
"score": 1179039,
|
||||
"perfects": 189,
|
||||
"uid": "<hidden>",
|
||||
"gauge": 0,
|
||||
"name": "Hersi",
|
||||
"badge": 1,
|
||||
"flags": 0,
|
||||
"goods": 0
|
||||
},
|
||||
{
|
||||
"misses": 1538,
|
||||
"timestamp": 0,
|
||||
"score": 405489,
|
||||
"perfects": 65,
|
||||
"uid": "80084945-3570-49cd-b2b1-dc23e96fcaf4",
|
||||
"gauge": 0,
|
||||
"name": "test",
|
||||
"badge": 1,
|
||||
"flags": 0,
|
||||
"goods": 0
|
||||
}
|
||||
],
|
||||
"mission": "",
|
||||
"gaugeSamples": [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0.00068503420334309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0.00068503420334309, 0, 0, 0, 0, 0, 0,
|
||||
0.00068503420334309, 0, 0.0013700684066862, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00068503420334309, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0.00068503420334309, 0.00068503420334309, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00068503420334309, 0, 0, 0, 0,
|
||||
0.00068503420334309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0.0013700684066862, 0, 0, 0.00068503420334309, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0.00068503420334309, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
],
|
||||
"difficulty": 2,
|
||||
"gauge_option": 0,
|
||||
"grade": "D",
|
||||
"irDescription": "Successfully imported score.",
|
||||
"retryCount": 0,
|
||||
"playbackSpeed": 1,
|
||||
"irState": 20,
|
||||
"illustrator": "square_head",
|
||||
"meanHitDeltaAbs": 0,
|
||||
"isSelf": false,
|
||||
"badge": 1,
|
||||
"effector": "Megacycle",
|
||||
"level": 15,
|
||||
"uid": "<hidden>",
|
||||
"maxCombo": 4,
|
||||
"mirror": false,
|
||||
"perfects": 65,
|
||||
"gauge_type": 0,
|
||||
"meanHitDelta": 0,
|
||||
"gauge": 0,
|
||||
"title": "<test> Brain Power"
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
local util = require("common.util")
|
||||
|
||||
---@class CColorRGBA
|
||||
ColorRGBA = {
|
||||
---Create a new Color instance
|
||||
---@param r integer # red or monochrome value
|
||||
---@param g? integer # green value
|
||||
---@param b? integer # blue value
|
||||
---@param a? integer # alpha value, default 255
|
||||
---@return ColorRGBA
|
||||
new = function (r, g , b, a)
|
||||
---@class ColorRGBA : CColorRGBA
|
||||
---@field r integer
|
||||
---@field g integer
|
||||
---@field b integer
|
||||
---@field a integer
|
||||
local o = {
|
||||
r = r or 0,
|
||||
g = g or r,
|
||||
b = b or r,
|
||||
a = a or 255,
|
||||
}
|
||||
|
||||
setmetatable(o, ColorRGBA)
|
||||
return o
|
||||
end,
|
||||
|
||||
---Mix two colors
|
||||
---@param color1 ColorRGBA
|
||||
---@param color2 ColorRGBA
|
||||
---@param factor number
|
||||
---@return ColorRGBA
|
||||
mix = function (color1, color2, factor)
|
||||
local r = math.floor(util.mix(color1.r, color2.r, factor))
|
||||
local g = math.floor(util.mix(color1.g, color2.g, factor))
|
||||
local b = math.floor(util.mix(color1.b, color2.b, factor))
|
||||
local a = math.floor(util.mix(color1.a, color2.a, factor))
|
||||
return ColorRGBA.new(r, g, b, a)
|
||||
end
|
||||
}
|
||||
|
||||
ColorRGBA.__index = ColorRGBA
|
||||
ColorRGBA.BLACK = ColorRGBA.new(0)
|
||||
ColorRGBA.GREY = ColorRGBA.new(128)
|
||||
ColorRGBA.WHITE = ColorRGBA.new(255)
|
||||
ColorRGBA.RED = ColorRGBA.new(255, 0, 0)
|
||||
ColorRGBA.GREEN = ColorRGBA.new(0, 255, 0)
|
||||
ColorRGBA.BLUE = ColorRGBA.new(0, 0, 255)
|
||||
ColorRGBA.YELLOW = ColorRGBA.new(255, 255, 0)
|
||||
ColorRGBA.CYAN = ColorRGBA.new(0, 255, 255)
|
||||
ColorRGBA.MAGENTA = ColorRGBA.new(255, 0, 255)
|
||||
|
||||
---Split to components
|
||||
---@return integer # red
|
||||
---@return integer # green
|
||||
---@return integer # blue
|
||||
---@return integer # alpha
|
||||
function ColorRGBA:components()
|
||||
---@cast self ColorRGBA
|
||||
|
||||
return self.r, self.g, self.b, self.a
|
||||
end
|
||||
|
||||
---Split to components scaled to [0.0, 1.0]
|
||||
---@return number # red
|
||||
---@return number # green
|
||||
---@return number # blue
|
||||
---@return number # alpha
|
||||
function ColorRGBA:componentsFloat()
|
||||
---@cast self ColorRGBA
|
||||
local scale = 255
|
||||
|
||||
return self.r / scale, self.g / scale, self.b / scale, self.a / scale
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
---@class CPoint2D
|
||||
Point2D = {
|
||||
---Create a Point2D instance
|
||||
---@param x? number # default 0.0
|
||||
---@param y? number # default 0.0
|
||||
---@return Point2D
|
||||
new = function(x, y)
|
||||
---@class Point2D : CPoint2D
|
||||
---@field x number
|
||||
---@field y number
|
||||
local o = {
|
||||
x = x + .0 or .0,
|
||||
y = y + .0 or .0,
|
||||
}
|
||||
|
||||
setmetatable(o, Point2D)
|
||||
return o
|
||||
end
|
||||
}
|
||||
|
||||
Point2D.__index = Point2D
|
||||
Point2D.ZERO = Point2D.new(0, 0)
|
||||
|
||||
function Point2D:coords()
|
||||
---@cast self Point2D
|
||||
|
||||
return self.x, self.y
|
||||
end
|
|
@ -7,6 +7,13 @@ local Sound = require("common.sound")
|
|||
local Numbers = require('components.numbers')
|
||||
local VolforceWindow = require("components.volforceWindow")
|
||||
|
||||
require("common.gameconfig")
|
||||
|
||||
-- Lua code completion annotation
|
||||
|
||||
---@diagnostic disable-next-line
|
||||
---@cast result ChallengeResult
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
|
@ -38,7 +45,9 @@ local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_in
|
|||
|
||||
local headerTitleImage = gfx.CreateSkinImage("challenge_result/header/title.png", 0);
|
||||
|
||||
local username = game.GetSkinSetting("username");
|
||||
-- gameplay table does not have a current username field, because why would it lmao
|
||||
-- workaround: retrieve it directly from Main.cfg file
|
||||
local username = GameConfig["MultiplayerUsername"] or game.GetSkinSetting("username") or "";
|
||||
local appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0);
|
||||
local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0);
|
||||
local crewImage = gfx.CreateSkinImage("crew/portrait.png", 0);
|
||||
|
@ -75,8 +84,8 @@ local gradeImages = {
|
|||
none = gfx.CreateSkinImage("common/grades/none.png", 0),
|
||||
}
|
||||
|
||||
local percRequired = 1;
|
||||
local percGet = 0;
|
||||
local percRequired = nil;
|
||||
local percGet = nil;
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample("challenge_result.wav")
|
||||
|
@ -161,7 +170,7 @@ function drawChartResult(deltaTime, x, y, chartResult)
|
|||
gfx.GlobalAlpha(1);
|
||||
gfx.Text(chartResult.title, x+160,y+32);
|
||||
|
||||
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty, chartResult.level)
|
||||
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty+1, chartResult.level)
|
||||
|
||||
local score = chartResult.score or 0;
|
||||
|
||||
|
@ -215,8 +224,11 @@ function drawCompletion()
|
|||
gfx.BeginPath()
|
||||
gfx.ImageRect(63, 1331, 766*0.85, 130*0.85, completitionImage, 1, 0)
|
||||
|
||||
Numbers.draw_number(925, 1370, 1.0, percGet, 3, scoreNumber, true, 0.3, 1.12)
|
||||
if (percRequired == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
Numbers.draw_number(925, 1370, 1.0, percGet, 3, scoreNumber, true, 0.3, 1.12)
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.Rect(741, 1402, 278*math.min(1, percGet / percRequired), 6);
|
||||
|
@ -226,7 +238,13 @@ end
|
|||
|
||||
|
||||
function result_set()
|
||||
if (result.requirement_text == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
local reqTextWords = common.split(result.requirement_text, ' ');
|
||||
|
||||
|
||||
for index, word in ipairs(reqTextWords) do
|
||||
if string.find(word, '%%') ~= nil then -- %% = %, because % is an escape char
|
||||
local percNumber = tonumber(string.gsub(word, '%%', ''), 10)
|
||||
|
@ -234,6 +252,10 @@ function result_set()
|
|||
end
|
||||
end
|
||||
|
||||
if (percRequired == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
game.Log(percRequired, game.LOGGER_ERROR);
|
||||
|
||||
local a = 0;
|
||||
|
|
|
@ -48,9 +48,11 @@ end
|
|||
---@param rotation number
|
||||
---@return number, boolean # The scale applied to the transform and the current landscape state
|
||||
function dimtable.setUpTransforms(x, y, rotation)
|
||||
local scale = dimtable.screen.width / dimtable.view.width;
|
||||
local isLandscape = dimtable.view.width > dimtable.view.height;
|
||||
|
||||
local designWidth = isLandscape and dimtable.design.height or dimtable.design.width
|
||||
local scale = dimtable.view.width / designWidth
|
||||
|
||||
gfx.ResetTransform();
|
||||
gfx.Translate(x, y);
|
||||
gfx.Rotate(rotation);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
local function split(s, delimiter)
|
||||
result = {};
|
||||
local result = {};
|
||||
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
||||
table.insert(result, match);
|
||||
end
|
||||
|
@ -59,6 +59,10 @@ local function lerp(x, x0, y0, x1, y1)
|
|||
return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
||||
end
|
||||
|
||||
local function mix(x, y, a)
|
||||
return (1 - a) * x + a * y
|
||||
end
|
||||
|
||||
--modulo operation for index value
|
||||
local function modIndex(index, mod)
|
||||
return (index - 1) % mod + 1
|
||||
|
@ -75,6 +79,41 @@ local function firstAlphaNum(s)
|
|||
return '';
|
||||
end
|
||||
|
||||
local function dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
local function all(t, predicate)
|
||||
predicate = predicate or function(e) return e end
|
||||
|
||||
for _, e in ipairs(t) do
|
||||
if not predicate(e) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function any(t, predicate)
|
||||
predicate = predicate or function(e) return e end
|
||||
|
||||
for _, e in ipairs(t) do
|
||||
if predicate(e) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
split = split,
|
||||
filter = filter,
|
||||
|
@ -84,6 +123,10 @@ return {
|
|||
roundToZero = roundToZero,
|
||||
areaOverlap = areaOverlap,
|
||||
lerp = lerp,
|
||||
mix = mix,
|
||||
modIndex = modIndex,
|
||||
firstAlphaNum = firstAlphaNum,
|
||||
dump = dump,
|
||||
all = all,
|
||||
any = any
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
local MAJOR = 0
|
||||
local MINOR = 2
|
||||
local PATCH = 2
|
||||
local MINOR = 3
|
||||
local PATCH = 0
|
||||
|
||||
local function getLongVersion()
|
||||
return "USC:E:G:S:" .. MAJOR .. MINOR .. PATCH
|
||||
|
@ -18,4 +18,4 @@ return {
|
|||
PATCH = PATCH,
|
||||
getLongVersion = getLongVersion,
|
||||
getVersion = getVersion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ local difficultyLabelImages = {
|
|||
gfx.CreateSkinImage("diff/5 infinite.png", 0),
|
||||
gfx.CreateSkinImage("diff/6 gravity.png", 0),
|
||||
gfx.CreateSkinImage("diff/7 heavenly.png", 0),
|
||||
gfx.CreateSkinImage("diff/8 vivid.png", 0)
|
||||
gfx.CreateSkinImage("diff/8 vivid.png", 0),
|
||||
gfx.CreateSkinImage("diff/9 exceed.png", 0),
|
||||
}
|
||||
|
||||
local difficultyLabelTexts = {
|
||||
|
@ -18,10 +19,17 @@ local difficultyLabelTexts = {
|
|||
"INF",
|
||||
"GRV",
|
||||
"HVN",
|
||||
"VVD"
|
||||
"VVD",
|
||||
"EXC"
|
||||
}
|
||||
|
||||
function render(deltatime, x, y, scale, diff, level)
|
||||
local difficultyLabelImage = difficultyLabelImages[diff]
|
||||
if difficultyLabelImage == nil then
|
||||
game.Log("Unknown chart difficulty index "..diff..", fallback to MXM", game.LOGGER_WARNING)
|
||||
difficultyLabelImage = difficultyLabelImages[4]
|
||||
end
|
||||
|
||||
gfx.Save()
|
||||
gfx.Translate(x,y);
|
||||
gfx.Scale(scale,scale)
|
||||
|
@ -61,4 +69,4 @@ end
|
|||
|
||||
return {
|
||||
render = render
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
require("common.globals")
|
||||
require("common.gameconfig")
|
||||
|
||||
local version = require("common.version")
|
||||
local Dim = require("common.dimensions")
|
||||
local Num = require("components.numbers")
|
||||
|
||||
local BAR_ALPHA = 191
|
||||
|
||||
|
@ -8,6 +12,10 @@ local footerY = Dim.design.height - FOOTER_HEIGHT
|
|||
|
||||
-- Images
|
||||
local footerRightImage = gfx.CreateSkinImage("components/bars/footer_right.png", 0)
|
||||
local timeImage = gfx.CreateSkinImage("components/bars/time.png", 0)
|
||||
local creditImage = gfx.CreateSkinImage("components/bars/credit.png", 0)
|
||||
local timeNumbers = Num.load_number_image("components/bars/time_num")
|
||||
local timeColon = gfx.CreateSkinImage("components/bars/time_colon.png", 0)
|
||||
|
||||
-- Animation related
|
||||
local entryTransitionScale = 0
|
||||
|
@ -15,7 +23,68 @@ local entryTransitionFooterYOffset = 0
|
|||
|
||||
local legend = {{control = "START", text = "Confirm selection"}, {control = "KNOB", text = "Scroll"}}
|
||||
|
||||
local function set() end
|
||||
local timeOut = tonumber(GameConfig["DemoIdleTime"]) or 0
|
||||
local demoIdleTime = timeOut
|
||||
local enableTimer = game.GetSkinSetting("songselect_enableTimer") or false
|
||||
local freezeTimer = game.GetSkinSetting("songselect_freezeTimer") or -1
|
||||
|
||||
local function resetTimer()
|
||||
timeOut = demoIdleTime or 0
|
||||
end
|
||||
|
||||
local function set(o)
|
||||
o = o or {
|
||||
enableTimer=game.GetSkinSetting("songselect_enableTimer") or false,
|
||||
freezeTimer=game.GetSkinSetting("songselect_freezeTimer") or -1
|
||||
}
|
||||
enableTimer = o.enableTimer
|
||||
freezeTimer = o.freezeTimer
|
||||
end
|
||||
|
||||
local function drawTimer(time_s, show_minutes)
|
||||
if show_minutes then
|
||||
gfx.BeginPath()
|
||||
local xpos, ypos = Dim.design.width - 500, footerY + 55
|
||||
local w, h = gfx.ImageSize(timeImage)
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeImage, 1, 0)
|
||||
|
||||
-- Draw minutes:seconds display
|
||||
local minutes, seconds = time_s // 60, time_s % 60
|
||||
|
||||
local tens, ones = math.floor(minutes // 10), math.floor(minutes % 10)
|
||||
w, h = 90, 90
|
||||
xpos, ypos = xpos + 55, footerY - 16
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
|
||||
xpos = xpos + 2 * w
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos - 29, ypos, w, h, timeColon, 1, 0)
|
||||
|
||||
xpos = xpos + 32
|
||||
tens, ones = math.floor(seconds // 10), math.floor(seconds % 10)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
else
|
||||
gfx.BeginPath()
|
||||
local xpos, ypos = Dim.design.width - 270, footerY + 55
|
||||
local w, h = gfx.ImageSize(timeImage)
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeImage, 1, 0)
|
||||
|
||||
-- Draw only seconds
|
||||
local tens, ones = math.floor(time_s // 10), math.floor(time_s % 10)
|
||||
w, h = 90, 90
|
||||
xpos, ypos = xpos + 55, footerY - 16
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function drawFooter()
|
||||
gfx.BeginPath()
|
||||
|
@ -23,9 +92,25 @@ local function drawFooter()
|
|||
gfx.Rect(0, footerY, Dim.design.width, FOOTER_HEIGHT)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(Dim.design.width - 275, footerY - 25, 328 * 0.85, 188 * 0.85, footerRightImage, 1, 0)
|
||||
-- Timer
|
||||
local showTimer = enableTimer and (freezeTimer ~= -1 or GameConfig["EventMode"] == "True")
|
||||
if showTimer then
|
||||
-- Show dynamic timer
|
||||
if freezeTimer ~= -1 then
|
||||
drawTimer(freezeTimer, freezeTimer > 59)
|
||||
else
|
||||
drawTimer(timeOut, demoIdleTime and demoIdleTime > 59)
|
||||
end
|
||||
gfx.BeginPath()
|
||||
local w, h = gfx.ImageSize(creditImage)
|
||||
gfx.ImageRect(Dim.design.width - 190, footerY + 95, w, h, creditImage, 1, 0)
|
||||
else
|
||||
-- Show static image
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(Dim.design.width - 275, footerY - 25, 328 * 0.85, 188 * 0.85, footerRightImage, 1, 0)
|
||||
end
|
||||
|
||||
-- Version String
|
||||
gfx.BeginPath()
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
|
||||
gfx.FontSize(20)
|
||||
|
@ -40,6 +125,8 @@ local function progressTransitions(deltaTime)
|
|||
|
||||
entryTransitionFooterYOffset = FOOTER_HEIGHT * (1 - entryTransitionScale)
|
||||
footerY = Dim.design.height - FOOTER_HEIGHT + entryTransitionFooterYOffset
|
||||
|
||||
timeOut = math.max(timeOut - deltaTime, 0)
|
||||
end
|
||||
|
||||
local function draw(deltaTime, params)
|
||||
|
@ -64,4 +151,4 @@ local function draw(deltaTime, params)
|
|||
gfx.Restore()
|
||||
end
|
||||
|
||||
return {set = set, draw = draw}
|
||||
return {set = set, draw = draw, resetTimer = resetTimer}
|
||||
|
|
|
@ -0,0 +1,619 @@
|
|||
--[[
|
||||
S2 song attribute radar component
|
||||
Original code thanks to RealFD, he's a real homie
|
||||
]]
|
||||
|
||||
require("common.globals")
|
||||
require("api.point2d")
|
||||
require("api.color")
|
||||
|
||||
local Dim = require("common.dimensions")
|
||||
local Util = require("common.util")
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
local RADAR_PURPLE = ColorRGBA.new(238, 130, 238)
|
||||
local RADAR_MAGENTA = ColorRGBA.new(191, 70, 235)
|
||||
local RADAR_GREEN = ColorRGBA.new(0, 255, 100)
|
||||
|
||||
local maxScaleFactor = 1.8
|
||||
|
||||
---@param p1 Point2D
|
||||
---@param p2 Point2D
|
||||
---@param width number
|
||||
---@param color ColorRGBA
|
||||
local function drawLine(p1, p2, width, color)
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(p1:coords())
|
||||
gfx.LineTo(p2:coords())
|
||||
gfx.StrokeColor(color:components())
|
||||
gfx.StrokeWidth(width)
|
||||
gfx.Stroke()
|
||||
end
|
||||
|
||||
---@param pos Point2D
|
||||
---@param text string
|
||||
---@param outlineWidth number
|
||||
---@param color ColorRGBA
|
||||
local function renderOutlinedText(pos, text, outlineWidth, color)
|
||||
local x, y = pos:coords()
|
||||
|
||||
local dimColor = color:mix(ColorRGBA.BLACK, 0.8)
|
||||
gfx.FillColor(dimColor:components());
|
||||
gfx.Text(text, x - outlineWidth, y + outlineWidth);
|
||||
gfx.Text(text, x - outlineWidth, y - outlineWidth);
|
||||
gfx.Text(text, x + outlineWidth, y + outlineWidth);
|
||||
gfx.Text(text, x + outlineWidth, y - outlineWidth);
|
||||
|
||||
gfx.FillColor(color:components());
|
||||
gfx.Text(text, x, y);
|
||||
end
|
||||
|
||||
---@param pos Point2D
|
||||
---@param graphdata table
|
||||
local function drawDebugText(pos, graphdata)
|
||||
local color = ColorRGBA.WHITE
|
||||
gfx.Save()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_CENTER)
|
||||
--renderOutlinedText(x, 20, '"' .. txtFilePath .. '"', 1, 255, 255, 255)
|
||||
renderOutlinedText(pos, "NOTES = " .. graphdata.notes, 1, color)
|
||||
renderOutlinedText(pos, "PEAK = " .. graphdata.peak, 1, color)
|
||||
renderOutlinedText(pos, "TSUMAMI = " .. graphdata.tsumami, 1, color)
|
||||
renderOutlinedText(pos, "TRICKY = " .. graphdata.tricky, 1, color)
|
||||
renderOutlinedText(pos, "ONE-HAND = " .. graphdata.onehand, 1, color)
|
||||
renderOutlinedText(pos, "HAND-TRIP = " .. graphdata.handtrip, 1, color)
|
||||
--renderOutlinedText(pos, "NOTES (Relative) = " .. graphdata.notes_relative, 1, color)
|
||||
--renderOutlinedText(pos, "TOTAL-MESURES = " .. graphdata.measures, 1, color)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
---@class CRadarAttributes
|
||||
RadarAttributes = {
|
||||
---Create RadarAttributes instance
|
||||
---@param text? string # default ""
|
||||
---@param offset? Point2D # default (0, 0)
|
||||
---@param color? ColorRGBA # default BLACK
|
||||
---@param align? integer # gfx.TEXT_ALIGN_<...> values, default gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE
|
||||
---@return RadarAttributes
|
||||
new = function (text, offset, color, align)
|
||||
---@class RadarAttributes
|
||||
---@field text string
|
||||
---@field offset Point2D
|
||||
---@field color ColorRGBA
|
||||
local o = {
|
||||
text = text or "",
|
||||
offset = offset or Point2D.ZERO,
|
||||
color = color or ColorRGBA.BLACK,
|
||||
align = align or gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE
|
||||
}
|
||||
|
||||
setmetatable(o, RadarAttributes)
|
||||
return o
|
||||
end
|
||||
}
|
||||
RadarAttributes.__index = RadarAttributes
|
||||
|
||||
|
||||
---@class CRadar
|
||||
Radar = {
|
||||
---@type RadarAttributes[][]
|
||||
ATTRIBUTES = {
|
||||
{RadarAttributes.new("notes", Point2D.new(0, 0), ColorRGBA.CYAN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),},
|
||||
{RadarAttributes.new("peak", Point2D.new(0, 0), ColorRGBA.RED, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM), },
|
||||
{RadarAttributes.new("tsumami", Point2D.new(0, 0), RADAR_PURPLE, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),},
|
||||
{RadarAttributes.new("tricky", Point2D.new(0, 0), ColorRGBA.YELLOW, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),},
|
||||
{
|
||||
RadarAttributes.new("hand", Point2D.new(0, 0), RADAR_MAGENTA, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),
|
||||
RadarAttributes.new("trip", Point2D.new(5, 16), RADAR_MAGENTA, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),
|
||||
},
|
||||
{
|
||||
RadarAttributes.new("one", Point2D.new(6, -16), RADAR_GREEN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),
|
||||
RadarAttributes.new("hand", Point2D.new(0, 0), RADAR_GREEN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),
|
||||
}
|
||||
},
|
||||
RADIUS = 100.0,
|
||||
|
||||
---Create Radar instance
|
||||
---@param pos Point2D
|
||||
---@param radius? number
|
||||
---@return Radar
|
||||
new = function (pos, radius)
|
||||
---@class Radar : CRadar
|
||||
local o = {
|
||||
_graphdata = {
|
||||
notes = 0,
|
||||
peak = 0,
|
||||
tsumami = 0,
|
||||
tricky = 0,
|
||||
handtrip = 0,
|
||||
onehand = 0,
|
||||
},
|
||||
_hexagonMesh = gfx.CreateShadedMesh("radar"),
|
||||
_outlineVertices = {},
|
||||
_attributePositions = {}, ---@type Point2D[][]
|
||||
_angleStep = (2 * math.pi) / #Radar.ATTRIBUTES, -- 360° / no. attributes, in radians
|
||||
_initRotation = math.pi / 2, -- 90°, in radians
|
||||
pos = pos or Point2D.ZERO,
|
||||
scale = radius and radius / Radar.RADIUS or 1.0,
|
||||
}
|
||||
|
||||
local sides = #Radar.ATTRIBUTES
|
||||
|
||||
local outlineRadius = Radar.RADIUS
|
||||
local attributeRadius = Radar.RADIUS + 30
|
||||
|
||||
for i = 0, sides - 1 do
|
||||
local attrIdx = i + 1
|
||||
local angle = i * o._angleStep - o._initRotation
|
||||
local cosAngle = math.cos(angle)
|
||||
local sinAngle = math.sin(angle)
|
||||
-- cache outline vertices
|
||||
table.insert(o._outlineVertices, Point2D.new(outlineRadius * cosAngle, outlineRadius * sinAngle))
|
||||
-- cache attribute positions
|
||||
table.insert(o._attributePositions, {})
|
||||
for j = 1, #Radar.ATTRIBUTES[attrIdx] do
|
||||
local attr = Radar.ATTRIBUTES[attrIdx][j]
|
||||
local attributePos = Point2D.new(attributeRadius * cosAngle, attributeRadius * sinAngle)
|
||||
attributePos.x = attributePos.x + attr.offset.x
|
||||
attributePos.y = attributePos.y + attr.offset.y
|
||||
table.insert(o._attributePositions[attrIdx], j, attributePos)
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(o, Radar)
|
||||
return o
|
||||
end,
|
||||
}
|
||||
Radar.__index = Radar
|
||||
|
||||
---@param w number
|
||||
---@param color ColorRGBA
|
||||
function Radar:drawOutline(w, color)
|
||||
---@cast self Radar
|
||||
|
||||
for i = 1, #self._outlineVertices do
|
||||
local j = i % #self._outlineVertices + 1
|
||||
drawLine(self._outlineVertices[i], self._outlineVertices[j], w, color)
|
||||
end
|
||||
end
|
||||
|
||||
---@param color ColorRGBA
|
||||
---@param ticks? integer
|
||||
function Radar:drawRadialTicks(color, ticks)
|
||||
---@cast self Radar
|
||||
|
||||
ticks = ticks or 3
|
||||
|
||||
gfx.Save()
|
||||
gfx.StrokeColor(color:components())
|
||||
|
||||
for i, vertex in ipairs(self._outlineVertices) do
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, 0)
|
||||
gfx.LineTo(vertex.x, vertex.y)
|
||||
gfx.Stroke()
|
||||
|
||||
local lineLength = math.sqrt(vertex.x * vertex.x + vertex.y * vertex.y)
|
||||
local tinyLineLength = 10
|
||||
|
||||
local tinyLineAngle = math.atan(vertex.y / vertex.x)
|
||||
if vertex.x < 0 then
|
||||
tinyLineAngle = tinyLineAngle + math.pi
|
||||
end
|
||||
|
||||
local halfTinyLineLength = tinyLineLength / 2
|
||||
|
||||
for j = 1, ticks do
|
||||
local distanceFromCenter = j * lineLength / (ticks + 1) -- Adjusted for 3 middle lines
|
||||
|
||||
local offsetX = distanceFromCenter * (vertex.x / lineLength)
|
||||
local offsetY = distanceFromCenter * (vertex.y / lineLength)
|
||||
|
||||
local endX = halfTinyLineLength * math.cos(tinyLineAngle - math.pi / 2) -- Rotate by -90 degrees
|
||||
local endY = halfTinyLineLength * math.sin(tinyLineAngle - math.pi / 2) -- Rotate by -90 degrees
|
||||
|
||||
local offsetX2 = halfTinyLineLength * math.cos(tinyLineAngle + math.pi / 2)
|
||||
local offsetY2 = halfTinyLineLength * math.sin(tinyLineAngle + math.pi / 2)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(offsetX - offsetX2, offsetY - offsetY2)
|
||||
gfx.LineTo(endX + offsetX + offsetX2 + offsetX2, endY + offsetY + offsetY2 + offsetY2)
|
||||
gfx.Stroke()
|
||||
end
|
||||
end
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
---@param fillColor ColorRGBA
|
||||
function Radar:drawBackground(fillColor)
|
||||
---@cast self Radar
|
||||
|
||||
gfx.Save()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(self._outlineVertices[1].x, self._outlineVertices[1].y)
|
||||
for i = 2, #self._outlineVertices do
|
||||
gfx.LineTo(self._outlineVertices[i].x, self._outlineVertices[i].y)
|
||||
end
|
||||
gfx.ClosePath()
|
||||
|
||||
gfx.FillColor(fillColor:components())
|
||||
gfx.Fill()
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function Radar:drawAttributes()
|
||||
---@cast self Radar
|
||||
|
||||
gfx.Save()
|
||||
|
||||
gfx.LoadSkinFont("contb.ttf")
|
||||
gfx.FontSize(21)
|
||||
for i = 1, #self._attributePositions do
|
||||
local attrPos = self._attributePositions[i]
|
||||
for j = 1, #attrPos do
|
||||
local pos = attrPos[j]
|
||||
local attr = Radar.ATTRIBUTES[i][j]
|
||||
gfx.TextAlign(attr.align)
|
||||
renderOutlinedText(pos, string.upper(attr.text), 1, attr.color)
|
||||
end
|
||||
end
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
---Draw shaded radar mesh
|
||||
---
|
||||
---Bug: ForceRender resets every transformation, you need to re-setup view transform afterwards.
|
||||
---ForceRender also resets the gfx stack, USC will crash if you try to call gfx.Restore(),
|
||||
---make sure the gfx stack is clean before calling radar:drawRadarMesh()
|
||||
function Radar:drawRadarMesh()
|
||||
---@cast self Radar
|
||||
|
||||
local scaleFact = {
|
||||
self._graphdata.notes,
|
||||
self._graphdata.peak,
|
||||
self._graphdata.tsumami,
|
||||
self._graphdata.tricky,
|
||||
self._graphdata.handtrip,
|
||||
self._graphdata.onehand,
|
||||
}
|
||||
|
||||
local colorMax = ColorRGBA.new(255, 12, 48, 230) -- magenta-ish
|
||||
local colorCenter = ColorRGBA.new(112, 119, 255, 230) -- light blue-ish purple
|
||||
|
||||
-- Calculate the maximum size based on the constraint
|
||||
local maxSize = self.RADIUS * self.scale
|
||||
local maxLineLength = maxSize * maxScaleFactor
|
||||
self._hexagonMesh:SetParam("maxSize", maxLineLength + .0)
|
||||
|
||||
-- Set the color of the hexagon
|
||||
self._hexagonMesh:SetParamVec4("colorMax", colorMax:componentsFloat())
|
||||
self._hexagonMesh:SetParamVec4("colorCenter", colorCenter:componentsFloat())
|
||||
|
||||
-- Set the primitive type to triangles
|
||||
self._hexagonMesh:SetPrimitiveType(self._hexagonMesh.PRIM_TRIFAN)
|
||||
|
||||
-- Calculate the vertices of the hexagon
|
||||
local sides = #Radar.ATTRIBUTES
|
||||
|
||||
local vertices = {}
|
||||
table.insert(vertices, {{0, 0}, {0, 0}})
|
||||
for i = 0, sides do
|
||||
local j = i % sides + 1
|
||||
local angle = i * self._angleStep - self._initRotation
|
||||
|
||||
--local angle = math.rad(60 * (i-1)) + rotationAngle
|
||||
local scale = scaleFact[j]
|
||||
local lineLength = maxSize * scale
|
||||
local px = lineLength * math.cos(angle)
|
||||
local py = lineLength * math.sin(angle)
|
||||
table.insert(vertices, {{px, py}, {0, 0}})
|
||||
end
|
||||
|
||||
-- Set the hexagon's vertices
|
||||
self._hexagonMesh:SetData(vertices)
|
||||
self._hexagonMesh:Draw()
|
||||
|
||||
-- YOU! You are the reason for all my pain!
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
--NOTE: THIS IS BUGGY, ForceRender fucks up so many things, call the individual draw functions at top level
|
||||
function Radar:drawGraph()
|
||||
---@cast self Radar
|
||||
|
||||
game.Log("Radar:drawGraph() SHOULD NOT BE CALLED", game.LOGGER_WARNING)
|
||||
|
||||
gfx.Save()
|
||||
gfx.Reset()
|
||||
gfx.ResetScissor()
|
||||
|
||||
Dim.updateResolution()
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
gfx.FontSize(28)
|
||||
gfx.Translate(self.pos.x, self.pos.y)
|
||||
gfx.Scale(self.scale, self.scale)
|
||||
|
||||
local strokeColor = ColorRGBA.new(255, 255, 255, 100)
|
||||
local fillColor = ColorRGBA.new(0, 0, 0, 191)
|
||||
|
||||
self:drawBackground(fillColor)
|
||||
self:drawOutline(3, strokeColor)
|
||||
self:drawRadarMesh()
|
||||
self:drawRadialTicks(strokeColor)
|
||||
self:drawAttributes()
|
||||
|
||||
local pos = Point2D.new(self.pos:coords())
|
||||
pos.y = pos.y - self.RADIUS
|
||||
--drawDebugText(pos, self._graphdata)
|
||||
|
||||
gfx.Restore()
|
||||
|
||||
--NOTE: Bug workaround: forcerender resets every transformation, re-setup view transform
|
||||
Dim.transformToScreenSpace()
|
||||
end
|
||||
|
||||
---Compute radar attribute values from ksh
|
||||
---@param info string # chart directory path
|
||||
---@param dif string # chart name without extension
|
||||
function Radar:updateGraph(info, dif)
|
||||
---@cast self Radar
|
||||
|
||||
--local pattern = "(.*[\\/])"
|
||||
--local extractedSubstring = info:match(pattern)
|
||||
--local txtFilePath = extractedSubstring .. "radar\\" .. dif .. ".txt"
|
||||
|
||||
--local song = io.open(txtFilePath, "r")
|
||||
local fullPath = info.."/"..dif..".ksh"
|
||||
local song = io.open(fullPath)
|
||||
game.Log('Reading chart data from "'..fullPath..'"', game.LOGGER_DEBUG)
|
||||
game.Log(song and "file open" or "file not found", game.LOGGER_DEBUG)
|
||||
if song then
|
||||
local chartData = song:read("*all")
|
||||
song:close()
|
||||
|
||||
local notesCount, knobCount, oneHandCount, handTripCount = 0, 0, 0, 0
|
||||
local chartLineCount = 0
|
||||
local notesValue = 0
|
||||
local peakValue = 0
|
||||
local tsumamiValue = 0
|
||||
local trickyValue = 0
|
||||
local totalMeasures = 0
|
||||
|
||||
local lastNotes = {}
|
||||
local lastFx = {}
|
||||
local measureLength = 0
|
||||
|
||||
---@cast chartData string
|
||||
for line in chartData:gmatch("[^\r\n]+") do
|
||||
-- <bt-lanes x 4>|<fx-lanes x 2>|<laser-lanes x 2><lane-spin (optional)>
|
||||
|
||||
--game.Log(line, game.LOGGER_DEBUG)
|
||||
|
||||
local patternBt = "([012][012][012][012])"
|
||||
local patternFx = "([012ABDFGHIJKLPQSTUVWX][012ABDFGHIJKLPQSTUVWX])"
|
||||
local patternLaser = "([%-:%dA-Za-o][%-:%dA-Za-o])"
|
||||
local patternLaneSpin = "([@S][%(%)<>]%d+)" -- optional
|
||||
local pattern = patternBt.."|"..patternFx.."|"..patternLaser
|
||||
|
||||
-- match line format
|
||||
|
||||
local noteType, fxType, laserType = line:match(pattern)
|
||||
local laneSpin = line:match(patternLaneSpin)
|
||||
|
||||
if noteType and fxType and laserType then
|
||||
chartLineCount = chartLineCount + 1
|
||||
|
||||
-- convert strings to array, to be easily indexable
|
||||
|
||||
noteType = {noteType:match("([012])([012])([012])([012])")}
|
||||
fxType = {fxType:match("([012ABDFGHIJKLPQSTUVWX])([012ABDFGHIJKLPQSTUVWX])")}
|
||||
laserType = {laserType:match("([%-:%dA-Za-o])([%-:%dA-Za-o])")}
|
||||
|
||||
---@cast noteType string[]
|
||||
---@cast fxType string[]
|
||||
---@cast laserType string[]
|
||||
|
||||
-- parse notes
|
||||
|
||||
local function isNewNote(idx, note)
|
||||
if note == "2" and lastNotes[idx] ~= note then
|
||||
-- a new hold note
|
||||
return true
|
||||
end
|
||||
if note == "1" then
|
||||
-- a chip
|
||||
return true
|
||||
end
|
||||
end
|
||||
for noteIdx, note in ipairs(noteType) do
|
||||
if isNewNote(noteIdx, note) then
|
||||
notesCount = notesCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- parse fx
|
||||
|
||||
local function isNewFx(idx, fx)
|
||||
if fx:match("[1ABDFGHIJKLPQSTUVWX]") and lastFx[idx] ~= fx then
|
||||
-- a new hold note
|
||||
return true
|
||||
end
|
||||
if fx == "2" then
|
||||
-- a chip
|
||||
return true
|
||||
end
|
||||
end
|
||||
for fxIdx, fx in ipairs(fxType) do
|
||||
if isNewFx(fxIdx, fx) then
|
||||
notesCount = notesCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- parse laser
|
||||
|
||||
for _, laser in ipairs(laserType) do
|
||||
if laser ~= "-" then
|
||||
knobCount = knobCount + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- figure out one-handed notes (there's a BT or FX while a hand is manipulating a knob)
|
||||
-- also try to figure out cross-handed notes (one-handed notes, but on the same side as knob)
|
||||
|
||||
local function countBtFx()
|
||||
local count = 0
|
||||
for noteIdx, note in ipairs(noteType) do
|
||||
if isNewNote(noteIdx, note) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for fxIdx, fx in ipairs(fxType) do
|
||||
if isNewFx(fxIdx, fx) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
---@param side "left"|"right"
|
||||
local function countSide(side)
|
||||
local count = 0
|
||||
local notes = {}
|
||||
local fx = ""
|
||||
if side == "left" then
|
||||
notes = {noteType[1], noteType[2]}
|
||||
fx = fxType[1]
|
||||
if isNewFx(1, fx) then
|
||||
count = count + 1
|
||||
end
|
||||
elseif side == "right" then
|
||||
notes = {noteType[3], noteType[4]}
|
||||
fx = fxType[2]
|
||||
if isNewFx(2, fx) then
|
||||
count = count + 1
|
||||
end
|
||||
else
|
||||
game.Log("countSide: Invalid side parameter", game.LOGGER_ERROR)
|
||||
return 0
|
||||
end
|
||||
for noteIdx, note in ipairs(notes) do
|
||||
if isNewNote(noteIdx, note) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
if laserType[1] ~= "-" and laserType[2] == "-" then
|
||||
oneHandCount = oneHandCount + countBtFx()
|
||||
handTripCount = handTripCount + countSide("left")
|
||||
end
|
||||
if laserType[1] == "-" and laserType[2] ~= "-" then
|
||||
oneHandCount = oneHandCount + countBtFx()
|
||||
handTripCount = handTripCount + countSide("right")
|
||||
end
|
||||
|
||||
lastNotes = noteType
|
||||
lastFx = fxType
|
||||
|
||||
measureLength = measureLength + 1
|
||||
end
|
||||
|
||||
if line == "--" then
|
||||
-- end of measure
|
||||
measureLength = math.max(1, measureLength)
|
||||
|
||||
local relativeMeasureLength = measureLength / 192
|
||||
|
||||
-- calculate peak density
|
||||
local peak = (notesCount / 6) / relativeMeasureLength
|
||||
peakValue = math.max(peakValue, peak)
|
||||
--[[
|
||||
local debuglog = {
|
||||
measureLength = measureLength,
|
||||
notesCount = notesCount,
|
||||
relativeMeasureLength = relativeMeasureLength,
|
||||
peak = peak,
|
||||
}
|
||||
for k, v in pairs(debuglog) do
|
||||
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||
end
|
||||
]]
|
||||
|
||||
-- cumulate "time" spent operating the knobs
|
||||
local tsumami = (knobCount / 2) / relativeMeasureLength
|
||||
tsumamiValue = tsumamiValue + tsumami
|
||||
|
||||
measureLength = 0
|
||||
notesCount = 0
|
||||
|
||||
-- cumulate peak values (used to average notes over the length of the song)
|
||||
notesValue = notesValue + peak
|
||||
|
||||
totalMeasures = totalMeasures + 1
|
||||
end
|
||||
|
||||
local beat = line:match("beat=(%d+/%d+)")
|
||||
if beat then
|
||||
beat = {beat:match("(%d+)/(%d+)")}
|
||||
end
|
||||
|
||||
--BUG: This is not correct, it needs to account for effect length
|
||||
local function isTricky()
|
||||
local tricks = {
|
||||
"beat",
|
||||
"stop",
|
||||
"zoom_top",
|
||||
"zoom_bottom",
|
||||
"zoom_side",
|
||||
"center_split",
|
||||
}
|
||||
return Util.any(tricks, function(e) return line:match("e") end)
|
||||
end
|
||||
if laneSpin or isTricky() then
|
||||
trickyValue = trickyValue + 1
|
||||
end
|
||||
end
|
||||
|
||||
local graphValues = {
|
||||
notes = notesValue / totalMeasures,
|
||||
peak = peakValue,
|
||||
tsumami = tsumamiValue / totalMeasures,
|
||||
tricky = trickyValue,
|
||||
handtrip = handTripCount,
|
||||
onehand = oneHandCount,
|
||||
}
|
||||
|
||||
game.Log("graphValues", game.LOGGER_DEBUG)
|
||||
for k,v in pairs(graphValues) do
|
||||
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||
end
|
||||
|
||||
local calibration = {
|
||||
notes = 10,
|
||||
peak = 48,
|
||||
tsumami = 20000,
|
||||
tricky = 128,
|
||||
handtrip = 300,
|
||||
onehand = 300,
|
||||
}
|
||||
|
||||
for key, factor in pairs(calibration) do
|
||||
-- Apply the scaling factor to each value
|
||||
self._graphdata[key] = graphValues[key] / factor
|
||||
|
||||
-- Limit to maximum scale factor
|
||||
self._graphdata[key] = math.min(self._graphdata[key], maxScaleFactor)
|
||||
end
|
||||
|
||||
game.Log("_graphdata", game.LOGGER_DEBUG)
|
||||
for k,v in pairs(self._graphdata) do
|
||||
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Radar
|
|
@ -1,11 +1,49 @@
|
|||
|
||||
local Dimensions = require 'common.dimensions'
|
||||
|
||||
local consoleBaseImage = gfx.CreateSkinImage("gameplay/console/base.png", 0)
|
||||
|
||||
local CONSOLE_W = 1352;
|
||||
local CONSOLE_H = 712;
|
||||
|
||||
local function createConsoleImage(name)
|
||||
return gfx.CreateSkinImage("gameplay/console/"..name..".png", 0)
|
||||
end
|
||||
|
||||
local function renderConsoleImage(image, alpha)
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(
|
||||
-CONSOLE_W/2,
|
||||
-CONSOLE_H/2+350,
|
||||
CONSOLE_W,
|
||||
CONSOLE_H,
|
||||
image,
|
||||
alpha,
|
||||
0
|
||||
);
|
||||
end
|
||||
|
||||
local consoleBaseImage = createConsoleImage("base")
|
||||
|
||||
local buttonGlowImages = {
|
||||
[0] = createConsoleImage("glow_bta"),
|
||||
createConsoleImage("glow_btb"),
|
||||
createConsoleImage("glow_btc"),
|
||||
createConsoleImage("glow_btd"),
|
||||
createConsoleImage("glow_fxl"),
|
||||
createConsoleImage("glow_fxr"),
|
||||
};
|
||||
|
||||
local knobGlowImages = {
|
||||
[0] = createConsoleImage("glow_voll"),
|
||||
createConsoleImage("glow_volr"),
|
||||
};
|
||||
|
||||
local lastKnobState = {
|
||||
[0] = -1,
|
||||
-1
|
||||
};
|
||||
|
||||
local doFlash = true;
|
||||
|
||||
local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRotation)
|
||||
local resx, resy = game.GetResolution();
|
||||
if (resx > resy) then
|
||||
|
@ -18,16 +56,32 @@ local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRo
|
|||
critLineRotation
|
||||
)
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(
|
||||
-CONSOLE_W/2,
|
||||
-CONSOLE_H/2+350,
|
||||
CONSOLE_W,
|
||||
CONSOLE_H,
|
||||
consoleBaseImage,
|
||||
1,
|
||||
0
|
||||
);
|
||||
renderConsoleImage(consoleBaseImage, 1)
|
||||
|
||||
if doFlash then
|
||||
for button=0,5 do
|
||||
if game.GetButton(button) then
|
||||
renderConsoleImage(buttonGlowImages[button], 0.75)
|
||||
end
|
||||
end
|
||||
|
||||
-- Knobs also work
|
||||
-- commented out do to missing/incorrect textures
|
||||
|
||||
--[[
|
||||
for knob=0,1 do
|
||||
local state = game.GetKnob(knob)
|
||||
|
||||
if state ~= lastKnobState[knob] then
|
||||
renderConsoleImage(knobGlowImages[knob], 1)
|
||||
|
||||
lastKnobState[knob] = state
|
||||
end
|
||||
end
|
||||
]]
|
||||
end
|
||||
|
||||
doFlash = not doFlash;
|
||||
end
|
||||
|
||||
return {
|
||||
|
|
|
@ -17,18 +17,21 @@ local cursorGlowTopImages = {
|
|||
gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_top_left.png", 0),
|
||||
gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_top_right.png", 0),
|
||||
}
|
||||
|
||||
local cursorGlowWhite = gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_white.png", 0);
|
||||
local cursorGlowColor = gfx.CreateSkinImage("gameplay/crit_line/cursor_glow_color.png", 0);
|
||||
local cursorTailColor = gfx.CreateSkinImage("gameplay/crit_line/cursor_tail_color.png", 0)
|
||||
local cursorTailImages = {
|
||||
gfx.CreateSkinImage("gameplay/crit_line/cursor_tail_l.png", 0),
|
||||
gfx.CreateSkinImage("gameplay/crit_line/cursor_tail_r.png", 0),
|
||||
}
|
||||
|
||||
local CRITBAR_W = 1080
|
||||
local CRITBAR_H = 251
|
||||
local CRITBAR_W = 1080 * 1.4
|
||||
local CRITBAR_H = 251 * 1.4
|
||||
|
||||
local scale = 1;
|
||||
local isLandscape = false;
|
||||
|
||||
local drawCursors = function (centerX, centerY,cursors, laserActive)
|
||||
local drawCursors = function (scale, cursors, laserActive)
|
||||
local cursorW = 598 * 0.165;
|
||||
local cursorH = 673 * 0.14;
|
||||
|
||||
|
@ -38,6 +41,7 @@ local drawCursors = function (centerX, centerY,cursors, laserActive)
|
|||
for i = 0, 1, 1 do
|
||||
local luaIndex = i + 1
|
||||
local cursor = cursors[i];
|
||||
local r, g, b = game.GetLaserColor(i);
|
||||
|
||||
gfx.Save();
|
||||
gfx.BeginPath();
|
||||
|
@ -45,20 +49,23 @@ local drawCursors = function (centerX, centerY,cursors, laserActive)
|
|||
local skew = cursor.pos * 0.001;
|
||||
gfx.SkewX(skew);
|
||||
|
||||
local cursorX = cursor.pos * (1 / scale) - cursorW / 2;
|
||||
local cursorPos = cursor.pos * (1 / scale)
|
||||
local cursorX = cursorPos - cursorW / 2;
|
||||
local cursorY = -cursorH / 2;
|
||||
|
||||
if laserActive[luaIndex] then
|
||||
gfx.ImageRect(
|
||||
cursor.pos - tailW / 2,
|
||||
- tailH / 2,
|
||||
tailW,
|
||||
tailH,
|
||||
cursorTailImages[luaIndex],
|
||||
cursor.alpha / 2,
|
||||
0
|
||||
)
|
||||
end
|
||||
gfx.SetImageTint(r, g, b);
|
||||
gfx.ImageRect(
|
||||
cursorPos - tailW / 2,
|
||||
- tailH / 2,
|
||||
tailW,
|
||||
tailH,
|
||||
cursorTailColor,
|
||||
cursor.alpha / 2,
|
||||
0
|
||||
)
|
||||
|
||||
local glowAlpha = cursor.alpha;
|
||||
if (i == 1) then glowAlpha = glowAlpha * 0.7; end
|
||||
|
||||
gfx.ImageRect(
|
||||
cursorX,
|
||||
|
@ -75,11 +82,23 @@ local drawCursors = function (centerX, centerY,cursors, laserActive)
|
|||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowBottomImages[luaIndex],
|
||||
cursor.alpha,
|
||||
cursorGlowWhite,
|
||||
glowAlpha,
|
||||
0
|
||||
);
|
||||
|
||||
gfx.SetImageTint(r, g, b);
|
||||
gfx.ImageRect(
|
||||
cursorX,
|
||||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowColor,
|
||||
glowAlpha,
|
||||
0
|
||||
);
|
||||
|
||||
gfx.SetImageTint(255, 255, 255);
|
||||
gfx.ImageRect(
|
||||
cursorX,
|
||||
cursorY,
|
||||
|
@ -95,17 +114,29 @@ local drawCursors = function (centerX, centerY,cursors, laserActive)
|
|||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowTopImages[luaIndex],
|
||||
cursor.alpha,
|
||||
cursorGlowWhite,
|
||||
glowAlpha,
|
||||
0
|
||||
);
|
||||
|
||||
|
||||
gfx.SetImageTint(r, g, b);
|
||||
gfx.ImageRect(
|
||||
cursorX,
|
||||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowColor,
|
||||
glowAlpha,
|
||||
0
|
||||
);
|
||||
|
||||
gfx.SetImageTint(255, 255, 255);
|
||||
gfx.Restore();
|
||||
end
|
||||
end
|
||||
|
||||
local renderBase = function (deltaTime, centerX, centerY, rotation)
|
||||
scale, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
|
||||
_, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0, 0, 0, 192)
|
||||
|
@ -124,9 +155,9 @@ local renderBase = function (deltaTime, centerX, centerY, rotation)
|
|||
end
|
||||
|
||||
local renderOverlay = function (deltaTime, centerX, centerY, rotation, cursors, laserActive)
|
||||
scale, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
|
||||
scale, _ = Dimensions.setUpTransforms(centerX, centerY, rotation)
|
||||
|
||||
drawCursors(centerX, centerY, cursors, laserActive)
|
||||
drawCursors(scale, cursors, laserActive)
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
@ -134,4 +165,4 @@ end
|
|||
return {
|
||||
renderBase=renderBase,
|
||||
renderOverlay=renderOverlay
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,22 +11,29 @@ local compare = {
|
|||
["OFF"] = -1
|
||||
}
|
||||
|
||||
local heightFractions = {
|
||||
["UPPER+"] = 2,
|
||||
local portraitHeightFractions = {
|
||||
["UPPER+"] = 2.4,
|
||||
["UPPER"] = 3,
|
||||
["STANDARD"] = 3.5,
|
||||
["LOWER"] = 5,
|
||||
["STANDARD"] = 4.2,
|
||||
["LOWER"] = 5.3,
|
||||
}
|
||||
|
||||
local landscapeHeightFractions = {
|
||||
["UPPER+"] = 1.5,
|
||||
["UPPER"] = 2.7,
|
||||
["STANDARD"] = 4.1,
|
||||
["LOWER"] = 6.7,
|
||||
}
|
||||
|
||||
local earlyLateFor = compare[game.GetSkinSetting("gameplay_earlyLateFor")]
|
||||
local msFor = compare[game.GetSkinSetting("gameplay_msFor")]
|
||||
local heightFraction = heightFractions[game.GetSkinSetting("gameplay_earlyLatePosition")]
|
||||
local earlyLatePosition = game.GetSkinSetting("gameplay_earlyLatePosition")
|
||||
|
||||
local EarlyLate = {
|
||||
timer = 0,
|
||||
lastMillisec = 0,
|
||||
showEarlyLate = false,
|
||||
showMillisec = false,
|
||||
color = {},
|
||||
earlyLateText = "",
|
||||
millisecText = ""
|
||||
}
|
||||
|
||||
function EarlyLate.render(deltaTime)
|
||||
|
@ -39,56 +46,64 @@ function EarlyLate.render(deltaTime)
|
|||
local screenW, screenH = Dimensions.screen.width, Dimensions.screen.height
|
||||
local screenCenterX = screenW / 2
|
||||
|
||||
local desh
|
||||
local desh, fractionTable
|
||||
|
||||
if screenH > screenW then
|
||||
desh = 1920
|
||||
desh = 1600
|
||||
fractionTable = portraitHeightFractions
|
||||
else
|
||||
desh = 1080
|
||||
fractionTable = landscapeHeightFractions
|
||||
end
|
||||
|
||||
local scale = desh / screenH
|
||||
local y = screenH / 8 * heightFraction
|
||||
local scale = screenH / desh
|
||||
local y = screenH / 8 * fractionTable[earlyLatePosition]
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FontSize(18 * scale)
|
||||
gfx.LoadSkinFont("Digital-Serial-ExtraBold.ttf")
|
||||
gfx.FontSize(20 * scale)
|
||||
|
||||
if EarlyLate.showEarlyLate then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
local color = EarlyLate.color
|
||||
gfx.FillColor(color[1], color[2], color[3])
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
|
||||
if EarlyLate.lastMillisec < 0 then
|
||||
gfx.FillColor(206, 94, 135)
|
||||
gfx.FastText("EARLY", screenCenterX - 100 * scale, y)
|
||||
else
|
||||
gfx.FillColor(53, 102, 197)
|
||||
gfx.FastText("LATE", screenCenterX - 100 * scale, y)
|
||||
end
|
||||
end
|
||||
|
||||
if EarlyLate.showMillisec then
|
||||
local msString = string.format("%dms", EarlyLate.lastMillisec)
|
||||
|
||||
if EarlyLate.lastMillisec >= 0 then
|
||||
msString = "+"..msString -- prepend + sign for lates
|
||||
end
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_BASELINE)
|
||||
|
||||
gfx.FastText(msString, screenCenterX + 100 * scale, y)
|
||||
end
|
||||
gfx.FastText(EarlyLate.earlyLateText, screenCenterX - 100 * scale, y)
|
||||
gfx.FastText(EarlyLate.millisecText, screenCenterX + 100 * scale, y)
|
||||
end
|
||||
|
||||
function EarlyLate.TriggerAnimation(rating, millisec)
|
||||
local showEarlyLate = rating <= earlyLateFor
|
||||
local showMillisec = rating <= msFor
|
||||
local isEarly = millisec < 0
|
||||
|
||||
if showEarlyLate or showMillisec then
|
||||
EarlyLate.timer = 120
|
||||
EarlyLate.lastMillisec = millisec
|
||||
EarlyLate.showEarlyLate = showEarlyLate
|
||||
EarlyLate.showMillisec = showMillisec
|
||||
if millisec == 0 then return end
|
||||
if not showEarlyLate and not showMillisec then return end
|
||||
|
||||
if showEarlyLate then
|
||||
EarlyLate.earlyLateText = isEarly and "EARLY" or "LATE"
|
||||
else
|
||||
EarlyLate.earlyLateText = ""
|
||||
end
|
||||
|
||||
if showMillisec then
|
||||
local millisecText = string.format("%dms", millisec)
|
||||
|
||||
-- prepend + sign for lates
|
||||
millisecText = isEarly and millisecText or "+"..millisecText
|
||||
|
||||
EarlyLate.millisecText = millisecText
|
||||
else
|
||||
EarlyLate.millisecText = ""
|
||||
end
|
||||
|
||||
if isEarly then
|
||||
EarlyLate.color = {206, 94, 135}
|
||||
else
|
||||
EarlyLate.color = {53, 102, 197}
|
||||
end
|
||||
|
||||
EarlyLate.timer = 120
|
||||
end
|
||||
|
||||
return EarlyLate
|
|
@ -126,10 +126,10 @@ function HitFX.renderLasers(deltaTime, critCenterX, critCenterY, critRotation, c
|
|||
end
|
||||
|
||||
-- Render
|
||||
local laserColor = {game.GetLaserColor(laser - 1)}
|
||||
local x = cursors[laser - 1].pos
|
||||
scale, _ = Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation)
|
||||
|
||||
Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation)
|
||||
local laserColor = {game.GetLaserColor(laser - 1)}
|
||||
local x = cursors[laser - 1].pos * (1 / scale)
|
||||
|
||||
laserState.Dome:render(deltaTime, {
|
||||
centered = true,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
require("common.gameconfig")
|
||||
local VolforceWindow = require('components.volforceWindow');
|
||||
|
||||
local desw = 1080;
|
||||
|
@ -13,7 +13,9 @@ local danBadgeImage = gfx.CreateSkinImage("dan.png", 0);
|
|||
local idolFrameImage = gfx.CreateSkinImage("crew/frame.png", 0);
|
||||
|
||||
|
||||
local username = game.GetSkinSetting('username') or '';
|
||||
-- gameplay table does not have a current username field, because why would it lmao
|
||||
-- workaround: retrieve it directly from Main.cfg file
|
||||
local username = GameConfig["MultiplayerUsername"] or game.GetSkinSetting('username') or '';
|
||||
|
||||
local drawBestDiff = function (deltaTime, score, bestReplay, y)
|
||||
if not bestReplay then return end
|
||||
|
@ -138,4 +140,4 @@ end
|
|||
|
||||
return {
|
||||
render=render
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
local call = nil
|
||||
|
||||
local EN = require("language.EN")
|
||||
local DE = require("language.DE")
|
||||
local SK = require("language.SK")
|
||||
local HU = require("language.HU")
|
||||
local test2 = require("language.test2")
|
||||
|
||||
local call = EN
|
||||
|
||||
if game.GetSkinSetting('words') == "EN" then
|
||||
call = EN
|
||||
elseif game.GetSkinSetting('words') == "DE" then
|
||||
|
@ -19,4 +19,4 @@ elseif game.GetSkinSetting('words') == "test2" then
|
|||
end
|
||||
|
||||
|
||||
return call
|
||||
return call
|
||||
|
|
|
@ -1,36 +1,6 @@
|
|||
local json = require("common.json")
|
||||
|
||||
local common = require('common.util');
|
||||
local Sound = require("common.sound")
|
||||
local difbar = require("components.diff_rectangle");
|
||||
local spinnybg = require('components.background');
|
||||
local msg = game.GetSkinSetting("MSG");
|
||||
local normname = game.GetSkinSetting("username")
|
||||
|
||||
local creww = game.GetSkinSetting("single_idol")
|
||||
|
||||
local m_jacket = gfx.CreateSkinImage("multi/lobby/multi_jacket.png", 1);
|
||||
local m_base_panel = gfx.CreateSkinImage("multi/lobby/multi_base_panel.png", 1);
|
||||
local m_anim = gfx.CreateSkinImage("multi/lobby/panel_laser.png", 1);
|
||||
local m_panel = gfx.CreateSkinImage("multi/lobby/matching_panel.png", 1);
|
||||
local m_s_panel = gfx.CreateSkinImage("multi/lobby/song_panel.png", 1);
|
||||
local m_host_panel = gfx.CreateSkinImage("multi/lobby/user_panel.png", 1);
|
||||
local m_bpm_panel = gfx.CreateSkinImage("multi/lobby/lane_speed_panel.png", 1);
|
||||
local m_info_panel = gfx.CreateSkinImage("multi/lobby/button_panel.png", 1);
|
||||
|
||||
local headerMatchingImage = gfx.CreateSkinImage("titlescreen/entry.png", 1);
|
||||
|
||||
local m_4pb_panels = gfx.CreateSkinImage("multi/lobby/user_panel_2.png", 1);
|
||||
|
||||
local ready_bt = gfx.CreateSkinImage("multi/lobby/READY.png", 1);
|
||||
|
||||
local bg = gfx.CreateSkinImage("multi/lobby/bg.png", 1);
|
||||
local bg_graid1 = gfx.CreateSkinImage("multi/lobby/gradient_bottom.png", 1);
|
||||
local bg_graid2 = gfx.CreateSkinImage("multi/lobby/gradient_top.png", 1);
|
||||
|
||||
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true);
|
||||
local idolAnimTransitionScale = 0;
|
||||
|
||||
local sound = require('common.sound');
|
||||
|
||||
local resX,resY = game.GetResolution()
|
||||
|
||||
|
@ -41,15 +11,6 @@ local buttonWidth = resX*(3/4);
|
|||
local buttonHeight = 75;
|
||||
local buttonBorder = 2;
|
||||
local portrait
|
||||
local jacket_size;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
local HEADER_HEIGHT = 100
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
local scale;
|
||||
|
||||
game.LoadSkinSample("click-02")
|
||||
game.LoadSkinSample("click-01")
|
||||
|
@ -63,7 +24,6 @@ local user_id = nil;
|
|||
local jacket = 0;
|
||||
local all_ready;
|
||||
local user_ready;
|
||||
local go;
|
||||
local hard_mode = false;
|
||||
local rotate_host = false;
|
||||
local start_game_soon = false;
|
||||
|
@ -73,20 +33,19 @@ local missing_song = false;
|
|||
local placeholderJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local did_exit = false;
|
||||
|
||||
local irHeartbeatRequested = false;
|
||||
local irText = ''
|
||||
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
|
||||
|
||||
local grades = {
|
||||
{["max"] = 6900000, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)},
|
||||
{["max"] = 7900000, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)},
|
||||
{["max"] = 8600000, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)},
|
||||
{["max"] = 8900000, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)},
|
||||
{["max"] = 9200000, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)},
|
||||
{["max"] = 9400000, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)},
|
||||
{["max"] = 9600000, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)},
|
||||
{["max"] = 9700000, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)},
|
||||
{["max"] = 9800000, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)},
|
||||
{["max"] = 9900000, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)}
|
||||
{["max"] = 6999999, ["image"] = gfx.CreateSkinImage("common/grades/D.png", 0)},
|
||||
{["max"] = 7999999, ["image"] = gfx.CreateSkinImage("common/grades/C.png", 0)},
|
||||
{["max"] = 8699999, ["image"] = gfx.CreateSkinImage("common/grades/B.png", 0)},
|
||||
{["max"] = 8999999, ["image"] = gfx.CreateSkinImage("common/grades/A.png", 0)},
|
||||
{["max"] = 9299999, ["image"] = gfx.CreateSkinImage("common/grades/A+.png", 0)},
|
||||
{["max"] = 9499999, ["image"] = gfx.CreateSkinImage("common/grades/AA.png", 0)},
|
||||
{["max"] = 9699999, ["image"] = gfx.CreateSkinImage("common/grades/AA+.png", 0)},
|
||||
{["max"] = 9799999, ["image"] = gfx.CreateSkinImage("common/grades/AAA.png", 0)},
|
||||
{["max"] = 9899999, ["image"] = gfx.CreateSkinImage("common/grades/AAA+.png", 0)},
|
||||
{["max"] = 99999999, ["image"] = gfx.CreateSkinImage("common/grades/S.png", 0)}
|
||||
}
|
||||
|
||||
local badges = {
|
||||
|
@ -117,260 +76,9 @@ end
|
|||
|
||||
local SERVER = game.GetSkinSetting("multi.server")
|
||||
|
||||
local drawIdol = function(deltaTime)
|
||||
local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime);
|
||||
if idolAnimTickRes == 1 then
|
||||
gfx.GlobalAlpha(idolAnimTransitionScale);
|
||||
|
||||
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60;
|
||||
if (idolAnimTransitionScale > 1) then
|
||||
idolAnimTransitionScale = 1;
|
||||
end
|
||||
|
||||
gfx.ImageRect(0, 0, resX,resY, idolAnimation, 1, 0);
|
||||
gfx.GlobalAlpha(1);
|
||||
end
|
||||
end
|
||||
|
||||
songjacket = function() -- self explanatory
|
||||
|
||||
if portrait then
|
||||
split = resX
|
||||
jacket_size = math.min(resX/4, resY/4);
|
||||
song_x_off = 0;
|
||||
else
|
||||
split = resX/2
|
||||
jacket_size = math.min(resX/2, resY/2);
|
||||
song_x_off = 1/2*resX;
|
||||
end
|
||||
|
||||
local jw , jh = gfx.ImageSize(m_jacket);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(333, 1284, jw/1.18, jh/1.18, m_jacket,1,0);
|
||||
|
||||
end
|
||||
|
||||
m_own_info = function()
|
||||
|
||||
local x = 0
|
||||
local y = 1310
|
||||
gfx.BeginPath();
|
||||
gfx.FontSize(40)
|
||||
gfx.ImageRect(x, y, 343/1.18, 361/1.18,m_host_panel,1,0)
|
||||
gfx.Text(normname, x+20, y+78)
|
||||
|
||||
gfx.FontSize(22)
|
||||
gfx.Text(irText, x+5, y+288);
|
||||
|
||||
end
|
||||
|
||||
m_base_part = function() -- just the images which slide up
|
||||
local jw , jh = gfx.ImageSize(m_base_panel);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(1, 1250, jw/1.17, jh/1.18, m_base_panel,1,0);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(1, 1250, jw/1.17, jh/1.18, m_anim,1,0);
|
||||
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.FontSize(24)
|
||||
end
|
||||
|
||||
m_part = function() -- room name part
|
||||
local jw , jh = gfx.ImageSize(m_panel);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(429, 1142, jw/1.175, jh/1.18, m_panel,1,0);
|
||||
|
||||
gfx.FontSize(32);
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(selected_room.name, 575, 1179)
|
||||
end
|
||||
|
||||
m_s_part = function () -- song info panel
|
||||
local jw , jh = gfx.ImageSize(m_s_panel);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(283, 1187, jw/1.175, jh/1.18, m_s_panel,1,0);
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.FillColor(255,255,255)
|
||||
|
||||
gfx.FontSize(32)
|
||||
if selected_song == nil then
|
||||
if host == user_id then
|
||||
gfx.Text("NO SONG", 535, 1236)
|
||||
gfx.Text("NO ARIST", 535, 1275)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text("NO EFFECTOR", 746, 1378)
|
||||
gfx.Text("NO ILLUSTRATOR", 746, 1406)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(22)
|
||||
gfx.Text("BPM ?",780, 1307)
|
||||
else
|
||||
if missing_song then
|
||||
gfx.Text("MISSING SONG!!!!", 535, 1235)
|
||||
gfx.Text("MISSING ARIST!!!!", 535, 1275)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text("MISSING EFFECTOR!!!!", 744, 1378)
|
||||
gfx.Text("MISSING ILLUSTRATOR!!!!", 744, 1406)
|
||||
else
|
||||
gfx.Text("HOST IS SELECTING SONG", 535, 1235)
|
||||
gfx.Text(" ", 535, 1275)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text(" ", 746, 1378)
|
||||
gfx.Text(" ", 746, 1406)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(22)
|
||||
gfx.Text("BPM ?",780, 1307)
|
||||
end
|
||||
end
|
||||
else
|
||||
if selected_song.min_bpm ~= selected_song.max_bpm then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(22);
|
||||
gfx.Text("BPM",777, 1307)
|
||||
gfx.FontSize(26);
|
||||
gfx.Text(string.format("%.0f - %.0f",
|
||||
selected_song.min_bpm, selected_song.max_bpm),
|
||||
780 + 77, 1307)
|
||||
else
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(22);
|
||||
gfx.Text("BPM",777, 1307)
|
||||
gfx.Text(string.format("%.0f",
|
||||
selected_song.min_bpm),
|
||||
780 + 77, 1307)
|
||||
end
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.FontSize(32);
|
||||
gfx.Text(selected_song.title, 535, 1236)
|
||||
gfx.Text(selected_song.artist, 535, 1275)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text(selected_song.effector, 746, 1377)
|
||||
gfx.Text(selected_song.illustrator, 746, 1406)
|
||||
end
|
||||
end
|
||||
|
||||
m_bpm_part = function () -- bpm and lane speed
|
||||
local jw , jh = gfx.ImageSize(m_bpm_panel);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(0, 1692, jw/1.18, jh/1.18, m_bpm_panel,1,0);
|
||||
|
||||
gfx.FontSize(32)
|
||||
if selected_song == nil then
|
||||
if host == user_id then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(32)
|
||||
gfx.Text("BPM ?",76, 1788)
|
||||
gfx.Text("LANE-SPEED ?",76, 1832)
|
||||
else
|
||||
if missing_song then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(32)
|
||||
gfx.Text("BPM ?",76, 1788)
|
||||
gfx.Text("LANE-SPEED ?",76, 1832)
|
||||
else
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(32)
|
||||
gfx.Text("BPM ?",76, 1788)
|
||||
gfx.Text("LANE-SPEED ?",76, 1832)
|
||||
end
|
||||
end
|
||||
end
|
||||
if selected_song ~= nil then
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(32);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
|
||||
if selected_song.min_bpm ~= selected_song.max_bpm then
|
||||
|
||||
gfx.Text("BPM",76, 1788)
|
||||
gfx.Text(string.format("%.0f - %.0f",
|
||||
selected_song.min_bpm, selected_song.max_bpm),
|
||||
76 + 75, 1788)
|
||||
|
||||
gfx.Text("LANE-SPEED",76, 1832)
|
||||
gfx.Text(string.format("%.2f = %.0f",
|
||||
selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed),
|
||||
76 + 175, 1832)
|
||||
else
|
||||
|
||||
gfx.FontSize(32);
|
||||
gfx.Text("BPM",76, 1788)
|
||||
gfx.Text(string.format("%.0f",
|
||||
selected_song.min_bpm),
|
||||
76 + 75, 1788)
|
||||
|
||||
gfx.Text("LANE-SPEED",76, 1832)
|
||||
gfx.Text(string.format("%.2f = %.0f",
|
||||
selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed),
|
||||
76 + 175, 1832)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
m_info_part = function () -- the info panel
|
||||
local jw , jh = gfx.ImageSize(m_info_panel);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(475, 1590, jw/1.18, jh/1.18, m_info_panel,1,0);
|
||||
|
||||
local check = 770
|
||||
|
||||
draw_checkbox("Excessive", check - 200, 1625, toggle_hard, hard_mode, not start_game_soon)
|
||||
draw_checkbox("Mirror Mode",check - 15, 1625, toggle_mirror, mirror_mode, not start_game_soon)
|
||||
draw_checkbox("Rotate Host",check + 175, 1625, toggle_rotate, do_rotate,
|
||||
(owner == user_id or host == user_id) and not start_game_soon)
|
||||
|
||||
for i, user in ipairs(lobby_users) do
|
||||
|
||||
buttonY = 1775
|
||||
|
||||
local side_button_off = 0
|
||||
if owner == user_id and user.id ~= user_id then
|
||||
draw_button("K",525+side_button_off, buttonY, 50, function()
|
||||
kick_user(user);
|
||||
end)
|
||||
side_button_off = 60;
|
||||
end
|
||||
if (owner == user_id or host == user_id) and user.id ~= host then
|
||||
draw_button("H",525+side_button_off, buttonY+85, 50, function()
|
||||
change_host(user);
|
||||
end)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
user_setup = function () -- (semi new) user layering
|
||||
local distance = 350
|
||||
for i, user in ipairs(lobby_users) do
|
||||
|
||||
|
||||
if i == 1 then
|
||||
draw_user(user, -distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5)
|
||||
elseif i == 2 then
|
||||
draw_user(user, 16, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5)
|
||||
elseif i == 3 then
|
||||
draw_user(user, 16+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5)
|
||||
elseif i == 4 then
|
||||
draw_user(user, 16+distance+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5)
|
||||
elseif i > 4 then
|
||||
draw_user(user, 16+distance+distance+distance+distance, 1142-360/1.2, 420/1.2, 330/1.2, i,215,245.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function drawHeader()
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0, 0, 0, BAR_ALPHA);
|
||||
gfx.Rect(0, 0, desw, HEADER_HEIGHT);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath()
|
||||
|
||||
gfx.ImageRect(desw/2 - 200, HEADER_HEIGHT/2 - 20, 400, 40, headerMatchingImage, 1, 0)
|
||||
end
|
||||
|
||||
mouse_clipped = function(x,y,w,h)
|
||||
return mposx > x and mposy > y and mposx < x+w and mposy < y+h;
|
||||
|
@ -437,6 +145,8 @@ draw_button_color = function(name, x, y, buttonWidth, hoverindex,r,g,b, olr,olg,
|
|||
end;
|
||||
|
||||
draw_checkbox = function(text, x, y, hoverindex, current, can_click)
|
||||
local rx = x - (buttonWidth / 2);
|
||||
local ty = y - (buttonHeight / 2);
|
||||
gfx.BeginPath();
|
||||
|
||||
if can_click then
|
||||
|
@ -446,7 +156,6 @@ draw_checkbox = function(text, x, y, hoverindex, current, can_click)
|
|||
end
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.FontSize(35);
|
||||
gfx.FillColor(201,0,0);
|
||||
gfx.Text(text, x, y)
|
||||
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(x, y, text);
|
||||
|
@ -454,55 +163,121 @@ draw_checkbox = function(text, x, y, hoverindex, current, can_click)
|
|||
local sx = xmin - 40;
|
||||
local sy = y - 15;
|
||||
|
||||
if can_click and mouse_clipped(xmin-10, ymin, xmax-xmin, ymax-ymin) then
|
||||
gfx.StrokeColor(0,128,255);
|
||||
if can_click and (mouse_clipped(sx, sy, 31, 30) or mouse_clipped(xmin-10, ymin, xmax-xmin, ymax-ymin)) then
|
||||
hovered = hoverindex;
|
||||
gfx.StrokeColor(255,128,0);
|
||||
end
|
||||
|
||||
gfx.Rect(sx, y - 15, 30, 30)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
|
||||
if current then
|
||||
-- Draw checkmark
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0, 236, 0);
|
||||
gfx.Text(text, x, y)
|
||||
gfx.Fill();
|
||||
gfx.MoveTo(sx+5, sy+10);
|
||||
gfx.LineTo(sx+15, y+5);
|
||||
gfx.LineTo(sx+35, y-15);
|
||||
gfx.StrokeWidth(5)
|
||||
gfx.StrokeColor(0,255,0);
|
||||
gfx.Stroke()
|
||||
|
||||
end
|
||||
end;
|
||||
|
||||
--look into user changing -- IMPORTANT !!!
|
||||
draw_user = function(user, x, y , w, h, rank, breadx,bready)
|
||||
local name = user.name
|
||||
local showthing = false
|
||||
local userHeight = 100
|
||||
|
||||
draw_user = function(user, x, y, buttonWidth, rank)
|
||||
local buttonHeight = userHeight;
|
||||
local rx = x - (buttonWidth / 2);
|
||||
local ty = y - (buttonHeight / 2);
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(256,128,255);
|
||||
|
||||
gfx.Rect(rx - buttonBorder,
|
||||
ty - buttonBorder,
|
||||
buttonWidth + (buttonBorder * 2),
|
||||
buttonHeight + (buttonBorder * 2));
|
||||
gfx.Fill();
|
||||
gfx.BeginPath();
|
||||
if host == user.id then
|
||||
gfx.FillColor(80,0,0);
|
||||
else
|
||||
gfx.FillColor(0,0,40);
|
||||
end
|
||||
gfx.Rect(rx, ty, buttonWidth, buttonHeight);
|
||||
gfx.Fill();
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(255,255,255);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.FontSize(40);
|
||||
local name = user.name;
|
||||
if user.id == user_id then
|
||||
name = name
|
||||
end
|
||||
if user.id == host then
|
||||
name = name
|
||||
name = name..' (host)'
|
||||
elseif user.missing_map then
|
||||
name = name..' (NO CHART)'
|
||||
elseif user.ready then
|
||||
showthing = true
|
||||
elseif not user.ready then
|
||||
showthing = false
|
||||
end
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(42)
|
||||
|
||||
|
||||
gfx.FontSize(30)
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x, y ,w, h,m_4pb_panels,1,0)
|
||||
gfx.Text(name, x+135, y+51)
|
||||
|
||||
|
||||
if showthing == true then
|
||||
local jw , jh = gfx.ImageSize(ready_bt);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x+breadx, y+bready, jw/1.18, jh/1.18, ready_bt,1,0);
|
||||
elseif showthing == false then
|
||||
local jw , jh = gfx.ImageSize(ready_bt);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x+breadx, y+bready, jw/1.18, jh/1.18, ready_bt,0,0);
|
||||
name = name..' (ready)'
|
||||
end
|
||||
if user.score ~= nil then
|
||||
name = '#'..rank..' '..name
|
||||
end
|
||||
first_y = y - 28
|
||||
second_y = y + 28
|
||||
gfx.Text(name, x - buttonWidth/2 + 5, first_y);
|
||||
if user.score ~= nil then
|
||||
gfx.FillColor(255,255,0)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
local combo_text = ' '..user.combo..'x'
|
||||
gfx.Text(combo_text, x+buttonWidth/2 - 5, second_y-5);
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(x+buttonWidth/2 - 5, second_y-5, combo_text);
|
||||
|
||||
|
||||
local score_text = ' '..string.format("%08d",user.score);
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(score_text, xmin, second_y-5);
|
||||
xmin,ymin,xmax,ymax = gfx.TextBounds(xmin, second_y-5, score_text);
|
||||
|
||||
if user.grade == nil then
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > user.score then
|
||||
user.grade = v.image
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if user.badge == nil and user.clear > 1 then
|
||||
user.badge = badges[user.clear];
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
local iw, ih = gfx.ImageSize(user.grade)
|
||||
local iar = iw/ih
|
||||
local grade_height = buttonHeight/2 - 10
|
||||
gfx.ImageRect(xmin - iar * grade_height, second_y - buttonHeight/4 , iar * grade_height, grade_height, user.grade, 1, 0)
|
||||
end
|
||||
if user.level ~= 0 then
|
||||
gfx.FillColor(255,255,0)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
local level_text = 'Lvl '..user.level..' '
|
||||
gfx.Text(level_text, x-buttonWidth/2 + 5, second_y-5)
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(x-buttonWidth/2 + 5, second_y-5, level_text);
|
||||
|
||||
|
||||
if user.badge then
|
||||
gfx.BeginPath()
|
||||
local iw, ih = gfx.ImageSize(user.badge)
|
||||
local iar = iw/ih;
|
||||
local badge_height = buttonHeight/2 - 10
|
||||
gfx.ImageRect(xmax+5, second_y - buttonHeight/4 , iar * badge_height, badge_height, user.badge, 1, 0)
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end;
|
||||
|
||||
function render_loading()
|
||||
|
@ -525,38 +300,89 @@ function render_loading()
|
|||
end
|
||||
|
||||
function render_info()
|
||||
|
||||
if searchStatus then
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, resY)
|
||||
gfx.LineTo(550, resY)
|
||||
gfx.LineTo(500, resY - 65)
|
||||
gfx.LineTo(0, resY - 65)
|
||||
gfx.ClosePath()
|
||||
gfx.FillColor(33,33,33)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.FontSize(70)
|
||||
gfx.Text("Multiplayer", 3, resY - 15)
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Multiplayer")
|
||||
gfx.FontSize(20)
|
||||
gfx.Text(MULTIPLAYER_VERSION, xmax + 13, resY - 15)
|
||||
--gfx.Text('Server: '..'', xmax + 13, resY - 15)
|
||||
draw_button_color("Settings", 500-60, resY-25, 120, function()
|
||||
mpScreen.OpenSettings();
|
||||
end, 33,33,33, 33,33,33)
|
||||
gfx.Restore()
|
||||
|
||||
if searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(searchStatus, 3, 3)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
draw_diff_icon = function(diff, x, y, w, h)
|
||||
difbar.render(deltaTime, x, y, 1, diff.difficulty, diff.level);
|
||||
draw_diff_icon = function(diff, x, y, w, h, selected)
|
||||
local shrinkX = w/4
|
||||
local shrinkY = h/4
|
||||
if selected then
|
||||
gfx.FontSize(h/2)
|
||||
shrinkX = w/6
|
||||
shrinkY = h/6
|
||||
else
|
||||
gfx.FontSize(math.floor(h / 3))
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRectVarying(x+shrinkX,y+shrinkY,w-shrinkX*2,h-shrinkY*2,0,0,0,0)
|
||||
gfx.FillColor(15,15,15)
|
||||
gfx.StrokeColor(table.unpack(diffColors[diff.difficulty + 1]))
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
|
||||
gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2))
|
||||
end
|
||||
|
||||
local doffset = 0;
|
||||
local timer = 0;
|
||||
|
||||
local possy = 1095;
|
||||
local possx = 150;
|
||||
draw_cursor = function(x,y,rotation,width)
|
||||
gfx.Save()
|
||||
gfx.BeginPath();
|
||||
gfx.Translate(x,y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(4)
|
||||
gfx.Rect(-width/2, -width/2, width, width)
|
||||
gfx.Stroke()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
draw_diffs = function(diffs, x, y, w, h, selectedDiff)
|
||||
local diffWidth = w/2
|
||||
local diffHeight = w/2
|
||||
local diffWidth = w/2.5
|
||||
local diffHeight = w/2.5
|
||||
local diffCount = #diffs
|
||||
gfx.Scissor(x+84 + possx,y + possy,w/2.451,h)
|
||||
gfx.Scissor(x,y,w,h)
|
||||
for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do
|
||||
local diff = diffs[i]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos + possx, y + possy, diffWidth, diffHeight, false)
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -565,14 +391,21 @@ draw_diffs = function(diffs, x, y, w, h, selectedDiff)
|
|||
local diff = diffs[i]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos + possx, y + possy, diffWidth, diffHeight, false)
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
local diff = diffs[selectedDiff]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (doffset)*(-0.8*diffWidth))
|
||||
draw_diff_icon(diff, xpos + possx, y + possy, diffWidth, diffHeight, true)
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, true)
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,128,255)
|
||||
gfx.Rect(x,y+10,2,diffHeight-h/6)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+w-2,y+10,2,diffHeight-h/6)
|
||||
gfx.Fill()
|
||||
gfx.ResetScissor()
|
||||
|
||||
draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5)
|
||||
end
|
||||
|
||||
set_diff = function(oldDiff, newDiff)
|
||||
|
@ -617,7 +450,7 @@ function draw_rooms(y, h)
|
|||
local ypos = y + (h/2) - offsetY;
|
||||
local status = room.current..'/'..room.max
|
||||
if room.ingame then
|
||||
status = status..' (IN MATCH)'
|
||||
status = status..' (In Game)'
|
||||
end
|
||||
if room.password then
|
||||
status = status..' <P>'
|
||||
|
@ -654,51 +487,75 @@ change_selected_room = function(off)
|
|||
selected_room_index = new_index;
|
||||
end
|
||||
|
||||
local IR_HeartbeatResponse = function(res)
|
||||
if res.statusCode == IRData.States.Success then
|
||||
irText = res.body.serverName .. ' ' .. res.body.irVersion;
|
||||
else
|
||||
game.Log("Can't connect to IR!", game.LOGGER_WARNING)
|
||||
end
|
||||
end
|
||||
|
||||
local IR_Handle = function()
|
||||
if not irHeartbeatRequested then
|
||||
IR.Heartbeat(IR_HeartbeatResponse)
|
||||
irHeartbeatRequested = true;
|
||||
end
|
||||
end
|
||||
|
||||
function render_lobby(deltaTime)
|
||||
|
||||
local jw , jh = gfx.ImageSize(bg);
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(0, 0, resX, resY, bg,1,0);
|
||||
drawIdol(deltaTime)
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(0, 0, resX, resY, bg_graid1,1,0);
|
||||
gfx.ImageRect(0, 0, resX, resY, bg_graid2,1,0);
|
||||
drawHeader()
|
||||
gfx.BeginPath();
|
||||
m_base_part()
|
||||
local jacket_size;
|
||||
|
||||
m_own_info()
|
||||
user_setup()
|
||||
-- split is how the screen is split or not
|
||||
if portrait then
|
||||
split = resX
|
||||
jacket_size = math.min(resX/3, resY/3);
|
||||
user_y_off = 375+jacket_size + 70
|
||||
song_x_off = 0;
|
||||
else
|
||||
split = resX/2
|
||||
jacket_size = math.min(resX/2, resY/2);
|
||||
user_y_off = 0
|
||||
song_x_off = 1/2*resX;
|
||||
end
|
||||
|
||||
m_info_part()
|
||||
m_part()
|
||||
m_s_part()
|
||||
m_bpm_part()
|
||||
songjacket()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.FontSize(70)
|
||||
gfx.Text(selected_room.name, resX/2, 50)
|
||||
|
||||
|
||||
-- === Users ===
|
||||
|
||||
gfx.Text("Users", split/2, user_y_off+100)
|
||||
|
||||
buttonY = user_y_off + 125 + userHeight/2
|
||||
for i, user in ipairs(lobby_users) do
|
||||
draw_user(user, split/2, buttonY, split*3/4, i)
|
||||
local side_button_off = 0
|
||||
if owner == user_id and user.id ~= user_id then
|
||||
draw_button("K",split/2 + split*3/8+10+25, buttonY, 50, function()
|
||||
kick_user(user);
|
||||
end)
|
||||
side_button_off = 60;
|
||||
end
|
||||
if (owner == user_id or host == user_id) and user.id ~= host then
|
||||
draw_button("H",split/2 + split*3/8+10+25+side_button_off, buttonY, 50, function()
|
||||
change_host(user);
|
||||
end)
|
||||
end
|
||||
buttonY = buttonY + userHeight
|
||||
end
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.FillColor(255,255,255)
|
||||
|
||||
|
||||
-- === song select ===
|
||||
|
||||
gfx.FontSize(60)
|
||||
gfx.Text("Selected Song:", split/2 + song_x_off, 100)
|
||||
gfx.FontSize(40)
|
||||
if selected_song == nil then
|
||||
if host == user_id then
|
||||
gfx.Text("Select song:", split/2 + song_x_off, 175)
|
||||
else
|
||||
if missing_song then
|
||||
gfx.Text("Missing song!!!!", split/2 + song_x_off, 175)
|
||||
else
|
||||
gfx.Text("Host is selecting song", split/2 + song_x_off, 175)
|
||||
end
|
||||
end
|
||||
if jacket == 0 then
|
||||
jacket = placeholderJacket
|
||||
end
|
||||
else
|
||||
gfx.Text(selected_song.title, split/2 + song_x_off, 175)
|
||||
draw_diffs(selected_song.all_difficulties, split/2 + song_x_off - 150, 200, 300, 100, selected_song.diff_index+1)
|
||||
|
||||
|
||||
if selected_song.jacket == nil or selected_song.jacket == placeholderJacket then
|
||||
selected_song.jacket = gfx.LoadImageJob(selected_song.jacketPath, placeholderJacket)
|
||||
jacket = selected_song.jacket
|
||||
|
@ -706,22 +563,75 @@ function render_lobby(deltaTime)
|
|||
end
|
||||
gfx.Save()
|
||||
gfx.BeginPath()
|
||||
gfx.Translate(481, 1303+jacket_size/2)
|
||||
gfx.Translate(split/2 + song_x_off, 325+jacket_size/2)
|
||||
gfx.ImageRect(-jacket_size/2,-jacket_size/2,jacket_size,jacket_size,jacket,1,0)
|
||||
|
||||
if mouse_clipped(481-jacket_size/2,1423+-jacket_size/2,jacket_size,jacket_size) and host == user_id then
|
||||
|
||||
if mouse_clipped(split/2 + song_x_off-jacket_size/2, 325, jacket_size,jacket_size) and host == user_id then
|
||||
hovered = function()
|
||||
missing_song = false
|
||||
mpScreen.SelectSong()
|
||||
end
|
||||
end
|
||||
gfx.Restore()
|
||||
if start_game_soon then
|
||||
draw_button("Game starting...", split/2 + song_x_off, 375+jacket_size, 600, function() end);
|
||||
else
|
||||
if host == user_id then
|
||||
if selected_song == nil or not selected_song.self_picked then
|
||||
draw_button_color("Select song", split/2 + song_x_off, 375+jacket_size, 600, function()
|
||||
missing_song = false
|
||||
mpScreen.SelectSong()
|
||||
end, 0, math.min(255, 128 + math.floor(32 * math.cos(timer * math.pi))), 0, 0,128,255);
|
||||
elseif user_ready and all_ready then
|
||||
draw_button("Start game", split/2 + song_x_off, 375+jacket_size, 600, start_game)
|
||||
elseif user_ready and not all_ready then
|
||||
draw_button("Waiting for others", split/2 + song_x_off, 375+jacket_size, 600, function()
|
||||
missing_song = false
|
||||
mpScreen.SelectSong()
|
||||
end)
|
||||
else
|
||||
draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up);
|
||||
end
|
||||
elseif host == nil then
|
||||
draw_button("Waiting for game to end", split/2 + song_x_off, 375+jacket_size, 600, function() end);
|
||||
elseif missing_song then
|
||||
draw_button("Missing Song!", split/2 + song_x_off, 375+jacket_size, 600, function() end);
|
||||
elseif selected_song ~= nil then
|
||||
if user_ready then
|
||||
draw_button("Cancel", split/2 + song_x_off, 375+jacket_size, 600, ready_up);
|
||||
else
|
||||
draw_button("Ready", split/2 + song_x_off, 375+jacket_size, 600, ready_up);
|
||||
end
|
||||
else
|
||||
draw_button("Waiting for host", split/2 + song_x_off, 375+jacket_size, 600, function() end);
|
||||
end
|
||||
end
|
||||
|
||||
draw_checkbox("Excessive", split/2 + song_x_off - 150, 375+jacket_size + 70, toggle_hard, hard_mode, not start_game_soon)
|
||||
draw_checkbox("Mirror", split/2 + song_x_off, 375+jacket_size + 70, toggle_mirror, mirror_mode, not start_game_soon)
|
||||
|
||||
draw_checkbox("Rotate Host", split/2 + song_x_off + 150 + 20, 375+jacket_size + 70, toggle_rotate, do_rotate,
|
||||
(owner == user_id or host == user_id) and not start_game_soon)
|
||||
|
||||
if selected_song ~= nil then
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP)
|
||||
if selected_song.min_bpm ~= selected_song.max_bpm then
|
||||
gfx.Text(string.format("BPM: %.0f-%.0f, Start BPM: %.0f, Hispeed: %.0f x %.1f = %.0f",
|
||||
selected_song.min_bpm, selected_song.max_bpm, selected_song.start_bpm,
|
||||
selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed),
|
||||
split/2 + song_x_off, 375+jacket_size + 70 + 30)
|
||||
else
|
||||
gfx.Text(string.format("BPM: %.0f, Hispeed: %.0f x %.1f = %.0f",
|
||||
selected_song.min_bpm,
|
||||
selected_song.speed_bpm, selected_song.hispeed, selected_song.speed_bpm * selected_song.hispeed),
|
||||
split/2 + song_x_off, 375+jacket_size + 70 + 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function render_room_list(deltaTime)
|
||||
|
||||
spinnybg.draw(deltaTime);
|
||||
|
||||
draw_rooms(175, resY - 290);
|
||||
|
||||
-- Draw cover for rooms out of view
|
||||
|
@ -789,18 +699,19 @@ function render_new_room_password(delta_time)
|
|||
gfx.Text(string.rep("*",#textInput.text), resX/2, resY/2+40)
|
||||
draw_button("Create Room", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep);
|
||||
end
|
||||
--here
|
||||
|
||||
function render_new_room_name(deltaTime)
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("segoeui.ttf")
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.FontSize(70)
|
||||
gfx.Text("Create New Room", resX/2, resY/4)
|
||||
|
||||
gfx.FillColor(50,50,50)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0, resY/2-10, resX, 60)
|
||||
gfx.Fill();
|
||||
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text("Please enter room name:", resX/2, resY/2-40)
|
||||
gfx.Text(textInput.text, resX/2, resY/2+40)
|
||||
draw_button("Next", resX/2, resY*3/4, resX/2, mpScreen.NewRoomStep);
|
||||
|
@ -833,8 +744,7 @@ render = function(deltaTime)
|
|||
mposx,mposy = game.GetMousePos();
|
||||
portrait = resY > resX
|
||||
|
||||
IR_Handle()
|
||||
Sound.stopMusic();
|
||||
sound.stopMusic();
|
||||
|
||||
doffset = doffset * 0.9
|
||||
ioffset = ioffset * 0.9
|
||||
|
@ -927,11 +837,10 @@ function join_room(room)
|
|||
mpScreen.JoinWithoutPassword(room.id)
|
||||
end
|
||||
end
|
||||
local que1 = 0
|
||||
|
||||
-- Handle button presses to advance the UI
|
||||
button_pressed = function(button)
|
||||
|
||||
if button == game.BUTTON_FXL and game.BUTTON_FXR then
|
||||
if button == game.BUTTON_STA then
|
||||
if start_game_soon then
|
||||
return
|
||||
end
|
||||
|
@ -961,10 +870,10 @@ button_pressed = function(button)
|
|||
end
|
||||
end
|
||||
|
||||
if button == game.BUTTON_BTA then
|
||||
if button == game.BUTTON_FXL then
|
||||
toggle_hard();
|
||||
end
|
||||
if button == game.BUTTON_BTB then
|
||||
if button == game.BUTTON_FXR then
|
||||
toggle_mirror();
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,9 @@ local Numbers = require('components.numbers')
|
|||
local DiffRectangle = require('components.diff_rectangle');
|
||||
local lang = require("language.call")
|
||||
|
||||
local creww = game.GetSkinSetting("single_idol")
|
||||
require('common.gameconfig')
|
||||
|
||||
local crew = game.GetSkinSetting("single_idol")
|
||||
|
||||
local VolforceWindow = require('components.volforceWindow')
|
||||
|
||||
|
@ -46,6 +48,8 @@ local defaultJacketImage = gfx.CreateSkinImage("result/default_jacket.png", 0);
|
|||
|
||||
local bestScoreBadgeImage = gfx.CreateSkinImage("result/best.png", 0);
|
||||
|
||||
local defaultCardImage = gfx.CreateSkinImage("result/default_appeal_card.png", 0)
|
||||
local defaultBadgeImage = gfx.CreateSkinImage("result/default_dan.png", 0)
|
||||
local appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0);
|
||||
local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0);
|
||||
|
||||
|
@ -99,7 +103,8 @@ local difficultyLabelImages = {
|
|||
gfx.CreateSkinImage("diff/5 infinite.png", 0),
|
||||
gfx.CreateSkinImage("diff/6 gravity.png", 0),
|
||||
gfx.CreateSkinImage("diff/7 heavenly.png", 0),
|
||||
gfx.CreateSkinImage("diff/8 vivid.png", 0)
|
||||
gfx.CreateSkinImage("diff/8 vivid.png", 0),
|
||||
gfx.CreateSkinImage("diff/9 exceed.png", 0)
|
||||
}
|
||||
|
||||
local clearBadgeImages = {
|
||||
|
@ -134,7 +139,11 @@ local clearBadgeImages = {
|
|||
}
|
||||
|
||||
-- ANIMS
|
||||
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true);
|
||||
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..crew, 1 / 30, 0, true);
|
||||
|
||||
if not idolAnimation then
|
||||
game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING)
|
||||
end
|
||||
|
||||
local transitionEnterScale = 0;
|
||||
local idolAnimTransitionScale = 0;
|
||||
|
@ -159,7 +168,9 @@ local BOTTOM_PANEL_TRANSTION_ENTER_OFFSET = 256;
|
|||
|
||||
local highScore;
|
||||
|
||||
local username = game.GetSkinSetting('username');
|
||||
-- gameplay table does not have a current username field, because why would it lmao
|
||||
-- workaround: retrieve it directly from Main.cfg file
|
||||
local username = GameConfig["MultiplayerUsername"] or game.GetSkinSetting('username') or '';
|
||||
local msg = game.GetSkinSetting("MSG");
|
||||
|
||||
local earlyLateBarsStats = {
|
||||
|
@ -262,17 +273,19 @@ function drawTimingBar(y, value, max, type)
|
|||
end
|
||||
|
||||
local drawIdol = function(deltaTime)
|
||||
local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime);
|
||||
if idolAnimTickRes == 1 then
|
||||
gfx.GlobalAlpha(idolAnimTransitionScale);
|
||||
|
||||
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60;
|
||||
if (idolAnimTransitionScale > 1) then
|
||||
idolAnimTransitionScale = 1;
|
||||
if idolAnimation then
|
||||
local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime);
|
||||
if idolAnimTickRes == 1 then
|
||||
gfx.GlobalAlpha(idolAnimTransitionScale);
|
||||
|
||||
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60;
|
||||
if (GameConfig["AutoScoreScreenshot"] or idolAnimTransitionScale > 1) then
|
||||
idolAnimTransitionScale = 1;
|
||||
end
|
||||
|
||||
gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0);
|
||||
gfx.GlobalAlpha(1);
|
||||
end
|
||||
|
||||
gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0);
|
||||
gfx.GlobalAlpha(1);
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -536,25 +549,27 @@ local drawBottomPanelContent = function(deltatime)
|
|||
-- Draw appeal card
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(bottomPanelX + 58, bottomPanelY + 277, 103, 132,
|
||||
appealCardImage, 1, 0);
|
||||
result.isSelf and appealCardImage or defaultCardImage, 1, 0);
|
||||
|
||||
-- Draw description
|
||||
gfx.FontSize(22)
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(msg, bottomPanelX + 190, bottomPanelY + 282);
|
||||
gfx.Text(result.isSelf and msg or ("Player "..result.displayIndex), bottomPanelX + 190, bottomPanelY + 282);
|
||||
|
||||
-- Draw username
|
||||
gfx.FontSize(28)
|
||||
gfx.Text(username, bottomPanelX + 190, bottomPanelY + 314);
|
||||
gfx.Text(result.playerName or username, bottomPanelX + 190, bottomPanelY + 314);
|
||||
|
||||
-- Draw dan badge
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(bottomPanelX + 187, bottomPanelY + 362, 107, 29,
|
||||
danBadgeImage, 1, 0);
|
||||
result.isSelf and danBadgeImage or defaultBadgeImage, 1, 0);
|
||||
|
||||
-- Draw volforce
|
||||
VolforceWindow.render(0, bottomPanelX + 310, bottomPanelY + 355)
|
||||
if result.isSelf then
|
||||
VolforceWindow.render(0, bottomPanelX + 310, bottomPanelY + 355)
|
||||
end
|
||||
|
||||
-- Draw IR text
|
||||
gfx.FontSize(22)
|
||||
|
@ -631,7 +646,7 @@ end
|
|||
|
||||
local tickTransitions = function(deltaTime)
|
||||
|
||||
if transitionEnterScale < 1 then
|
||||
if not GameConfig["AutoScoreScreenshot"] and transitionEnterScale < 1 then
|
||||
transitionEnterScale = transitionEnterScale + deltaTime / 0.66 -- transition should last for that time in seconds
|
||||
else
|
||||
transitionEnterScale = 1
|
||||
|
@ -648,7 +663,7 @@ local tickTransitions = function(deltaTime)
|
|||
(1 - Easing.outQuad(transitionEnterScale)))
|
||||
|
||||
|
||||
if badgeLinesAnimScale < 1 then
|
||||
if not GameConfig["AutoScoreScreenshot"] and badgeLinesAnimScale < 1 then
|
||||
badgeLinesAnimScale = badgeLinesAnimScale + deltaTime / 0.5 -- transition should last for that time in seconds
|
||||
else
|
||||
badgeLinesAnimScale = 0
|
||||
|
@ -823,4 +838,4 @@ end
|
|||
|
||||
screenshot_captured = function(path)
|
||||
game.PlaySample("shutter")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
local Easing = require('common.easing')
|
||||
local Dim = require("common.dimensions")
|
||||
local SongSelectHeader = require('components.headers.songSelectHeader')
|
||||
local Footer = require('components.footer')
|
||||
|
||||
local defaultFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/bg.png', 0)
|
||||
local collectionFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/col_bg.png', 0)
|
||||
|
@ -22,7 +21,8 @@ local ITEM_HEIGHT = 172
|
|||
local specialFolders = {
|
||||
{
|
||||
keys = {
|
||||
'SOUND VOLTEX BOOTH', 'SDVX BOOTH', 'SOUND VOLTEX I', 'SDVX I',
|
||||
'SOUND VOLTEX BOOTH', 'SOUND VOLTEX I BOOTH', 'SDVX BOOTH',
|
||||
'SOUND VOLTEX I', 'SDVX I',
|
||||
'SOUND VOLTEX 1', 'SDVX 1', 'SDVX I BOOTH'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
|
@ -30,7 +30,9 @@ local specialFolders = {
|
|||
}, {
|
||||
keys = {
|
||||
'SOUND VOLTEX INFINITE INFECTION', 'SDVX INFINITE INFECTION',
|
||||
'SOUND VOLTEX II', 'SDVX II', 'SOUND VOLTEX 2', 'SDVX 2', 'SDVX II INFINITE INFECTION'
|
||||
'SOUND VOLTEX II INFINITE INFECTION',
|
||||
'SOUND VOLTEX II', 'SDVX II', 'SOUND VOLTEX 2', 'SDVX 2',
|
||||
'SDVX II INFINITE INFECTION'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
'song_select/filter_wheel/special_folder_bgs/Infinite Infection.png',
|
||||
|
@ -41,16 +43,17 @@ local specialFolders = {
|
|||
'SOUND VOLTEX GRAVITY WARS',
|
||||
'SDVX GRAVITY WARS',
|
||||
'SOUND VOLTEX III',
|
||||
'SOUND VOLTEX III GRAVITY WARS',
|
||||
'SDVX III',
|
||||
'SOUND VOLTEX 3',
|
||||
'SDVX 3',
|
||||
'SDVX III GRAVITY WARS',
|
||||
'SDVX III GRAVITY WARS'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage('song_select/filter_wheel/special_folder_bgs/Gravity Wars.png', 0)
|
||||
},
|
||||
{
|
||||
keys = {
|
||||
'SOUND VOLTEX HEAVENLY HAVEN', 'SDVX HEAVENLY HAVEN',
|
||||
'SOUND VOLTEX HEAVENLY HAVEN', 'SDVX HEAVENLY HAVEN', 'SOUND VOLTEX IV HEAVENLY HAVEN',
|
||||
'SOUND VOLTEX IV', 'SDVX IV', 'SOUND VOLTEX 4', 'SDVX 4', 'SDVX IV HEAVENLY HAVEN'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
|
@ -58,26 +61,27 @@ local specialFolders = {
|
|||
}, {
|
||||
keys = {
|
||||
'SOUND VOLTEX VIVID WAVE', 'SDVX VIVID WAVE', 'SOUND VOLTEX V',
|
||||
'SDVX V', 'SOUND VOLTEX 5', 'SDVX 5', 'SDVX V VIVID WAVE'
|
||||
'SDVX V', 'SOUND VOLTEX 5', 'SDVX 5', 'SDVX V VIVID WAVE', 'SOUND VOLTEX V VIVID WAVE'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
'song_select/filter_wheel/special_folder_bgs/Vivid Wave.png', 0)
|
||||
}, {
|
||||
keys = {
|
||||
'SOUND VOLTEX EXCEED GEAR', 'SDVX EXCEED GEAR', 'SOUND VOLTEX VI',
|
||||
'SDVX VI', 'SOUND VOLTEX 6', 'SDVX 6', 'SDVX VI EXCEED GEAR'
|
||||
'SDVX VI', 'SOUND VOLTEX 6', 'SDVX 6', 'SDVX VI EXCEED GEAR',
|
||||
'SOUND VOLTEX VI EXCEED GEAR'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
'song_select/filter_wheel/special_folder_bgs/Exceed Gear.png', 0)
|
||||
}, {
|
||||
keys = {
|
||||
'NAUTICA',
|
||||
'NAUTICA', 'nautica'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
'song_select/filter_wheel/special_folder_bgs/Nautica.png', 0)
|
||||
}, {
|
||||
keys = {
|
||||
'KSHOOTMANIA', 'K-SHOOT MANIA', "K SHOOT MANIA"
|
||||
'KSHOOTMANIA', 'K-SHOOT MANIA', 'K SHOOT MANIA'
|
||||
},
|
||||
folderBg = gfx.CreateSkinImage(
|
||||
'song_select/filter_wheel/special_folder_bgs/KShootMania.png', 0)
|
||||
|
@ -366,7 +370,6 @@ local drawFilterWheel = function (deltaTime)
|
|||
|
||||
if (game.GetSkinSetting('_currentScreen') == 'songwheel') then
|
||||
SongSelectHeader.draw(deltaTime)
|
||||
Footer.draw(deltaTime)
|
||||
end
|
||||
|
||||
if (isFilterWheelActive ~= previousActiveState) then
|
||||
|
|
|
@ -6,9 +6,13 @@ local Wallpaper = require("components.wallpaper")
|
|||
local common = require('common.util')
|
||||
local Sound = require("common.sound")
|
||||
local Numbers = require('components.numbers')
|
||||
local Footer = require('components.footer')
|
||||
|
||||
local VolforceCalc = require('components.volforceCalc')
|
||||
|
||||
require("api.point2d")
|
||||
require("components.radar")
|
||||
|
||||
local dataPanelImage = gfx.CreateSkinImage("song_select/data_bg_overlay.png", 1)
|
||||
local dataGlowOverlayImage = gfx.CreateSkinImage("song_select/data_panel/data_glow_overlay.png", 1)
|
||||
local gradeBgImage = gfx.CreateSkinImage("song_select/data_panel/grade_bg.png", 1)
|
||||
|
@ -39,8 +43,6 @@ local searchInfoPanelImage = gfx.CreateSkinImage("song_select/search_info_panel.
|
|||
|
||||
local defaultJacketImage = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
|
||||
|
||||
|
||||
local difficultyLabelImages = {
|
||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/novice.png", 1),
|
||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/advanced.png", 1),
|
||||
|
@ -50,6 +52,7 @@ local difficultyLabelImages = {
|
|||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/gravity.png", 1),
|
||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/heavenly.png", 1),
|
||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/vivid.png", 1),
|
||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/exceed.png", 1),
|
||||
}
|
||||
|
||||
local badgeImages = {
|
||||
|
@ -103,6 +106,7 @@ local difficultyLabelUnderImages = {
|
|||
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/xcd.png", 0),
|
||||
}
|
||||
|
||||
game.LoadSkinSample('song_wheel/cursor_change.wav')
|
||||
|
@ -111,6 +115,8 @@ game.LoadSkinSample('song_wheel/diff_change.wav')
|
|||
local scoreNumbers = Numbers.load_number_image("score_num")
|
||||
local difficultyNumbers = Numbers.load_number_image("diff_num")
|
||||
|
||||
local showEffectRadar = game.GetSkinSetting("songselect_showEffectRadar") or false
|
||||
|
||||
local LEADERBOARD_PLACE_NAMES = {
|
||||
'1st',
|
||||
'2nd',
|
||||
|
@ -123,13 +129,16 @@ local songPlateHeight = 172
|
|||
local selectedIndex = 1
|
||||
local selectedDifficulty = 1
|
||||
|
||||
local radar = Radar.new(Point2D.new(0, 0))
|
||||
local updateRadar = true
|
||||
|
||||
local jacketCache = {}
|
||||
|
||||
local top50diffs = {}
|
||||
|
||||
local irRequestStatus = 1 -- 0=unused, 1=not requested, 2=loading, others are status codes
|
||||
local irRequestTimeout = 2
|
||||
local irLeaderboard = {}
|
||||
local irLeaderboard = {} ---@type ServerScore[]|{}
|
||||
local irLeaderboardsCache = {}
|
||||
|
||||
local transitionScrollScale = 0
|
||||
|
@ -156,6 +165,7 @@ local transitionSearchInfoEnterScale = 0
|
|||
local transitionSearchBackgroundAlpha = 0
|
||||
local transitionSearchbarOffsetY = 0
|
||||
local transitionSearchInfoOffsetY = 0
|
||||
local transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
|
||||
|
||||
local transitionLaserScale = 0
|
||||
local transitionLaserY = 0
|
||||
|
@ -193,32 +203,32 @@ local resolutionChange = function(x, y)
|
|||
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR)
|
||||
end
|
||||
|
||||
function getCorrectedIndex(from, offset)
|
||||
total = #songwheel.songs
|
||||
local function getCorrectedIndex(from, offset)
|
||||
local total = #songwheel.songs
|
||||
|
||||
if (math.abs(offset) > total) then
|
||||
if (offset < 0) then
|
||||
if (offset < 0) then
|
||||
offset = offset + total*math.floor(math.abs(offset)/total)
|
||||
else
|
||||
offset = offset - total*math.floor(math.abs(offset)/total)
|
||||
end
|
||||
end
|
||||
|
||||
index = from + offset
|
||||
local index = from + offset
|
||||
|
||||
if index < 1 then
|
||||
index = total + (from+offset) -- this only happens if the offset is negative
|
||||
end
|
||||
if index < 1 then
|
||||
index = total + (from+offset) -- this only happens if the offset is negative
|
||||
end
|
||||
|
||||
if index > total then
|
||||
indexesUntilEnd = total - from
|
||||
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
||||
end
|
||||
if index > total then
|
||||
local indexesUntilEnd = total - from
|
||||
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
||||
end
|
||||
|
||||
return index
|
||||
return index
|
||||
end
|
||||
|
||||
function getJacketImage(song)
|
||||
local function getJacketImage(song)
|
||||
if not jacketCache[song.id] or jacketCache[song.id]==defaultJacketImage then
|
||||
jacketCache[song.id] = gfx.LoadImageJob(song.difficulties[
|
||||
math.min(selectedDifficulty, #song.difficulties)
|
||||
|
@ -228,12 +238,12 @@ function getJacketImage(song)
|
|||
return jacketCache[song.id]
|
||||
end
|
||||
|
||||
function getGradeImageForScore(score)
|
||||
local function getGradeImageForScore(score)
|
||||
local gradeImage = gradeImages.none
|
||||
local bestGradeCutoff = 0
|
||||
for gradeName, scoreCutoff in pairs(gradeCutoffs) do
|
||||
if scoreCutoff <= score then
|
||||
if scoreCutoff > bestGradeCutoff then
|
||||
if scoreCutoff > bestGradeCutoff then
|
||||
gradeImage = gradeImages[gradeName]
|
||||
bestGradeCutoff = scoreCutoff
|
||||
end
|
||||
|
@ -243,31 +253,30 @@ function getGradeImageForScore(score)
|
|||
return gradeImage
|
||||
end
|
||||
|
||||
function drawLaserAnim()
|
||||
local function drawLaserAnim()
|
||||
gfx.Save()
|
||||
gfx.BeginPath()
|
||||
|
||||
|
||||
gfx.Scissor(0, transitionLaserY, desw, 100)
|
||||
|
||||
|
||||
gfx.ImageRect(0, 0, desw, desh, laserAnimBaseImage, 1, 0)
|
||||
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function drawBackground(deltaTime)
|
||||
local function drawBackground(deltaTime)
|
||||
Background.draw(deltaTime)
|
||||
|
||||
|
||||
local song = songwheel.songs[selectedIndex]
|
||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||
|
||||
if (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then
|
||||
-- If the score for song exists
|
||||
if song and diff then
|
||||
if song and diff then
|
||||
local jacketImage = getJacketImage(song)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(transitionJacketBgScrollPosX, 0, 900, 900, jacketImage or defaultJacketImage, transitionJacketBgScrollAlpha, 0)
|
||||
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64))
|
||||
gfx.Rect(0,0,900,900)
|
||||
|
@ -281,7 +290,7 @@ function drawBackground(deltaTime)
|
|||
|
||||
drawLaserAnim()
|
||||
|
||||
if song and diff and (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then
|
||||
if song and diff and (not isFilterWheelActive and transitionLeaveReappearTimer == 0) then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, desw, desh, dataGlowOverlayImage, transitionAfterscrollDataOverlayAlpha, 0)
|
||||
gfx.BeginPath()
|
||||
|
@ -298,7 +307,9 @@ function drawBackground(deltaTime)
|
|||
|
||||
end
|
||||
|
||||
function drawSong(song, y)
|
||||
---@param song SongWheelSong
|
||||
---@param y number
|
||||
local function drawSong(song, y)
|
||||
if (not song) then return end
|
||||
|
||||
local songX = desw/2+28
|
||||
|
@ -309,14 +320,14 @@ function drawSong(song, y)
|
|||
end
|
||||
|
||||
local bestScore
|
||||
if selectedSongDifficulty.scores then
|
||||
if selectedSongDifficulty.scores then
|
||||
bestScore = selectedSongDifficulty.scores[1]
|
||||
end
|
||||
|
||||
-- Draw the bg for the song plate
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(songX, y, 515, 172, songPlateBg, 1, 0)
|
||||
|
||||
|
||||
-- Draw jacket
|
||||
local jacketImage = getJacketImage(song)
|
||||
gfx.BeginPath()
|
||||
|
@ -325,7 +336,7 @@ function drawSong(song, y)
|
|||
-- Draw the overlay for the song plate (that bottom black bar)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(songX, y, 515, 172, songPlateBottomBarOverlayImage, 1, 0)
|
||||
|
||||
|
||||
-- Draw the difficulty notch background
|
||||
gfx.BeginPath()
|
||||
local diffIndex = Charting.GetDisplayDifficulty(selectedSongDifficulty.jacketPath, selectedSongDifficulty.difficulty)
|
||||
|
@ -345,7 +356,7 @@ function drawSong(song, y)
|
|||
if selectedSongDifficulty.topBadge then
|
||||
badgeImage = badgeImages[selectedSongDifficulty.topBadge+1]
|
||||
end
|
||||
|
||||
|
||||
local badgeAlpha = 1
|
||||
if (selectedSongDifficulty.topBadge >= 3) then
|
||||
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge
|
||||
|
@ -358,7 +369,7 @@ function drawSong(song, y)
|
|||
local gradeImage = gradeImages.none
|
||||
local gradeAlpha = 1
|
||||
|
||||
if bestScore then
|
||||
if bestScore then
|
||||
gradeImage = getGradeImageForScore(bestScore.score)
|
||||
|
||||
if (bestScore.score >= gradeCutoffs.S) then
|
||||
|
@ -370,13 +381,13 @@ function drawSong(song, y)
|
|||
gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0)
|
||||
|
||||
-- Draw top 50 label if applicable
|
||||
if (top50diffs[selectedSongDifficulty.id]) then
|
||||
if (top50diffs[selectedSongDifficulty.hash]) then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function drawSongList()
|
||||
local function drawSongList()
|
||||
gfx.GlobalAlpha(1-transitionLeaveScale)
|
||||
|
||||
local numOfSongsAround = 7 -- How many songs should be up and how many should be down of the selected one
|
||||
|
@ -389,7 +400,7 @@ function drawSongList()
|
|||
drawSong(songwheel.songs[songIndex], desh/2-songPlateHeight/2-songPlateHeight*i + yOffset)
|
||||
i=i+1
|
||||
end
|
||||
|
||||
|
||||
-- Draw the selected song
|
||||
drawSong(songwheel.songs[selectedIndex], desh/2-songPlateHeight/2 + yOffset)
|
||||
|
||||
|
@ -403,127 +414,8 @@ function drawSongList()
|
|||
gfx.GlobalAlpha(1)
|
||||
end
|
||||
|
||||
function drawData() -- Draws the song data on the left panel
|
||||
|
||||
if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end
|
||||
|
||||
local song = songwheel.songs[selectedIndex]
|
||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||
local bestScore = diff and diff.scores[1]
|
||||
|
||||
if not song then return false end
|
||||
|
||||
local jacketImage = getJacketImage(song)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0)
|
||||
|
||||
if (top50diffs[diff.id]) then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0)
|
||||
end
|
||||
|
||||
gfx.Save()
|
||||
-- Draw best score
|
||||
gfx.BeginPath()
|
||||
|
||||
local scoreNumber = 0
|
||||
if bestScore then
|
||||
scoreNumber = bestScore.score
|
||||
end
|
||||
|
||||
Numbers.draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12)
|
||||
Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12)
|
||||
|
||||
-- Draw grade
|
||||
local gradeImage = gradeImages.none
|
||||
local gradeAlpha = transitionAfterscrollGradeAlpha
|
||||
if bestScore then
|
||||
gradeImage = getGradeImageForScore(bestScore.score)
|
||||
|
||||
if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then
|
||||
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
||||
end
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0)
|
||||
|
||||
-- Draw badge
|
||||
badgeImage = badgeImages[diff.topBadge+1]
|
||||
|
||||
local badgeAlpha = transitionAfterscrollBadgeAlpha
|
||||
if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then
|
||||
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, badgeAlpha, 0)
|
||||
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw BPM
|
||||
gfx.BeginPath()
|
||||
gfx.FontSize(24)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Save()
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out
|
||||
gfx.Text(song.bpm, 85, 920)
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw song title
|
||||
gfx.FontSize(28)
|
||||
gfx.GlobalAlpha(transitionAfterscrollTextSongTitle)
|
||||
gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955)
|
||||
|
||||
-- Draw artist
|
||||
gfx.GlobalAlpha(transitionAfterscrollTextSongArtist)
|
||||
gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997)
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
-- Draw difficulties
|
||||
local DIFF_X_START = 98.5
|
||||
local DIFF_GAP = 114.8
|
||||
gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha)
|
||||
for i, diff in ipairs(song.difficulties) do
|
||||
gfx.BeginPath()
|
||||
|
||||
local index = diff.difficulty+1
|
||||
|
||||
if i == selectedDifficulty then
|
||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-(163*0.8)/2, 1028, 163*0.8, 163*0.8, diffCursorImage, 1, 0)
|
||||
end
|
||||
|
||||
Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1)
|
||||
|
||||
local diffLabelImage = difficultyLabelUnderImages[
|
||||
Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty)
|
||||
]
|
||||
local tw, th = gfx.ImageSize(diffLabelImage)
|
||||
tw=tw*0.9
|
||||
th=th*0.9
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0)
|
||||
end
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
|
||||
-- Scoreboard
|
||||
|
||||
drawLocalLeaderboard(diff)
|
||||
drawIrLeaderboard()
|
||||
|
||||
gfx.FontSize(22)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha)
|
||||
gfx.Text(diff.effector, 270, 1180) -- effected by
|
||||
gfx.Text(diff.illustrator, 270, 1210) -- illustrated by
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
end
|
||||
|
||||
function drawLocalLeaderboard(diff)
|
||||
---@param diff SongWheelDifficulty
|
||||
local function drawLocalLeaderboard(diff)
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.FontSize(26)
|
||||
|
||||
|
@ -548,39 +440,49 @@ function drawLocalLeaderboard(diff)
|
|||
gfx.Text("LOCAL TOP", sbBarContentRightX, scoreBoardY + sbBarHeight/2)
|
||||
|
||||
for i = 1, 5, 1 do
|
||||
local scoreTable = diff.scores[i]
|
||||
local score = scoreTable and scoreTable.score
|
||||
local username = scoreTable and scoreTable.playerName
|
||||
|
||||
-- if for some reason there's a score but no associated username, fall back to skin setting
|
||||
if score and username == "" then
|
||||
username = game.GetSkinSetting("username")
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(scoreBoardX, scoreBoardY + i*sbBarHeight, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
gfx.Text(game.GetSkinSetting("username"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||
gfx.Text(username or "-", sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Text((diff.scores[i]) and diff.scores[i].score or "- - - - - - - -", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||
local scoreText = score and tostring(score) or "- - - - - - - -"
|
||||
gfx.Text(scoreText, sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||
end
|
||||
end
|
||||
|
||||
function drawIrLeaderboard()
|
||||
local function drawIrLeaderboard()
|
||||
if not IRData.Active then
|
||||
return
|
||||
end
|
||||
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.FontSize(26)
|
||||
|
||||
|
||||
local scoreBoardX = 75
|
||||
local scoreBoardY = 1500
|
||||
|
||||
|
||||
local sbBarWidth = 336*1.2
|
||||
local sbBarHeight = 33
|
||||
|
||||
|
||||
local sbBarContentLeftX = scoreBoardX + 80
|
||||
local sbBarContentRightX = scoreBoardX + sbBarWidth/2 + 30
|
||||
|
||||
-- Draw the header
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(scoreBoardX, scoreBoardY, sbBarWidth, sbBarHeight, scoreBoardBarBgImage, 1, 0)
|
||||
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
|
||||
|
@ -616,8 +518,8 @@ function drawIrLeaderboard()
|
|||
-- Becuase the scores are in "random order", we have to do this
|
||||
for index, irScore in ipairs(irLeaderboard) do
|
||||
-- local irScore = irLeaderboard[i]
|
||||
|
||||
if irScore then
|
||||
|
||||
if irScore then
|
||||
local rank = index
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
|
@ -626,7 +528,7 @@ function drawIrLeaderboard()
|
|||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
gfx.Text(string.upper(irScore.username), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight)
|
||||
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Text(string.format("%d", irScore.score), sbBarContentRightX, scoreBoardY + sbBarHeight/2 + rank*sbBarHeight)
|
||||
|
||||
|
@ -637,7 +539,130 @@ function drawIrLeaderboard()
|
|||
end
|
||||
end
|
||||
|
||||
function drawFilterInfo(deltatime)
|
||||
local function drawData() -- Draws the song data on the left panel
|
||||
|
||||
if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end
|
||||
|
||||
local song = songwheel.songs[selectedIndex]
|
||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||
local bestScore = diff and diff.scores[1]
|
||||
|
||||
if not song then return false end
|
||||
|
||||
local jacketImage = getJacketImage(song)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0)
|
||||
|
||||
if (top50diffs[diff.hash]) then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0)
|
||||
end
|
||||
|
||||
-- Draw best score
|
||||
gfx.Save()
|
||||
gfx.BeginPath()
|
||||
|
||||
local scoreNumber = 0
|
||||
if bestScore then
|
||||
scoreNumber = bestScore.score
|
||||
end
|
||||
|
||||
Numbers.draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12)
|
||||
Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12)
|
||||
|
||||
-- Draw grade
|
||||
local gradeImage = gradeImages.none
|
||||
local gradeAlpha = transitionAfterscrollGradeAlpha
|
||||
if bestScore then
|
||||
gradeImage = getGradeImageForScore(bestScore.score)
|
||||
|
||||
if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then
|
||||
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
||||
end
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0)
|
||||
|
||||
-- Draw badge
|
||||
local badgeImage = badgeImages[diff.topBadge+1]
|
||||
|
||||
local badgeAlpha = transitionAfterscrollBadgeAlpha
|
||||
if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then
|
||||
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, badgeAlpha, 0)
|
||||
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw BPM
|
||||
gfx.Save()
|
||||
gfx.BeginPath()
|
||||
gfx.FontSize(24)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out
|
||||
gfx.Text(song.bpm, 85, 920)
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw song title
|
||||
gfx.Save()
|
||||
gfx.FontSize(28)
|
||||
gfx.GlobalAlpha(transitionAfterscrollTextSongTitle)
|
||||
gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955)
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw artist
|
||||
gfx.Save()
|
||||
gfx.GlobalAlpha(transitionAfterscrollTextSongArtist)
|
||||
gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997)
|
||||
gfx.Restore()
|
||||
|
||||
-- Draw difficulties
|
||||
local DIFF_X_START = 98.5
|
||||
local DIFF_GAP = 114.8
|
||||
gfx.Save()
|
||||
gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha)
|
||||
for i, diff in ipairs(song.difficulties) do
|
||||
gfx.BeginPath()
|
||||
|
||||
local index = diff.difficulty+1
|
||||
|
||||
if i == selectedDifficulty then
|
||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-(163*0.8)/2, 1028, 163*0.8, 163*0.8, diffCursorImage, 1, 0)
|
||||
end
|
||||
|
||||
Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1)
|
||||
|
||||
local diffLabelImage = difficultyLabelUnderImages[
|
||||
Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty)
|
||||
]
|
||||
local tw, th = gfx.ImageSize(diffLabelImage)
|
||||
tw=tw*0.9
|
||||
th=th*0.9
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0)
|
||||
end
|
||||
gfx.Restore()
|
||||
|
||||
-- Scoreboard
|
||||
|
||||
drawLocalLeaderboard(diff)
|
||||
drawIrLeaderboard()
|
||||
|
||||
gfx.Save()
|
||||
gfx.FontSize(22)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha)
|
||||
gfx.Text(diff.effector, 270, 1180) -- effected by
|
||||
gfx.Text(diff.illustrator, 270, 1210) -- illustrated by
|
||||
gfx.Restore()
|
||||
|
||||
end
|
||||
|
||||
local function drawFilterInfo(deltatime)
|
||||
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
||||
|
||||
if (songwheel.searchInputActive) then
|
||||
|
@ -646,17 +671,17 @@ function drawFilterInfo(deltatime)
|
|||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(5, 95, 417*0.85, 163*0.85, filterInfoBgImage, 1, 0)
|
||||
|
||||
|
||||
local folderLabel = game.GetSkinSetting('_songWheelActiveFolderLabel')
|
||||
local subFolderLabel = game.GetSkinSetting('_songWheelActiveSubFolderLabel')
|
||||
local sortOptionLabel = game.GetSkinSetting('_songWheelActiveSortOptionLabel')
|
||||
|
||||
|
||||
gfx.FontSize(24)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Text(folderLabel or '', 167, 131)
|
||||
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Text(subFolderLabel or '', 195, 166)
|
||||
|
||||
|
@ -667,7 +692,7 @@ function drawFilterInfo(deltatime)
|
|||
gfx.Text(sortOptionLabel or '', desw-150, 130)
|
||||
end
|
||||
|
||||
function drawCursor()
|
||||
local function drawCursor()
|
||||
if isFilterWheelActive or transitionLeaveScale ~= 0 then return false end
|
||||
|
||||
gfx.BeginPath()
|
||||
|
@ -678,7 +703,7 @@ function drawCursor()
|
|||
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
|
||||
end
|
||||
|
||||
function drawSearch()
|
||||
local function drawSearch()
|
||||
if (not songwheel.searchInputActive and searchPreviousActiveState) then
|
||||
searchPreviousActiveState = false
|
||||
game.PlaySample('sort_wheel/enter.wav')
|
||||
|
@ -686,7 +711,7 @@ function drawSearch()
|
|||
searchPreviousActiveState = true
|
||||
game.PlaySample('sort_wheel/leave.wav')
|
||||
end
|
||||
|
||||
|
||||
if (songwheel.searchText ~= '' and searchInfoPreviousActiveState == true) then
|
||||
searchInfoPreviousActiveState = false
|
||||
elseif (songwheel.searchText == '' and searchInfoPreviousActiveState == false) then
|
||||
|
@ -712,7 +737,7 @@ function drawSearch()
|
|||
local infoXPos = 0
|
||||
local infoYStartPos = desh - sh - 772 + 242
|
||||
local infoYPos = infoYStartPos + transitionSearchInfoOffsetY
|
||||
|
||||
|
||||
if (game.GetSkinSetting('gameplay_showSearchControls')) then
|
||||
gfx.ImageRect(infoXPos, infoYPos, sw, sh, searchInfoPanelImage, transitionSearchBackgroundInfoAlpha, 0)
|
||||
end
|
||||
|
@ -748,7 +773,7 @@ function drawSearch()
|
|||
gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2)
|
||||
end
|
||||
|
||||
function drawScrollbar()
|
||||
local function drawScrollbar()
|
||||
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
||||
|
||||
-- Scrollbar Background
|
||||
|
@ -791,7 +816,33 @@ function drawScrollbar()
|
|||
end
|
||||
end
|
||||
|
||||
function refreshIrLeaderboard(deltaTime)
|
||||
---Called on IR Leaderboard fetch complete
|
||||
---@param res IRLeaderboardResponse
|
||||
local function onIrLeaderboardFetched(res)
|
||||
irRequestStatus = res.statusCode
|
||||
|
||||
local song = songwheel.songs[selectedIndex]
|
||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||
|
||||
if res.statusCode == IRData.States.Success then
|
||||
local tempIrLB = res.body
|
||||
|
||||
table.sort(tempIrLB, function (a,b)
|
||||
return a.score > b.score
|
||||
end)
|
||||
|
||||
irLeaderboard = tempIrLB
|
||||
irLeaderboardsCache[diff.hash] = irLeaderboard
|
||||
else
|
||||
local httpStatus = (res.statusCode // 10) * 100 + res.statusCode % 10 -- convert to 100 range
|
||||
game.Log("IR error (" .. httpStatus .. "): " .. res.description, game.LOGGER_ERROR)
|
||||
if res.body then
|
||||
game.Log(common.dump(res.body), game.LOGGER_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function refreshIrLeaderboard(deltaTime)
|
||||
if not IRData.Active then
|
||||
return
|
||||
end
|
||||
|
@ -820,56 +871,10 @@ function refreshIrLeaderboard(deltaTime)
|
|||
end
|
||||
|
||||
irRequestStatus = 2 -- Loading
|
||||
-- onIrLeaderboardFetched({
|
||||
-- statusCode = 20,
|
||||
-- body = {}
|
||||
-- })
|
||||
IR.Leaderboard(diff.hash, 'best', 4, onIrLeaderboardFetched)
|
||||
end
|
||||
|
||||
function dump(o)
|
||||
if type(o) == 'table' then
|
||||
local s = '{ '
|
||||
for k,v in pairs(o) do
|
||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
||||
end
|
||||
return s .. '} '
|
||||
else
|
||||
return tostring(o)
|
||||
end
|
||||
end
|
||||
|
||||
function onIrLeaderboardFetched(res)
|
||||
irRequestStatus = res.statusCode
|
||||
|
||||
local song = songwheel.songs[selectedIndex]
|
||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||
game.Log(diff.hash, game.LOGGER_ERROR)
|
||||
|
||||
if res.statusCode == IRData.States.Success then
|
||||
game.Log('Raw IR reposonse: ' .. dump(res.body), game.LOGGER_ERROR)
|
||||
local tempIrLB = res.body
|
||||
|
||||
table.sort(tempIrLB, function (a,b)
|
||||
-- game.Log(a.score .. ' ?? ' .. b.score, game.LOGGER_ERROR)
|
||||
return a.score > b.score
|
||||
end)
|
||||
|
||||
-- for i, tempScore in ipairs(tempIrLeaderboard) do
|
||||
-- irLeaderboard[tempScore.ranking] = tempScore
|
||||
-- end
|
||||
|
||||
irLeaderboard = tempIrLB
|
||||
irLeaderboardsCache[diff.hash] = irLeaderboard
|
||||
|
||||
game.Log(dump(irLeaderboard), game.LOGGER_ERROR)
|
||||
else
|
||||
game.Log("IR error " .. res.statusCode, game.LOGGER_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
function tickTransitions(deltaTime)
|
||||
local function tickTransitions(deltaTime)
|
||||
if transitionScrollScale < 1 then
|
||||
transitionScrollScale = transitionScrollScale + deltaTime / 0.1 -- transition should last for that time in seconds
|
||||
else
|
||||
|
@ -885,7 +890,7 @@ function tickTransitions(deltaTime)
|
|||
transitionAfterscrollScale = 1
|
||||
end
|
||||
|
||||
if scrollingUp then
|
||||
if scrollingUp then
|
||||
transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * songPlateHeight
|
||||
else
|
||||
transitionScrollOffsetY = Easing.inQuad(1-transitionScrollScale) * -songPlateHeight
|
||||
|
@ -930,8 +935,6 @@ function tickTransitions(deltaTime)
|
|||
end
|
||||
end
|
||||
|
||||
transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
|
||||
|
||||
-- Grade alpha
|
||||
if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then
|
||||
transitionAfterscrollGradeAlpha = 0.5
|
||||
|
@ -940,7 +943,7 @@ function tickTransitions(deltaTime)
|
|||
else
|
||||
transitionAfterscrollGradeAlpha = 0
|
||||
end
|
||||
|
||||
|
||||
-- Badge alpha
|
||||
if transitionAfterscrollScale >= 0.032 and transitionAfterscrollScale < 0.035 then
|
||||
transitionAfterscrollBadgeAlpha = 0.5
|
||||
|
@ -962,7 +965,7 @@ function tickTransitions(deltaTime)
|
|||
else
|
||||
transitionAfterscrollTextSongArtist = 1
|
||||
end
|
||||
|
||||
|
||||
-- Difficulties alpha
|
||||
if transitionAfterscrollScale < 0.025 then
|
||||
transitionAfterscrollDifficultiesAlpha = math.min(1, transitionAfterscrollScale / 0.025)
|
||||
|
@ -982,7 +985,7 @@ function tickTransitions(deltaTime)
|
|||
elseif transitionJacketBgScrollScale >= 0.05 and transitionJacketBgScrollScale < 0.1 then
|
||||
transitionJacketBgScrollAlpha = math.min(1, (transitionJacketBgScrollScale-0.05) / 0.05)
|
||||
elseif transitionJacketBgScrollScale >= 0.8 and transitionJacketBgScrollScale < 1 then
|
||||
transitionJacketBgScrollAlpha = math.max(0,
|
||||
transitionJacketBgScrollAlpha = math.max(0,
|
||||
math.min(1, 1-((transitionJacketBgScrollScale-0.8) / 0.05))
|
||||
)
|
||||
else
|
||||
|
@ -999,25 +1002,17 @@ function tickTransitions(deltaTime)
|
|||
end
|
||||
|
||||
transitionLaserY = desh - math.min(transitionLaserScale * 2 * desh, desh)
|
||||
|
||||
|
||||
-- Flash transition
|
||||
if transitionFlashScale < 1 then
|
||||
---@type number|string
|
||||
local songBpm = 120
|
||||
|
||||
if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then
|
||||
songBpm = songwheel.songs[selectedIndex].bpm
|
||||
|
||||
-- Is a variable BPM
|
||||
if (type(songBpm) == "string") then
|
||||
local s = split(songBpm, '-')
|
||||
songBpm = tonumber(s[1]) -- Lowest bpm value
|
||||
end
|
||||
end
|
||||
|
||||
-- If the original songBpm is "2021.04.01" for example, the above code can produce `nil` in the songBpm
|
||||
-- since it cannot parse the number out of that string. Here we implement a fallback, to not crash
|
||||
-- USC on whacky charts. Whacky charters, quit using batshit insane bpm values. It makes me angery >:(
|
||||
if (songBpm == nil) then
|
||||
songBpm = 120
|
||||
local songBpmStr = songwheel.songs[selectedIndex].bpm
|
||||
local songBpmStrs = common.split(songBpmStr, '-')
|
||||
local minBpm = tonumber(songBpmStrs[1]) -- Lowest bpm value
|
||||
songBpm = minBpm or songBpm
|
||||
end
|
||||
|
||||
transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds
|
||||
|
@ -1057,7 +1052,41 @@ function tickTransitions(deltaTime)
|
|||
end
|
||||
end
|
||||
|
||||
draw_songwheel = function(deltaTime)
|
||||
---This function is basically a workaround for the ForceRender call
|
||||
local function drawRadar()
|
||||
if not showEffectRadar then return end
|
||||
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
||||
|
||||
local x, y = 375, 650
|
||||
local scale = 0.666
|
||||
|
||||
gfx.FontSize(28)
|
||||
gfx.Translate(x, y)
|
||||
gfx.Scale(scale, scale)
|
||||
|
||||
local strokeColor = ColorRGBA.new(255, 255, 255, 128)
|
||||
local fillColor = ColorRGBA.new(0, 0, 0, 191)
|
||||
|
||||
gfx.ResetScissor()
|
||||
radar:drawBackground(fillColor)
|
||||
radar:drawOutline(3, strokeColor)
|
||||
|
||||
--Bug: ForceRender resets every transformation, need to re-setup view transform afterwards
|
||||
--ForceRender also resets gfx stack, USC will crash if you try to call gfx.Restore(),
|
||||
--make sure the gfx stack is clean before calling radar:drawRadarMesh()
|
||||
radar:drawRadarMesh()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
gfx.Save()
|
||||
gfx.Translate(x, y)
|
||||
gfx.Scale(scale, scale)
|
||||
radar:drawRadialTicks(strokeColor)
|
||||
radar:drawAttributes()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
local draw_songwheel = function(deltaTime)
|
||||
drawBackground(deltaTime)
|
||||
|
||||
drawSongList()
|
||||
|
@ -1065,6 +1094,9 @@ draw_songwheel = function(deltaTime)
|
|||
isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1
|
||||
|
||||
drawData()
|
||||
|
||||
drawRadar()
|
||||
|
||||
drawCursor()
|
||||
|
||||
drawFilterInfo(deltaTime)
|
||||
|
@ -1079,13 +1111,16 @@ draw_songwheel = function(deltaTime)
|
|||
local debugScrollingUp= "FALSE"
|
||||
if scrollingUp then debugScrollingUp = "TRUE" end
|
||||
|
||||
if game.GetSkinSetting('debug_showInformation') then
|
||||
if game.GetSkinSetting('debug_showInformation') then
|
||||
gfx.Text('S_I: ' .. selectedIndex .. ' // S_D: ' .. selectedDifficulty .. ' // S_UP: ' .. debugScrollingUp .. ' // AC_TS: ' .. transitionAfterscrollScale .. ' // L_TS: ' .. transitionLeaveScale .. ' // IR_CODE: ' .. irRequestStatus .. ' // IR_T: ' .. irRequestTimeout, 8, 8)
|
||||
end
|
||||
|
||||
Footer.draw(deltaTime)
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line:lowercase-global
|
||||
render = function (deltaTime)
|
||||
tickTransitions(deltaTime)
|
||||
|
||||
|
@ -1093,6 +1128,13 @@ render = function (deltaTime)
|
|||
|
||||
Sound.stopMusic()
|
||||
|
||||
if showEffectRadar and updateRadar then
|
||||
local difficultyNames = {"nov","adv","exh","mxm","inf","grv","hvn","vvd","exc"}
|
||||
local diff = songwheel.songs[selectedIndex].difficulties[selectedDifficulty].difficulty + 1
|
||||
radar:updateGraph(songwheel.songs[selectedIndex].path, difficultyNames[diff])
|
||||
updateRadar = false
|
||||
end
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
Wallpaper.render()
|
||||
|
@ -1104,6 +1146,7 @@ render = function (deltaTime)
|
|||
refreshIrLeaderboard(deltaTime)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line:lowercase-global
|
||||
songs_changed = function (withAll)
|
||||
|
||||
irLeaderboardsCache = {} -- Reset LB cache
|
||||
|
@ -1113,57 +1156,70 @@ songs_changed = function (withAll)
|
|||
game.SetSkinSetting('_songWheelScrollbarTotal', #songwheel.songs)
|
||||
game.SetSkinSetting('_songWheelScrollbarIndex', selectedIndex)
|
||||
|
||||
local diffs = {}
|
||||
for i = 1, #songwheel.allSongs do
|
||||
local song = songwheel.allSongs[i]
|
||||
for j = 1, #song.difficulties do
|
||||
local diff = song.difficulties[j]
|
||||
diff.force = VolforceCalc.calc(diff)
|
||||
table.insert(diffs, diff)
|
||||
end
|
||||
end
|
||||
table.sort(diffs, function (l, r)
|
||||
return l.force > r.force
|
||||
end)
|
||||
totalForce = 0
|
||||
for i = 1, 50 do
|
||||
if diffs[i] then
|
||||
top50diffs[diffs[i].id] = true
|
||||
totalForce = totalForce + diffs[i].force
|
||||
end
|
||||
end
|
||||
local diffs = {}
|
||||
for i = 1, #songwheel.allSongs do
|
||||
local song = songwheel.allSongs[i]
|
||||
for j = 1, #song.difficulties do
|
||||
local diff = song.difficulties[j]
|
||||
table.insert(diffs, {hash = diff.hash, force = VolforceCalc.calc(diff)})
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(diffs, function (l, r)
|
||||
return l.force > r.force
|
||||
end)
|
||||
|
||||
local totalForce = 0
|
||||
for i = 1, 50 do
|
||||
local diff = diffs[i]
|
||||
if not diff then
|
||||
break
|
||||
end
|
||||
top50diffs[diff.hash] = true
|
||||
totalForce = totalForce + diff.force
|
||||
end
|
||||
|
||||
game.SetSkinSetting('_volforce', totalForce)
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line:lowercase-global
|
||||
set_index = function(newIndex)
|
||||
transitionScrollScale = 0
|
||||
transitionAfterscrollScale = 0
|
||||
transitionJacketBgScrollScale = 0
|
||||
|
||||
Footer.resetTimer()
|
||||
|
||||
game.SetSkinSetting('_songWheelScrollbarTotal', #songwheel.songs)
|
||||
game.SetSkinSetting('_songWheelScrollbarIndex', newIndex)
|
||||
|
||||
scrollingUp = false
|
||||
if ((newIndex > selectedIndex and not (newIndex == #songwheel.songs and selectedIndex == 1)) or (newIndex == 1 and selectedIndex == #songwheel.songs)) then
|
||||
scrollingUp = true
|
||||
end
|
||||
scrollingUp = false
|
||||
if ((newIndex > selectedIndex and not (newIndex == #songwheel.songs and selectedIndex == 1)) or (newIndex == 1 and selectedIndex == #songwheel.songs)) then
|
||||
scrollingUp = true
|
||||
end
|
||||
|
||||
game.PlaySample('song_wheel/cursor_change.wav')
|
||||
updateRadar = true
|
||||
|
||||
game.PlaySample('song_wheel/cursor_change.wav')
|
||||
|
||||
selectedIndex = newIndex
|
||||
end
|
||||
|
||||
local json = require("common.json")
|
||||
|
||||
---@diagnostic disable-next-line:lowercase-global
|
||||
set_diff = function(newDiff)
|
||||
if newDiff ~= selectedDifficulty then
|
||||
if newDiff ~= selectedDifficulty then
|
||||
jacketCache = {} -- Clear the jacket cache for the new diff jackets
|
||||
|
||||
game.PlaySample('song_wheel/diff_change.wav')
|
||||
end
|
||||
|
||||
Footer.resetTimer()
|
||||
updateRadar = true
|
||||
|
||||
selectedDifficulty = newDiff
|
||||
irLeaderboard = {}
|
||||
irRequestStatus = 1
|
||||
irRequestTimeout = 2
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,6 +40,10 @@ local resources = {
|
|||
}
|
||||
}
|
||||
|
||||
if not resources.anims.idolAnimation then
|
||||
game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING)
|
||||
end
|
||||
|
||||
-- load audio samples
|
||||
for _, path in pairs(resources.audiosamples) do
|
||||
game.LoadSkinSample(path)
|
||||
|
@ -146,6 +150,8 @@ local playedBgm = false
|
|||
|
||||
local triggerServiceMenu = false
|
||||
|
||||
Footer.set{enableTimer=false}
|
||||
|
||||
local function setButtonActions()
|
||||
buttons[1].action = Menu.Challenges
|
||||
buttons[2].action = Menu.Multiplayer
|
||||
|
@ -306,16 +312,18 @@ local function draw_titlescreen(deltaTime)
|
|||
gfx.BeginPath()
|
||||
Background.draw(deltaTime)
|
||||
|
||||
local idolAnimTickRes = gfx.TickAnimation(resources.anims.idolAnimation, deltaTime)
|
||||
if idolAnimTickRes == 1 then
|
||||
gfx.GlobalAlpha(idolAnimTransitionScale)
|
||||
if resources.anims.idolAnimation then
|
||||
local idolAnimTickRes = gfx.TickAnimation(resources.anims.idolAnimation, deltaTime)
|
||||
if idolAnimTickRes == 1 then
|
||||
gfx.GlobalAlpha(idolAnimTransitionScale)
|
||||
|
||||
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60
|
||||
if (idolAnimTransitionScale > 1) then idolAnimTransitionScale = 1 end
|
||||
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60
|
||||
if (idolAnimTransitionScale > 1) then idolAnimTransitionScale = 1 end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, Dim.design.width, Dim.design.height, resources.anims.idolAnimation, 1, 0)
|
||||
gfx.GlobalAlpha(1)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, Dim.design.width, Dim.design.height, resources.anims.idolAnimation, 1, 0)
|
||||
gfx.GlobalAlpha(1)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw selector background
|
||||
|
|
|
@ -16,5 +16,5 @@ uniform mat4 world;
|
|||
void main()
|
||||
{
|
||||
fsTex = inTex;
|
||||
gl_Position = proj * camera * world * vec4(inPos.x, inPos.y * 3.8, 0, 1);
|
||||
gl_Position = proj * camera * world * vec4(inPos.x, inPos.y * 5.7, 0, 1);
|
||||
}
|
|
@ -20,10 +20,27 @@ uniform int hitState;
|
|||
|
||||
const float laserSize = 1.0675;
|
||||
|
||||
vec3 rgb2hsv(vec3 c)
|
||||
{
|
||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
||||
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||
}
|
||||
|
||||
vec3 hsv2rgb(vec3 c)
|
||||
{
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (laserPart == 1) {
|
||||
target = texture(mainTex, fsTex);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -40,8 +57,26 @@ void main() {
|
|||
x += (laserSize / 2);
|
||||
|
||||
float y = 0.25 * ceil(float(hitState)) + 0.01;
|
||||
vec4 channels = texture(mainTex, vec2(x, y));
|
||||
|
||||
float visiblityMultiplier = 1;
|
||||
vec3 baseColor = color.rgb * channels.g;
|
||||
vec3 baseHsv = rgb2hsv(baseColor);
|
||||
|
||||
target = texture(mainTex, vec2(x, y)) * vec4(visiblityMultiplier,visiblityMultiplier,visiblityMultiplier,1);
|
||||
float h = baseHsv.x;
|
||||
|
||||
float hilightHue = 0.0;
|
||||
if (h < 2.0 / 12.0)
|
||||
hilightHue = 1.0 - smoothstep(0, 1.0 / 12.0, h);
|
||||
else hilightHue = smoothstep(2.0 / 12.0, 6.0 / 12.0, h);
|
||||
|
||||
hilightHue = mod((280.0 / 360.0) + (hilightHue * 140.0 / 360.0), 1.0);
|
||||
vec3 hilightColor = hsv2rgb(vec3(hilightHue, 1, 1));
|
||||
|
||||
baseColor = baseColor * (1.0 - channels.b) + vec3(channels.b);
|
||||
|
||||
//hilightColor = hilightColor * channels.r;
|
||||
|
||||
vec3 mixedColor = clamp(mix(baseColor, hilightColor, channels.r), 0.0, 1.0);
|
||||
|
||||
target = vec4(mixedColor, 1);
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
const float PI = 3.1415926535897932384626433832795;
|
||||
const float PI_2 = 1.57079632679489661923;
|
||||
|
||||
layout(location=1) in vec2 fsTex;
|
||||
layout(location=2) in vec3 fragPos;
|
||||
|
||||
layout(location=0) out vec4 target;
|
||||
|
||||
uniform vec4 colorCenter;
|
||||
uniform vec4 colorMax;
|
||||
|
||||
uniform float maxSize;
|
||||
|
||||
float lerp(float x, vec2 p0, vec2 p1)
|
||||
{
|
||||
return p0.y + (p1.y - p0.y) * ((x - p0.x)/(p1.x - p0.x));
|
||||
}
|
||||
|
||||
vec4 toHSV(vec4 rgb)
|
||||
{
|
||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
vec4 p = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g));
|
||||
vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r));
|
||||
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, rgb.a);
|
||||
}
|
||||
|
||||
vec4 toRGB(vec4 hsv)
|
||||
{
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);
|
||||
return vec4(hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y), hsv.a);
|
||||
}
|
||||
|
||||
// x is in [0.0, 1.0]
|
||||
vec4 lerpColor(float x, vec4 color1, vec4 color2)
|
||||
{
|
||||
// convert RGB color space to HSV
|
||||
vec4 hsv1 = toHSV(color1), hsv2 = toHSV(color2);
|
||||
float hue = (mod(mod((hsv2.x-hsv1.x), 1.) + 1.5, 1.)-0.5)*x + hsv1.x;
|
||||
vec4 hsv = vec4(hue, mix(hsv1.yzw, hsv2.yzw, x));
|
||||
// convert HSV color space back to RGB
|
||||
return toRGB(hsv);
|
||||
}
|
||||
|
||||
float EaseInSine(float x)
|
||||
{
|
||||
return 1.0 - cos((x * PI) / 2.0);
|
||||
}
|
||||
|
||||
vec4 coordToColor(vec2 pos, float maxSize)
|
||||
{
|
||||
float r = length(pos);
|
||||
float factor = lerp(r, vec2(0,0), vec2(maxSize,1));
|
||||
return lerpColor(EaseInSine(factor), colorCenter, colorMax);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
target = coordToColor(fragPos.xy, maxSize);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout(location=0) in vec2 inPos;
|
||||
layout(location=1) in vec2 inTex;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
layout(location=1) out vec2 fsTex;
|
||||
layout(location=2) out vec3 fragPos;
|
||||
|
||||
uniform mat4 proj;
|
||||
uniform mat4 world;
|
||||
|
||||
void main()
|
||||
{
|
||||
fsTex = inTex;
|
||||
gl_Position = proj * world * vec4(inPos.xy, 0, 1);
|
||||
fragPos = vec3(inPos.xy, 0);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout(location=1) in vec2 fsTex;
|
||||
layout(location=0) out vec4 target;
|
||||
layout(location=2) in vec4 inColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
target = inColor;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#version 330
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
const float PI = 3.1415926535897932384626433832795;
|
||||
const float PI_2 = 1.57079632679489661923;
|
||||
|
||||
layout(location=0) in vec2 inPos;
|
||||
layout(location=1) in vec2 inTex;
|
||||
|
||||
out gl_PerVertex
|
||||
{
|
||||
vec4 gl_Position;
|
||||
};
|
||||
layout(location=1) out vec2 fsTex;
|
||||
layout(location=2) out vec4 vertexColor;
|
||||
|
||||
uniform mat4 proj;
|
||||
uniform mat4 world;
|
||||
|
||||
uniform vec4 colorCenter;
|
||||
uniform vec4 colorMax;
|
||||
|
||||
uniform float maxSize;
|
||||
|
||||
// Polar coordinate utility functions
|
||||
float hypot(vec2 pos)
|
||||
{
|
||||
return sqrt(pos.x * pos.x + pos.y * pos.y);
|
||||
}
|
||||
|
||||
/*
|
||||
float atan2(vec2 pos)
|
||||
{
|
||||
if (pos.x > 0)
|
||||
{
|
||||
return atan(pos.y / pos.x);
|
||||
}
|
||||
|
||||
if (pos.x < 0 && pos.y >= 0)
|
||||
{
|
||||
return atan(pos.y / pos.x) + PI;
|
||||
}
|
||||
|
||||
if (pos.x < 0 && pos.y < 0)
|
||||
{
|
||||
return atan(pos.y / pos.x) - PI;
|
||||
}
|
||||
|
||||
if (pos.x == 0 && pos.y > 0)
|
||||
{
|
||||
return PI_2;
|
||||
}
|
||||
|
||||
if (pos.x == 0 && pos.y < 0)
|
||||
{
|
||||
return -PI_2;
|
||||
}
|
||||
|
||||
// The following is not mathematically correct, as it's undefined
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
vec2 toPolar(vec2 cartesian)
|
||||
{
|
||||
return vec2(hypot(pos), atan2(pos));
|
||||
}
|
||||
*/
|
||||
|
||||
float lerp(float x, vec2 p0, vec2 p1)
|
||||
{
|
||||
return p0.y + (p1.y - p0.y) * ((x - p0.x)/(p1.x - p0.x));
|
||||
}
|
||||
|
||||
// x is in [0.0, 1.0]
|
||||
vec4 lerpColor(float x, vec4 color1, vec4 color2)
|
||||
{
|
||||
return color1 + (color2 - color1) * x;
|
||||
}
|
||||
|
||||
vec4 coordToColor(vec2 pos, float maxSize)
|
||||
{
|
||||
float r = hypot(pos);
|
||||
//float phi = atan2(pos);
|
||||
|
||||
float factor = lerp(r, vec2(0,0), vec2(maxSize,1));
|
||||
|
||||
return lerpColor(factor, colorCenter, colorMax);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = proj * world * vec4(inPos.xy, 0, 1);
|
||||
vertexColor = coordToColor(inPos.xy, maxSize);
|
||||
}
|
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 859 B |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 1.8 KiB |