Compare commits

..

No commits in common. "master" and "8bbea73be5c7467841fee542739c2a6ff68835a8" have entirely different histories.

82 changed files with 1045 additions and 42481 deletions

BIN
CHANGELOG

Binary file not shown.

View File

@ -33,7 +33,7 @@
"Crew": { "type": "label" },
"single_idol": {
"label": "Crew idol animations, folder name in `crew/anim/`",
"label": "!!!ALWAYS MATCH THE NAME!!!",
"type": "text",
"default": "nothing"
},
@ -97,28 +97,5 @@
"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
}
}

View File

@ -23,53 +23,47 @@ IRData = {}
---@field serverTime integer
---@field serverName string
---@field irVersion string
IRHeartbeatResponseBody = {}
---@class IRRecordResponseBody
---@field record ServerScore
IRRecordResponseBody = {}
---@alias IRLeaderboardResponseBody ServerScore[]
---@class IRLeaderboardResponseBody
---@field scores ServerScore[]
IRLeaderboardResponseBody = {}
---@class IRResponse
---@field statusCode integer
---@field description string
---@class IRHeartbeatResponse : IRResponse
---@field body IRHeartbeatResponseBody
---@class IRChartTrackedResponse : IRResponse
---@field body {}
---@class IRRecordResponse : IRResponse
---@field body IRRecordResponseBody
---@class IRLeaderboardResponse : IRResponse
---@field body ServerScore[]
---@field body nil|IRHeartbeatResponseBody|IRRecordResponseBody|IRLeaderboardResponseBody
IRResponse = {}
-- Performs a Heartbeat request.
---@param callback fun(res: IRHeartbeatResponse) # Callback function receives IRResponse as it's first parameter
---@param callback fun(res: IRResponse) # 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: IRChartTrackedResponse) # Callback function receives IRResponse as it's first parameter
---@param callback fun(res: IRResponse) # 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: IRRecordResponse) # Callback function receives IRResponse as it's first parameter
---@param callback fun(res: IRResponse) # 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: IRLeaderboardResponse) # Callback function receives IRResponse as it's first parameter
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
local function Leaderboard(hash, mode, n, callback) end
---@class IR
---@type table
IR = {
Heartbeat = Heartbeat,
ChartTracked = ChartTracked,
Record = Record,
Leaderboard = Leaderboard
}
}

View File

@ -1,121 +0,0 @@
-- 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

View File

@ -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,10 +280,11 @@ 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>/fonts/<name>`
-- Loads a font from `skins/<skin>/textures/<path>`
-- Sets it as the current font if it is already loaded
---@param name string
LoadSkinFont = function(name) end
---@param name? string
---@param filename string
LoadSkinFont = function(name, filename) 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
@ -471,7 +472,7 @@ UpdateImagePattern = function(pattern, sx, sy, ix, iy, angle, alpha) end
---@param size? integer
UpdateLabel = function(label, text, size) end
---@class gfx
---@type table
gfx = {
BLEND_ZERO = 1,
BLEND_ONE = 2,
@ -594,4 +595,4 @@ gfx = {
Translate = Translate,
UpdateImagePattern = UpdateImagePattern,
UpdateLabel = UpdateLabel,
};
};

View File

@ -1,7 +1,4 @@
-- result `result` table
---@diagnostic disable:lowercase-global
---@diagnostic disable:missing-return
-- result and challengeresult `result` table
---@class HitStat
---@field timeFrac number -- Fraction of when in the chart the note was hit, `0.0` to `1.0`
@ -10,6 +7,7 @@
---@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`
@ -18,6 +16,7 @@
---@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
@ -26,25 +25,21 @@
---@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 # Hit windows of the score
---@field hitWindow HitWindow|nil # Hit windows of the score, only for singleplayer results screen
---@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 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 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 ServerScoreOptions
---@field gaugeType integer # An enum value representing the gauge type used. 0 = normal, 1 = hard. Further values are not currently specified.
@ -52,6 +47,7 @@
---@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
@ -67,19 +63,28 @@
---@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
@ -87,7 +92,7 @@
---@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|MultiplayerScore)[] # All scores
---@field highScores Score[] # 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
@ -98,7 +103,7 @@
---@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 level
---@field level integer # Chart or challenge level
---@field maxCombo integer # Result max chain
---@field meanHitDelta number # Mean hit delta
---@field meanHitDeltaAbs number # Absolute value of mean hit delta
@ -108,34 +113,20 @@
---@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)
---@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
---@field title string # Chart (with player name in multiplayer) or challenge title
---@field uid nil|string # Only for multiplayer, UID of the player
result = {};

View File

@ -1,4 +1,117 @@
---@diagnostic disable:missing-return
-- 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
---@class ShadedMesh
ShadedMesh = {
@ -12,148 +125,57 @@ 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,
};
-- 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
-- Gets the length of the mesh
---@return number length
GetLength = function() 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
-- 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
function ShadedMesh:AddTexture(uniformName, path) end
-- Stops meshes beyond the track from being rendered if `doClip`
---@param doClip boolean
SetClipWithTrack = function(doClip) 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
-- 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
-- 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 = {
};
-- 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
GetLength = GetLength,
UseGameMesh = UseGameMesh,
ScaleToLength = ScaleToLength,
SetClipWithTrack = SetClipWithTrack,
SetLength = SetLength,
};

View File

@ -1,63 +1,29 @@
---@diagnostic disable: lowercase-global
-- songwheel `songwheel` table
---@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
---@class Difficulty
---@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 SongWheelScore[] # Scores for the current difficulty
---@field scores Score[] # Scores for the current difficulty
---@field topBadge integer # `0 = Never Played`, `1 = Played`, `2 = Cleared`, `3 = Hard Cleared`, `4 = Full Chain`, `5 = Perfect Chain`
SongWheelDifficulty = {}
Difficulty = {};
---@class SongWheelSong
---@class Song
---@field artist string # Chart artist
---@field difficulties SongWheelDifficulty[] # Array of difficulties for the current song
---@field bpm string # Chart BPM
---@field difficulties Difficulty[] # Array of difficulties for the current song
---@field bpm number # Chart BPM
---@field id integer # Song id, unique static identifier
---@field path string # Full filepath to the chart folder on the disk
---@field title string # Chart title
SongWheelSong = {}
Song = {};
---@class songwheel
---@field allSongs SongWheelSong[] # Array of all available songs
---@field allSongs Song[] # 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 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
---@field songs Song[] # Array of songs with the current filters/sorting applied
songwheel = {};

View File

@ -1,136 +0,0 @@
{
"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"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,74 +0,0 @@
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

View File

@ -1,28 +0,0 @@
---@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

View File

@ -7,13 +7,6 @@ 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
@ -45,9 +38,7 @@ local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_in
local headerTitleImage = gfx.CreateSkinImage("challenge_result/header/title.png", 0);
-- 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 username = game.GetSkinSetting("username");
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);
@ -84,8 +75,8 @@ local gradeImages = {
none = gfx.CreateSkinImage("common/grades/none.png", 0),
}
local percRequired = nil;
local percGet = nil;
local percRequired = 1;
local percGet = 0;
-- AUDIO
game.LoadSkinSample("challenge_result.wav")
@ -170,7 +161,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+1, chartResult.level)
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty, chartResult.level)
local score = chartResult.score or 0;
@ -224,12 +215,9 @@ function drawCompletion()
gfx.BeginPath()
gfx.ImageRect(63, 1331, 766*0.85, 130*0.85, completitionImage, 1, 0)
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);
gfx.FillColor(255, 128, 0, 255);
@ -238,13 +226,7 @@ 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)
@ -252,10 +234,6 @@ function result_set()
end
end
if (percRequired == nil) then
return
end
game.Log(percRequired, game.LOGGER_ERROR);
local a = 0;

View File

@ -48,11 +48,9 @@ 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);

View File

@ -1,5 +1,5 @@
local function split(s, delimiter)
local result = {};
result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
@ -59,10 +59,6 @@ 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
@ -79,41 +75,6 @@ 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,
@ -123,10 +84,6 @@ return {
roundToZero = roundToZero,
areaOverlap = areaOverlap,
lerp = lerp,
mix = mix,
modIndex = modIndex,
firstAlphaNum = firstAlphaNum,
dump = dump,
all = all,
any = any
}

View File

@ -1,6 +1,6 @@
local MAJOR = 0
local MINOR = 3
local PATCH = 0
local MINOR = 2
local PATCH = 2
local function getLongVersion()
return "USC:E:G:S:" .. MAJOR .. MINOR .. PATCH
@ -18,4 +18,4 @@ return {
PATCH = PATCH,
getLongVersion = getLongVersion,
getVersion = getVersion
}
}

View File

@ -7,8 +7,7 @@ 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/9 exceed.png", 0),
gfx.CreateSkinImage("diff/8 vivid.png", 0)
}
local difficultyLabelTexts = {
@ -19,17 +18,10 @@ local difficultyLabelTexts = {
"INF",
"GRV",
"HVN",
"VVD",
"EXC"
"VVD"
}
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)
@ -69,4 +61,4 @@ end
return {
render = render
}
}

View File

@ -1,9 +1,5 @@
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
@ -12,10 +8,6 @@ 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
@ -23,68 +15,7 @@ local entryTransitionFooterYOffset = 0
local legend = {{control = "START", text = "Confirm selection"}, {control = "KNOB", text = "Scroll"}}
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 set() end
local function drawFooter()
gfx.BeginPath()
@ -92,25 +23,9 @@ local function drawFooter()
gfx.Rect(0, footerY, Dim.design.width, FOOTER_HEIGHT)
gfx.Fill()
-- 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
gfx.BeginPath()
gfx.ImageRect(Dim.design.width - 275, footerY - 25, 328 * 0.85, 188 * 0.85, footerRightImage, 1, 0)
-- Version String
gfx.BeginPath()
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
gfx.FontSize(20)
@ -125,8 +40,6 @@ 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)
@ -151,4 +64,4 @@ local function draw(deltaTime, params)
gfx.Restore()
end
return {set = set, draw = draw, resetTimer = resetTimer}
return {set = set, draw = draw}

View File

@ -1,619 +0,0 @@
--[[
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

View File

@ -1,49 +1,11 @@
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
@ -56,32 +18,16 @@ local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRo
critLineRotation
)
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;
gfx.BeginPath();
gfx.ImageRect(
-CONSOLE_W/2,
-CONSOLE_H/2+350,
CONSOLE_W,
CONSOLE_H,
consoleBaseImage,
1,
0
);
end
return {

View File

@ -17,21 +17,18 @@ 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 * 1.4
local CRITBAR_H = 251 * 1.4
local CRITBAR_W = 1080
local CRITBAR_H = 251
local scale = 1;
local isLandscape = false;
local drawCursors = function (scale, cursors, laserActive)
local drawCursors = function (centerX, centerY,cursors, laserActive)
local cursorW = 598 * 0.165;
local cursorH = 673 * 0.14;
@ -41,7 +38,6 @@ local drawCursors = function (scale, 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();
@ -49,23 +45,20 @@ local drawCursors = function (scale, cursors, laserActive)
local skew = cursor.pos * 0.001;
gfx.SkewX(skew);
local cursorPos = cursor.pos * (1 / scale)
local cursorX = cursorPos - cursorW / 2;
local cursorX = cursor.pos * (1 / scale) - cursorW / 2;
local cursorY = -cursorH / 2;
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
if laserActive[luaIndex] then
gfx.ImageRect(
cursor.pos - tailW / 2,
- tailH / 2,
tailW,
tailH,
cursorTailImages[luaIndex],
cursor.alpha / 2,
0
)
end
gfx.ImageRect(
cursorX,
@ -82,23 +75,11 @@ local drawCursors = function (scale, cursors, laserActive)
cursorY,
cursorW,
cursorH,
cursorGlowWhite,
glowAlpha,
cursorGlowBottomImages[luaIndex],
cursor.alpha,
0
);
gfx.SetImageTint(r, g, b);
gfx.ImageRect(
cursorX,
cursorY,
cursorW,
cursorH,
cursorGlowColor,
glowAlpha,
0
);
gfx.SetImageTint(255, 255, 255);
gfx.ImageRect(
cursorX,
cursorY,
@ -114,29 +95,17 @@ local drawCursors = function (scale, cursors, laserActive)
cursorY,
cursorW,
cursorH,
cursorGlowWhite,
glowAlpha,
cursorGlowTopImages[luaIndex],
cursor.alpha,
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)
_, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
scale, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
gfx.BeginPath()
gfx.FillColor(0, 0, 0, 192)
@ -155,9 +124,9 @@ local renderBase = function (deltaTime, centerX, centerY, rotation)
end
local renderOverlay = function (deltaTime, centerX, centerY, rotation, cursors, laserActive)
scale, _ = Dimensions.setUpTransforms(centerX, centerY, rotation)
scale, isLandscape = Dimensions.setUpTransforms(centerX, centerY, rotation)
drawCursors(scale, cursors, laserActive)
drawCursors(centerX, centerY, cursors, laserActive)
gfx.ResetTransform()
end
@ -165,4 +134,4 @@ end
return {
renderBase=renderBase,
renderOverlay=renderOverlay
}
}

View File

@ -11,29 +11,22 @@ local compare = {
["OFF"] = -1
}
local portraitHeightFractions = {
["UPPER+"] = 2.4,
local heightFractions = {
["UPPER+"] = 2,
["UPPER"] = 3,
["STANDARD"] = 4.2,
["LOWER"] = 5.3,
}
local landscapeHeightFractions = {
["UPPER+"] = 1.5,
["UPPER"] = 2.7,
["STANDARD"] = 4.1,
["LOWER"] = 6.7,
["STANDARD"] = 3.5,
["LOWER"] = 5,
}
local earlyLateFor = compare[game.GetSkinSetting("gameplay_earlyLateFor")]
local msFor = compare[game.GetSkinSetting("gameplay_msFor")]
local earlyLatePosition = game.GetSkinSetting("gameplay_earlyLatePosition")
local heightFraction = heightFractions[game.GetSkinSetting("gameplay_earlyLatePosition")]
local EarlyLate = {
timer = 0,
color = {},
earlyLateText = "",
millisecText = ""
lastMillisec = 0,
showEarlyLate = false,
showMillisec = false,
}
function EarlyLate.render(deltaTime)
@ -46,64 +39,56 @@ function EarlyLate.render(deltaTime)
local screenW, screenH = Dimensions.screen.width, Dimensions.screen.height
local screenCenterX = screenW / 2
local desh, fractionTable
local desh
if screenH > screenW then
desh = 1600
fractionTable = portraitHeightFractions
desh = 1920
else
desh = 1080
fractionTable = landscapeHeightFractions
end
local scale = screenH / desh
local y = screenH / 8 * fractionTable[earlyLatePosition]
local scale = desh / screenH
local y = screenH / 8 * heightFraction
gfx.BeginPath()
gfx.LoadSkinFont("Digital-Serial-ExtraBold.ttf")
gfx.FontSize(20 * scale)
gfx.LoadSkinFont("dfmarugoth.ttf")
gfx.FontSize(18 * scale)
local color = EarlyLate.color
gfx.FillColor(color[1], color[2], color[3])
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
if EarlyLate.showEarlyLate then
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
gfx.FastText(EarlyLate.earlyLateText, screenCenterX - 100 * scale, y)
gfx.FastText(EarlyLate.millisecText, screenCenterX + 100 * scale, y)
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
end
function EarlyLate.TriggerAnimation(rating, millisec)
local showEarlyLate = rating <= earlyLateFor
local showMillisec = rating <= msFor
local isEarly = millisec < 0
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 = ""
if showEarlyLate or showMillisec then
EarlyLate.timer = 120
EarlyLate.lastMillisec = millisec
EarlyLate.showEarlyLate = showEarlyLate
EarlyLate.showMillisec = showMillisec
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

View File

@ -126,10 +126,10 @@ function HitFX.renderLasers(deltaTime, critCenterX, critCenterY, critRotation, c
end
-- Render
scale, _ = Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation)
local laserColor = {game.GetLaserColor(laser - 1)}
local x = cursors[laser - 1].pos * (1 / scale)
local x = cursors[laser - 1].pos
Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation)
laserState.Dome:render(deltaTime, {
centered = true,

View File

@ -1,4 +1,4 @@
require("common.gameconfig")
local VolforceWindow = require('components.volforceWindow');
local desw = 1080;
@ -13,9 +13,7 @@ local danBadgeImage = gfx.CreateSkinImage("dan.png", 0);
local idolFrameImage = gfx.CreateSkinImage("crew/frame.png", 0);
-- 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 username = game.GetSkinSetting('username') or '';
local drawBestDiff = function (deltaTime, score, bestReplay, y)
if not bestReplay then return end
@ -140,4 +138,4 @@ end
return {
render=render
}
}

View File

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

View File

@ -1,6 +1,36 @@
local json = require("common.json")
local sound = require('common.sound');
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 resX,resY = game.GetResolution()
@ -11,6 +41,15 @@ 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")
@ -24,6 +63,7 @@ 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;
@ -33,19 +73,20 @@ local missing_song = false;
local placeholderJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
local did_exit = false;
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
local irHeartbeatRequested = false;
local irText = ''
local grades = {
{["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)}
{["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)}
}
local badges = {
@ -76,9 +117,260 @@ 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;
@ -145,8 +437,6 @@ 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
@ -156,6 +446,7 @@ 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);
@ -163,121 +454,55 @@ draw_checkbox = function(text, x, y, hoverindex, current, can_click)
local sx = xmin - 40;
local sy = y - 15;
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
if can_click and 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.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()
gfx.FillColor(0, 236, 0);
gfx.Text(text, x, y)
gfx.Fill();
end
end;
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;
--look into user changing -- IMPORTANT !!!
draw_user = function(user, x, y , w, h, rank, breadx,bready)
local name = user.name
local showthing = false
if user.id == user_id then
name = name
end
if user.id == host then
name = name..' (host)'
elseif user.missing_map then
name = name..' (NO CHART)'
name = name
elseif user.ready then
name = name..' (ready)'
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);
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()
@ -300,89 +525,38 @@ function render_loading()
end
function render_info()
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
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, 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))
draw_diff_icon = function(diff, x, y, w, h)
difbar.render(deltaTime, x, y, 1, diff.difficulty, diff.level);
end
local doffset = 0;
local timer = 0;
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
local possy = 1095;
local possx = 150;
draw_diffs = function(diffs, x, y, w, h, selectedDiff)
local diffWidth = w/2.5
local diffHeight = w/2.5
local diffWidth = w/2
local diffHeight = w/2
local diffCount = #diffs
gfx.Scissor(x,y,w,h)
gfx.Scissor(x+84 + possx,y + possy,w/2.451,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, y, diffWidth, diffHeight, false)
draw_diff_icon(diff, xpos + possx, y + possy, diffWidth, diffHeight, false)
end
end
@ -391,21 +565,14 @@ 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, y, diffWidth, diffHeight, false)
draw_diff_icon(diff, xpos + possx, y + possy, 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, 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()
draw_diff_icon(diff, xpos + possx, y + possy, diffWidth, diffHeight, true)
gfx.ResetScissor()
draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5)
end
set_diff = function(oldDiff, newDiff)
@ -450,7 +617,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 Game)'
status = status..' (IN MATCH)'
end
if room.password then
status = status..' <P>'
@ -487,75 +654,51 @@ 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 jacket_size;
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()
-- 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_own_info()
user_setup()
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)
m_info_part()
m_part()
m_s_part()
m_bpm_part()
songjacket()
-- === 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
@ -563,75 +706,22 @@ function render_lobby(deltaTime)
end
gfx.Save()
gfx.BeginPath()
gfx.Translate(split/2 + song_x_off, 325+jacket_size/2)
gfx.Translate(481, 1303+jacket_size/2)
gfx.ImageRect(-jacket_size/2,-jacket_size/2,jacket_size,jacket_size,jacket,1,0)
if mouse_clipped(split/2 + song_x_off-jacket_size/2, 325, jacket_size,jacket_size) and host == user_id then
if mouse_clipped(481-jacket_size/2,1423+-jacket_size/2,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
@ -699,19 +789,18 @@ 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);
@ -744,7 +833,8 @@ render = function(deltaTime)
mposx,mposy = game.GetMousePos();
portrait = resY > resX
sound.stopMusic();
IR_Handle()
Sound.stopMusic();
doffset = doffset * 0.9
ioffset = ioffset * 0.9
@ -837,10 +927,11 @@ 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_STA then
if button == game.BUTTON_FXL and game.BUTTON_FXR then
if start_game_soon then
return
end
@ -870,10 +961,10 @@ button_pressed = function(button)
end
end
if button == game.BUTTON_FXL then
if button == game.BUTTON_BTA then
toggle_hard();
end
if button == game.BUTTON_FXR then
if button == game.BUTTON_BTB then
toggle_mirror();
end
end

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,7 @@ local Numbers = require('components.numbers')
local DiffRectangle = require('components.diff_rectangle');
local lang = require("language.call")
require('common.gameconfig')
local crew = game.GetSkinSetting("single_idol")
local creww = game.GetSkinSetting("single_idol")
local VolforceWindow = require('components.volforceWindow')
@ -48,8 +46,6 @@ 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);
@ -103,8 +99,7 @@ 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/9 exceed.png", 0)
gfx.CreateSkinImage("diff/8 vivid.png", 0)
}
local clearBadgeImages = {
@ -139,11 +134,7 @@ local clearBadgeImages = {
}
-- ANIMS
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..crew, 1 / 30, 0, true);
if not idolAnimation then
game.Log("Crew folder crew/anim/"..crew.." does not exist.", game.LOGGER_WARNING)
end
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true);
local transitionEnterScale = 0;
local idolAnimTransitionScale = 0;
@ -168,9 +159,7 @@ local BOTTOM_PANEL_TRANSTION_ENTER_OFFSET = 256;
local highScore;
-- gameplay table does not have a current username field, because why would it lmao
-- workaround: retrieve it directly from Main.cfg file
local username = GameConfig["MultiplayerUsername"] or game.GetSkinSetting('username') or '';
local username = game.GetSkinSetting('username');
local msg = game.GetSkinSetting("MSG");
local earlyLateBarsStats = {
@ -273,19 +262,17 @@ function drawTimingBar(y, value, max, type)
end
local drawIdol = function(deltaTime)
if idolAnimation then
local idolAnimTickRes = gfx.TickAnimation(idolAnimation, deltaTime);
if idolAnimTickRes == 1 then
gfx.GlobalAlpha(idolAnimTransitionScale);
idolAnimTransitionScale = idolAnimTransitionScale + 1 / 60;
if (GameConfig["AutoScoreScreenshot"] or idolAnimTransitionScale > 1) then
idolAnimTransitionScale = 1;
end
gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0);
gfx.GlobalAlpha(1);
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, desw, desh, idolAnimation, 1, 0);
gfx.GlobalAlpha(1);
end
end
@ -549,27 +536,25 @@ local drawBottomPanelContent = function(deltatime)
-- Draw appeal card
gfx.BeginPath();
gfx.ImageRect(bottomPanelX + 58, bottomPanelY + 277, 103, 132,
result.isSelf and appealCardImage or defaultCardImage, 1, 0);
appealCardImage, 1, 0);
-- Draw description
gfx.FontSize(22)
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
gfx.Text(result.isSelf and msg or ("Player "..result.displayIndex), bottomPanelX + 190, bottomPanelY + 282);
gfx.Text(msg, bottomPanelX + 190, bottomPanelY + 282);
-- Draw username
gfx.FontSize(28)
gfx.Text(result.playerName or username, bottomPanelX + 190, bottomPanelY + 314);
gfx.Text(username, bottomPanelX + 190, bottomPanelY + 314);
-- Draw dan badge
gfx.BeginPath();
gfx.ImageRect(bottomPanelX + 187, bottomPanelY + 362, 107, 29,
result.isSelf and danBadgeImage or defaultBadgeImage, 1, 0);
danBadgeImage, 1, 0);
-- Draw volforce
if result.isSelf then
VolforceWindow.render(0, bottomPanelX + 310, bottomPanelY + 355)
end
VolforceWindow.render(0, bottomPanelX + 310, bottomPanelY + 355)
-- Draw IR text
gfx.FontSize(22)
@ -646,7 +631,7 @@ end
local tickTransitions = function(deltaTime)
if not GameConfig["AutoScoreScreenshot"] and transitionEnterScale < 1 then
if transitionEnterScale < 1 then
transitionEnterScale = transitionEnterScale + deltaTime / 0.66 -- transition should last for that time in seconds
else
transitionEnterScale = 1
@ -663,7 +648,7 @@ local tickTransitions = function(deltaTime)
(1 - Easing.outQuad(transitionEnterScale)))
if not GameConfig["AutoScoreScreenshot"] and badgeLinesAnimScale < 1 then
if badgeLinesAnimScale < 1 then
badgeLinesAnimScale = badgeLinesAnimScale + deltaTime / 0.5 -- transition should last for that time in seconds
else
badgeLinesAnimScale = 0
@ -838,4 +823,4 @@ end
screenshot_captured = function(path)
game.PlaySample("shutter")
end
end

View File

@ -1,6 +1,7 @@
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)
@ -21,8 +22,7 @@ local ITEM_HEIGHT = 172
local specialFolders = {
{
keys = {
'SOUND VOLTEX BOOTH', 'SOUND VOLTEX I BOOTH', 'SDVX BOOTH',
'SOUND VOLTEX I', 'SDVX I',
'SOUND VOLTEX BOOTH', 'SDVX BOOTH', 'SOUND VOLTEX I', 'SDVX I',
'SOUND VOLTEX 1', 'SDVX 1', 'SDVX I BOOTH'
},
folderBg = gfx.CreateSkinImage(
@ -30,9 +30,7 @@ local specialFolders = {
}, {
keys = {
'SOUND VOLTEX INFINITE INFECTION', 'SDVX INFINITE INFECTION',
'SOUND VOLTEX II INFINITE INFECTION',
'SOUND VOLTEX II', 'SDVX II', 'SOUND VOLTEX 2', 'SDVX 2',
'SDVX 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',
@ -43,17 +41,16 @@ 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 IV HEAVENLY HAVEN',
'SOUND VOLTEX HEAVENLY HAVEN', 'SDVX HEAVENLY HAVEN',
'SOUND VOLTEX IV', 'SDVX IV', 'SOUND VOLTEX 4', 'SDVX 4', 'SDVX IV HEAVENLY HAVEN'
},
folderBg = gfx.CreateSkinImage(
@ -61,27 +58,26 @@ local specialFolders = {
}, {
keys = {
'SOUND VOLTEX VIVID WAVE', 'SDVX VIVID WAVE', 'SOUND VOLTEX V',
'SDVX V', 'SOUND VOLTEX 5', 'SDVX 5', 'SDVX V VIVID WAVE', 'SOUND VOLTEX V VIVID WAVE'
'SDVX V', 'SOUND VOLTEX 5', 'SDVX 5', 'SDVX 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',
'SOUND VOLTEX VI EXCEED GEAR'
'SDVX VI', 'SOUND VOLTEX 6', 'SDVX 6', 'SDVX 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)
@ -370,6 +366,7 @@ local drawFilterWheel = function (deltaTime)
if (game.GetSkinSetting('_currentScreen') == 'songwheel') then
SongSelectHeader.draw(deltaTime)
Footer.draw(deltaTime)
end
if (isFilterWheelActive ~= previousActiveState) then

View File

@ -6,13 +6,9 @@ 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)
@ -43,6 +39,8 @@ 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),
@ -52,7 +50,6 @@ 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 = {
@ -106,7 +103,6 @@ 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')
@ -115,8 +111,6 @@ 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',
@ -129,16 +123,13 @@ 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 = {} ---@type ServerScore[]|{}
local irLeaderboard = {}
local irLeaderboardsCache = {}
local transitionScrollScale = 0
@ -165,7 +156,6 @@ local transitionSearchInfoEnterScale = 0
local transitionSearchBackgroundAlpha = 0
local transitionSearchbarOffsetY = 0
local transitionSearchInfoOffsetY = 0
local transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
local transitionLaserScale = 0
local transitionLaserY = 0
@ -203,32 +193,32 @@ local resolutionChange = function(x, y)
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR)
end
local function getCorrectedIndex(from, offset)
local total = #songwheel.songs
function getCorrectedIndex(from, offset)
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
local index = from + offset
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
local indexesUntilEnd = total - from
index = offset - indexesUntilEnd -- this only happens if the offset is positive
end
if index > total then
indexesUntilEnd = total - from
index = offset - indexesUntilEnd -- this only happens if the offset is positive
end
return index
return index
end
local function getJacketImage(song)
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)
@ -238,12 +228,12 @@ local function getJacketImage(song)
return jacketCache[song.id]
end
local function getGradeImageForScore(score)
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
@ -253,30 +243,31 @@ local function getGradeImageForScore(score)
return gradeImage
end
local function drawLaserAnim()
function drawLaserAnim()
gfx.Save()
gfx.BeginPath()
gfx.Scissor(0, transitionLaserY, desw, 100)
gfx.ImageRect(0, 0, desw, desh, laserAnimBaseImage, 1, 0)
gfx.Restore()
end
local function drawBackground(deltaTime)
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)
@ -290,7 +281,7 @@ local 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()
@ -307,9 +298,7 @@ local function drawBackground(deltaTime)
end
---@param song SongWheelSong
---@param y number
local function drawSong(song, y)
function drawSong(song, y)
if (not song) then return end
local songX = desw/2+28
@ -320,14 +309,14 @@ local 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()
@ -336,7 +325,7 @@ local 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)
@ -356,7 +345,7 @@ local 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
@ -369,7 +358,7 @@ local 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
@ -381,13 +370,13 @@ local function drawSong(song, y)
gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0)
-- Draw top 50 label if applicable
if (top50diffs[selectedSongDifficulty.hash]) then
if (top50diffs[selectedSongDifficulty.id]) then
gfx.BeginPath()
gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0)
end
end
local function drawSongList()
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
@ -400,7 +389,7 @@ local 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)
@ -414,8 +403,127 @@ local function drawSongList()
gfx.GlobalAlpha(1)
end
---@param diff SongWheelDifficulty
local function drawLocalLeaderboard(diff)
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)
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
gfx.FontSize(26)
@ -440,49 +548,39 @@ local 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(username or "-", sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
gfx.Text(game.GetSkinSetting("username"), sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
gfx.BeginPath()
local scoreText = score and tostring(score) or "- - - - - - - -"
gfx.Text(scoreText, sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
gfx.Text((diff.scores[i]) and diff.scores[i].score or "- - - - - - - -", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
end
end
local function drawIrLeaderboard()
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()
@ -518,8 +616,8 @@ local 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()
@ -528,7 +626,7 @@ local 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)
@ -539,130 +637,7 @@ local function drawIrLeaderboard()
end
end
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)
function drawFilterInfo(deltatime)
gfx.LoadSkinFont('NotoSans-Regular.ttf')
if (songwheel.searchInputActive) then
@ -671,17 +646,17 @@ local 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)
@ -692,7 +667,7 @@ local function drawFilterInfo(deltatime)
gfx.Text(sortOptionLabel or '', desw-150, 130)
end
local function drawCursor()
function drawCursor()
if isFilterWheelActive or transitionLeaveScale ~= 0 then return false end
gfx.BeginPath()
@ -703,7 +678,7 @@ local function drawCursor()
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
end
local function drawSearch()
function drawSearch()
if (not songwheel.searchInputActive and searchPreviousActiveState) then
searchPreviousActiveState = false
game.PlaySample('sort_wheel/enter.wav')
@ -711,7 +686,7 @@ local 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
@ -737,7 +712,7 @@ local 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
@ -773,7 +748,7 @@ local function drawSearch()
gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2)
end
local function drawScrollbar()
function drawScrollbar()
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
-- Scrollbar Background
@ -816,33 +791,7 @@ local function drawScrollbar()
end
end
---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)
function refreshIrLeaderboard(deltaTime)
if not IRData.Active then
return
end
@ -871,10 +820,56 @@ local function refreshIrLeaderboard(deltaTime)
end
irRequestStatus = 2 -- Loading
-- onIrLeaderboardFetched({
-- statusCode = 20,
-- body = {}
-- })
IR.Leaderboard(diff.hash, 'best', 4, onIrLeaderboardFetched)
end
local function tickTransitions(deltaTime)
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)
if transitionScrollScale < 1 then
transitionScrollScale = transitionScrollScale + deltaTime / 0.1 -- transition should last for that time in seconds
else
@ -890,7 +885,7 @@ local 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
@ -935,6 +930,8 @@ local function tickTransitions(deltaTime)
end
end
transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
-- Grade alpha
if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then
transitionAfterscrollGradeAlpha = 0.5
@ -943,7 +940,7 @@ local function tickTransitions(deltaTime)
else
transitionAfterscrollGradeAlpha = 0
end
-- Badge alpha
if transitionAfterscrollScale >= 0.032 and transitionAfterscrollScale < 0.035 then
transitionAfterscrollBadgeAlpha = 0.5
@ -965,7 +962,7 @@ local function tickTransitions(deltaTime)
else
transitionAfterscrollTextSongArtist = 1
end
-- Difficulties alpha
if transitionAfterscrollScale < 0.025 then
transitionAfterscrollDifficultiesAlpha = math.min(1, transitionAfterscrollScale / 0.025)
@ -985,7 +982,7 @@ local 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
@ -1002,17 +999,25 @@ local 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
local songBpmStr = songwheel.songs[selectedIndex].bpm
local songBpmStrs = common.split(songBpmStr, '-')
local minBpm = tonumber(songBpmStrs[1]) -- Lowest bpm value
songBpm = minBpm or songBpm
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
end
transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds
@ -1052,41 +1057,7 @@ local function tickTransitions(deltaTime)
end
end
---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)
draw_songwheel = function(deltaTime)
drawBackground(deltaTime)
drawSongList()
@ -1094,9 +1065,6 @@ local draw_songwheel = function(deltaTime)
isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1
drawData()
drawRadar()
drawCursor()
drawFilterInfo(deltaTime)
@ -1111,16 +1079,13 @@ local 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)
@ -1128,13 +1093,6 @@ 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()
@ -1146,7 +1104,6 @@ render = function (deltaTime)
refreshIrLeaderboard(deltaTime)
end
---@diagnostic disable-next-line:lowercase-global
songs_changed = function (withAll)
irLeaderboardsCache = {} -- Reset LB cache
@ -1156,70 +1113,57 @@ 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]
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
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
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
updateRadar = true
game.PlaySample('song_wheel/cursor_change.wav')
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

View File

@ -40,10 +40,6 @@ 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)
@ -150,8 +146,6 @@ local playedBgm = false
local triggerServiceMenu = false
Footer.set{enableTimer=false}
local function setButtonActions()
buttons[1].action = Menu.Challenges
buttons[2].action = Menu.Multiplayer
@ -312,18 +306,16 @@ local function draw_titlescreen(deltaTime)
gfx.BeginPath()
Background.draw(deltaTime)
if resources.anims.idolAnimation then
local idolAnimTickRes = gfx.TickAnimation(resources.anims.idolAnimation, deltaTime)
if idolAnimTickRes == 1 then
gfx.GlobalAlpha(idolAnimTransitionScale)
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)
end
gfx.BeginPath()
gfx.ImageRect(0, 0, Dim.design.width, Dim.design.height, resources.anims.idolAnimation, 1, 0)
gfx.GlobalAlpha(1)
end
-- Draw selector background

View File

@ -16,5 +16,5 @@ uniform mat4 world;
void main()
{
fsTex = inTex;
gl_Position = proj * camera * world * vec4(inPos.x, inPos.y * 5.7, 0, 1);
gl_Position = proj * camera * world * vec4(inPos.x, inPos.y * 3.8, 0, 1);
}

View File

@ -20,27 +20,10 @@ 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;
}
@ -57,26 +40,8 @@ void main() {
x += (laserSize / 2);
float y = 0.25 * ceil(float(hitState)) + 0.01;
vec4 channels = texture(mainTex, vec2(x, y));
vec3 baseColor = color.rgb * channels.g;
vec3 baseHsv = rgb2hsv(baseColor);
float 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);
target = texture(mainTex, vec2(x, y)) * vec4(visiblityMultiplier,visiblityMultiplier,visiblityMultiplier,1);
}

View File

@ -1,66 +0,0 @@
#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);
}

View File

@ -1,22 +0,0 @@
#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);
}

View File

@ -1,11 +0,0 @@
#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;
}

View File

@ -1,94 +0,0 @@
#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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB