Effect Radar Implementation v1 #46
|
@ -97,5 +97,14 @@
|
||||||
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
|
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"separator_g": {},
|
||||||
|
"Experimental features": { "type": "label" },
|
||||||
|
|
||||||
|
"songselect_showEffectRadar": {
|
||||||
|
"label": "Show Effect Radar for compatible songs (VERY WIP)",
|
||||||
|
"type": "bool",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,44 +23,50 @@ IRData = {}
|
||||||
---@field serverTime integer
|
---@field serverTime integer
|
||||||
---@field serverName string
|
---@field serverName string
|
||||||
---@field irVersion string
|
---@field irVersion string
|
||||||
IRHeartbeatResponseBody = {}
|
|
||||||
|
|
||||||
---@class IRRecordResponseBody
|
---@class IRRecordResponseBody
|
||||||
---@field record ServerScore
|
---@field record ServerScore
|
||||||
IRRecordResponseBody = {}
|
|
||||||
|
|
||||||
---@class IRLeaderboardResponseBody
|
---@alias IRLeaderboardResponseBody ServerScore[]
|
||||||
---@field scores ServerScore[]
|
|
||||||
IRLeaderboardResponseBody = {}
|
|
||||||
|
|
||||||
---@class IRResponse
|
---@class IRResponse
|
||||||
---@field statusCode integer
|
---@field statusCode integer
|
||||||
---@field description string
|
---@field description string
|
||||||
---@field body nil|IRHeartbeatResponseBody|IRRecordResponseBody|IRLeaderboardResponseBody
|
|
||||||
IRResponse = {}
|
---@class IRHeartbeatResponse : IRResponse
|
||||||
|
---@field body IRHeartbeatResponseBody
|
||||||
|
|
||||||
|
---@class IRChartTrackedResponse : IRResponse
|
||||||
|
---@field body {}
|
||||||
|
|
||||||
|
---@class IRRecordResponse : IRResponse
|
||||||
|
---@field body IRRecordResponseBody
|
||||||
|
|
||||||
|
---@class IRLeaderboardResponse : IRResponse
|
||||||
|
---@field body ServerScore[]
|
||||||
|
|
||||||
-- Performs a Heartbeat request.
|
-- Performs a Heartbeat request.
|
||||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
---@param callback fun(res: IRHeartbeatResponse) # Callback function receives IRResponse as it's first parameter
|
||||||
local function Heartbeat(callback) end
|
local function Heartbeat(callback) end
|
||||||
|
|
||||||
-- Performs a Chart Tracked request for the chart with the provided hash.
|
-- Performs a Chart Tracked request for the chart with the provided hash.
|
||||||
---@param hash string # song hash
|
---@param hash string # song hash
|
||||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
---@param callback fun(res: IRChartTrackedResponse) # Callback function receives IRResponse as it's first parameter
|
||||||
local function ChartTracked(hash, callback) end
|
local function ChartTracked(hash, callback) end
|
||||||
|
|
||||||
-- Performs a Record request for the chart with the provided hash.
|
-- Performs a Record request for the chart with the provided hash.
|
||||||
---@param hash string # song hash
|
---@param hash string # song hash
|
||||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
---@param callback fun(res: IRRecordResponse) # Callback function receives IRResponse as it's first parameter
|
||||||
local function Record(hash, callback) end
|
local function Record(hash, callback) end
|
||||||
|
|
||||||
-- Performs a Leaderboard request for the chart with the provided hash, with parameters mode and n.
|
-- Performs a Leaderboard request for the chart with the provided hash, with parameters mode and n.
|
||||||
---@param hash string # song hash
|
---@param hash string # song hash
|
||||||
---@param mode "best"|"rivals" # request leaderboard mode
|
---@param mode "best"|"rivals" # request leaderboard mode
|
||||||
---@param n integer # limit the number of requested scores
|
---@param n integer # limit the number of requested scores
|
||||||
---@param callback fun(res: IRResponse) # Callback function receives IRResponse as it's first parameter
|
---@param callback fun(res: IRLeaderboardResponse) # Callback function receives IRResponse as it's first parameter
|
||||||
local function Leaderboard(hash, mode, n, callback) end
|
local function Leaderboard(hash, mode, n, callback) end
|
||||||
|
|
||||||
---@type table
|
---@class IR
|
||||||
IR = {
|
IR = {
|
||||||
Heartbeat = Heartbeat,
|
Heartbeat = Heartbeat,
|
||||||
ChartTracked = ChartTracked,
|
ChartTracked = ChartTracked,
|
||||||
|
|
|
@ -267,7 +267,7 @@ LoadSharedSkinTexture = function(name, path) end
|
||||||
|
|
||||||
-- Loads a font fromt the specified filename
|
-- Loads a font fromt the specified filename
|
||||||
-- Sets it as the current font if it is already loaded
|
-- Sets it as the current font if it is already loaded
|
||||||
---@param name? string
|
---@param name string
|
||||||
---@param filename string
|
---@param filename string
|
||||||
LoadFont = function(name, filename) end
|
LoadFont = function(name, filename) end
|
||||||
|
|
||||||
|
@ -280,11 +280,10 @@ LoadFont = function(name, filename) end
|
||||||
---@return any # returns `placeholder` until the image is loaded
|
---@return any # returns `placeholder` until the image is loaded
|
||||||
LoadImageJob = function(filepath, placeholder, w, h) end
|
LoadImageJob = function(filepath, placeholder, w, h) end
|
||||||
|
|
||||||
-- Loads a font from `skins/<skin>/textures/<path>`
|
-- Loads a font from `skins/<skin>/fonts/<name>`
|
||||||
-- Sets it as the current font if it is already loaded
|
-- Sets it as the current font if it is already loaded
|
||||||
---@param name? string
|
---@param name string
|
||||||
---@param filename string
|
LoadSkinFont = function(name) end
|
||||||
LoadSkinFont = function(name, filename) end
|
|
||||||
|
|
||||||
-- Loads an image outside of the main thread to prevent rendering lock-up
|
-- 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
|
-- Image will be loaded at original size unless `w` and `h` are provided
|
||||||
|
@ -472,7 +471,7 @@ UpdateImagePattern = function(pattern, sx, sy, ix, iy, angle, alpha) end
|
||||||
---@param size? integer
|
---@param size? integer
|
||||||
UpdateLabel = function(label, text, size) end
|
UpdateLabel = function(label, text, size) end
|
||||||
|
|
||||||
---@type table
|
---@class gfx
|
||||||
gfx = {
|
gfx = {
|
||||||
BLEND_ZERO = 1,
|
BLEND_ZERO = 1,
|
||||||
BLEND_ONE = 2,
|
BLEND_ONE = 2,
|
||||||
|
|
|
@ -1,117 +1,4 @@
|
||||||
-- Adds a texture that was loaded with `gfx.LoadSharedTexture` to the material that can be used in the shader code
|
---@diagnostic disable:missing-return
|
||||||
---@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
|
---@class ShadedMesh
|
||||||
ShadedMesh = {
|
ShadedMesh = {
|
||||||
|
@ -125,57 +12,148 @@ ShadedMesh = {
|
||||||
PRIM_LINELIST = 3,
|
PRIM_LINELIST = 3,
|
||||||
PRIM_LINESTRIP = 4,
|
PRIM_LINESTRIP = 4,
|
||||||
PRIM_POINTLIST = 5,
|
PRIM_POINTLIST = 5,
|
||||||
|
|
||||||
AddSharedTexture = AddSharedTexture,
|
|
||||||
AddSkinTexture = AddSkinTexture,
|
|
||||||
AddTexture = AddTexture,
|
|
||||||
Draw = Draw,
|
|
||||||
GetPosition = GetPosition,
|
|
||||||
GetRotation = GetRotation,
|
|
||||||
GetScale = GetScale,
|
|
||||||
SetBlendMode = SetBlendMode,
|
|
||||||
SetData = SetData,
|
|
||||||
SetOpaque = SetOpaque,
|
|
||||||
SetParam = SetParam,
|
|
||||||
SetParamVec2 = SetParamVec2,
|
|
||||||
SetParamVec3 = SetParamVec3,
|
|
||||||
SetParamVec4 = SetParamVec4,
|
|
||||||
SetPosition = SetPosition,
|
|
||||||
SetPrimitiveType = SetPrimitiveType,
|
|
||||||
SetRotation = SetRotation,
|
|
||||||
SetScale = SetScale,
|
|
||||||
SetWireframe = SetWireframe,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
-- Gets the length of the mesh
|
-- Adds a texture that was loaded with `gfx.LoadSharedTexture` to the material that can be used in the shader code
|
||||||
---@return number length
|
---@param uniformName string
|
||||||
GetLength = function() end
|
---@param textureName string
|
||||||
|
function ShadedMesh:AddSharedTexture(uniformName, textureName) end
|
||||||
|
|
||||||
-- Sets the y-scale of the mesh based on its length
|
-- Adds a texture to the material that can be used in the shader code
|
||||||
-- Useful for creating fake buttons which may have variable length based on duration
|
---@param uniformName string
|
||||||
---@param length number
|
---@param path string # prepended with `skins/<skin>/textures/`
|
||||||
ScaleToLength = function(length) end
|
function ShadedMesh:AddSkinTexture(uniformName, path) end
|
||||||
|
|
||||||
-- Stops meshes beyond the track from being rendered if `doClip`
|
-- Adds a texture to the material that can be used in the shader code
|
||||||
---@param doClip boolean
|
---@param uniformName string
|
||||||
SetClipWithTrack = function(doClip) end
|
---@param path string
|
||||||
|
function ShadedMesh:AddTexture(uniformName, path) end
|
||||||
|
|
||||||
-- Sets the length (in the y-direction relative to the track) of the mesh
|
-- Gets the translation of the mesh
|
||||||
---@param length number # Optional constants: `BUTTON_TEXTURE_LENGTH`, `FXBUTTON_TEXTURE_LENGTH`, and `TRACK_LENGTH`
|
---@return number x, number y, number z
|
||||||
SetLength = function(length) end
|
function ShadedMesh:GetPosition() end
|
||||||
|
|
||||||
|
-- Gets the rotation (in degrees) of the mesh
|
||||||
|
---@return number roll, number yaw, number pitch
|
||||||
|
function ShadedMesh:GetRotation() end
|
||||||
|
|
||||||
|
-- Gets the scale of the mesh
|
||||||
|
---@return number x, number y, number z
|
||||||
|
function ShadedMesh:GetScale() end
|
||||||
|
|
||||||
|
-- Sets the blending mode
|
||||||
|
---@param mode integer # options also available as fields of the object prefixed with `BLEND`
|
||||||
|
-- `Normal` = 0 (default)
|
||||||
|
-- `Additive` = 1
|
||||||
|
-- `Multiply` = 2
|
||||||
|
function ShadedMesh:SetBlendMode(mode) end
|
||||||
|
|
||||||
|
-- Sets the geometry data
|
||||||
|
---@param data table # array of vertices in clockwise order starting from the top left e.g.
|
||||||
|
-- ```
|
||||||
|
-- {
|
||||||
|
-- { { 0, 0 }, { 0, 0 } },
|
||||||
|
-- { { 50, 0 }, { 1, 0 } },
|
||||||
|
-- { { 50, 50 }, { 1, 1 } },
|
||||||
|
-- { { 0, 50 }, { 0, 1 } },
|
||||||
|
-- }
|
||||||
|
-- ```
|
||||||
|
function ShadedMesh:SetData(data) end
|
||||||
|
|
||||||
|
-- Sets the material is opaque or non-opaque (default)
|
||||||
|
---@param opaque boolean
|
||||||
|
function ShadedMesh:SetOpaque(opaque) end
|
||||||
|
|
||||||
|
-- Sets the value of the specified uniform
|
||||||
|
---@param uniformName string
|
||||||
|
---@param value number # `float`
|
||||||
|
function ShadedMesh:SetParam(uniformName, value) end
|
||||||
|
|
||||||
|
-- Sets the value of the specified 2d vector uniform
|
||||||
|
---@param uniformName string
|
||||||
|
---@param x number # `float`
|
||||||
|
---@param y number # `float`
|
||||||
|
function ShadedMesh:SetParamVec2(uniformName, x, y) end
|
||||||
|
|
||||||
|
-- Sets the value of the specified 3d vector uniform
|
||||||
|
---@param uniformName string
|
||||||
|
---@param x number # `float`
|
||||||
|
---@param y number # `float`
|
||||||
|
---@param z number # `float`
|
||||||
|
function ShadedMesh:SetParamVec3(uniformName, x, y, z) end
|
||||||
|
|
||||||
|
-- Sets the value of the specified 4d vector uniform
|
||||||
|
---@param uniformName string
|
||||||
|
---@param x number # `float`
|
||||||
|
---@param y number # `float`
|
||||||
|
---@param z number # `float`
|
||||||
|
---@param w number # `float`
|
||||||
|
function ShadedMesh:SetParamVec4(uniformName, x, y, z, w) end
|
||||||
|
|
||||||
|
-- Sets the translation for the mesh
|
||||||
|
-- Relative to the screen for `ShadedMesh`
|
||||||
|
-- Relative to the center of the crit line for `ShadedMeshOnTrack`
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param z? number # Default `0`
|
||||||
|
function ShadedMesh:SetPosition(x, y, z) end
|
||||||
|
|
||||||
|
-- Sets the format for geometry data provided by `SetData`
|
||||||
|
---@param type integer # options also available as fields of the object prefixed with `PRIM`
|
||||||
|
-- `TriangleList` = 0 (default)
|
||||||
|
-- `TriangleStrip` = 1
|
||||||
|
-- `TriangleFan` = 2
|
||||||
|
-- `LineList` = 3
|
||||||
|
-- `LineStrip` = 4
|
||||||
|
-- `PointList` = 5
|
||||||
|
function ShadedMesh:SetPrimitiveType(type) end
|
||||||
|
|
||||||
|
-- Sets the rotation (in degrees) of the mesh
|
||||||
|
-- **WARNING:** For `ShadedMesh`, pitch and yaw may clip, rendering portions or the entire mesh invisible
|
||||||
|
---@param roll number
|
||||||
|
---@param yaw? number # Default `0`
|
||||||
|
---@param pitch? number # Default `0`
|
||||||
|
function ShadedMesh:SetRotation(roll, yaw, pitch) end
|
||||||
|
|
||||||
|
-- Sets the scale of the mesh
|
||||||
|
---@param x number
|
||||||
|
---@param y number
|
||||||
|
---@param z? number # Default `0`
|
||||||
|
function ShadedMesh:SetScale(x, y, z) end
|
||||||
|
|
||||||
|
-- Sets the wireframe mode of the object (does not render texture)
|
||||||
|
-- Useful for debugging models or geometry shaders
|
||||||
|
---@param useWireframe boolean
|
||||||
|
function ShadedMesh:SetWireframe(useWireframe) end
|
||||||
|
|
||||||
|
-- Renders the `ShadedMesh` object
|
||||||
|
function ShadedMesh:Draw() end
|
||||||
|
|
||||||
-- Uses an existing game mesh
|
|
||||||
---@param meshName string # Options: `'button'`, `'fxbutton'`, and `'track'`
|
|
||||||
UseGameMesh = function(meshName) end
|
|
||||||
|
|
||||||
---@class ShadedMeshOnTrack : ShadedMesh
|
---@class ShadedMeshOnTrack : ShadedMesh
|
||||||
---@field BUTTON_TEXTURE_LENGTH number
|
---@field BUTTON_TEXTURE_LENGTH number
|
||||||
---@field FXBUTTON_TEXTURE_LENGTH number
|
---@field FXBUTTON_TEXTURE_LENGTH number
|
||||||
---@field TRACK_LENGTH number
|
---@field TRACK_LENGTH number
|
||||||
ShadedMeshOnTrack = {
|
ShadedMeshOnTrack = {
|
||||||
GetLength = GetLength,
|
|
||||||
UseGameMesh = UseGameMesh,
|
|
||||||
ScaleToLength = ScaleToLength,
|
|
||||||
SetClipWithTrack = SetClipWithTrack,
|
|
||||||
SetLength = SetLength,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
-- Gets the length of the mesh
|
||||||
|
---@return number length
|
||||||
|
function ShadedMeshOnTrack:GetLength() end
|
||||||
|
|
||||||
|
-- Sets the y-scale of the mesh based on its length
|
||||||
|
-- Useful for creating fake buttons which may have variable length based on duration
|
||||||
|
---@param length number
|
||||||
|
function ShadedMeshOnTrack:ScaleToLength(length) end
|
||||||
|
|
||||||
|
-- Stops meshes beyond the track from being rendered if `doClip`
|
||||||
|
---@param doClip boolean
|
||||||
|
function ShadedMeshOnTrack:SetClipWithTrack(doClip) end
|
||||||
|
|
||||||
|
-- Sets the length (in the y-direction relative to the track) of the mesh
|
||||||
|
---@param length number # Optional constants: `BUTTON_TEXTURE_LENGTH`, `FXBUTTON_TEXTURE_LENGTH`, and `TRACK_LENGTH`
|
||||||
|
function ShadedMeshOnTrack:SetLength(length) end
|
||||||
|
|
||||||
|
-- Uses an existing game mesh
|
||||||
|
---@param meshName string # Options: `'button'`, `'fxbutton'`, and `'track'`
|
||||||
|
function ShadedMeshOnTrack:UseGameMesh(meshName) end
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---@diagnostic disable: lowercase-global
|
||||||
-- songwheel `songwheel` table
|
-- songwheel `songwheel` table
|
||||||
|
|
||||||
---@class SongWheelScore
|
---@class SongWheelScore
|
||||||
|
@ -35,7 +36,7 @@ SongWheelDifficulty = {}
|
||||||
---@class SongWheelSong
|
---@class SongWheelSong
|
||||||
---@field artist string # Chart artist
|
---@field artist string # Chart artist
|
||||||
---@field difficulties SongWheelDifficulty[] # Array of difficulties for the current song
|
---@field difficulties SongWheelDifficulty[] # Array of difficulties for the current song
|
||||||
---@field bpm number # Chart BPM
|
---@field bpm string # Chart BPM
|
||||||
---@field id integer # Song id, unique static identifier
|
---@field id integer # Song id, unique static identifier
|
||||||
---@field path string # Full filepath to the chart folder on the disk
|
---@field path string # Full filepath to the chart folder on the disk
|
||||||
---@field title string # Chart title
|
---@field title string # Chart title
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,74 @@
|
||||||
|
local util = require("common.util")
|
||||||
|
|
||||||
|
---@class CColorRGBA
|
||||||
|
ColorRGBA = {
|
||||||
|
---Create a new Color instance
|
||||||
|
---@param r integer # red or monochrome value
|
||||||
|
---@param g? integer # green value
|
||||||
|
---@param b? integer # blue value
|
||||||
|
---@param a? integer # alpha value, default 255
|
||||||
|
---@return ColorRGBA
|
||||||
|
new = function (r, g , b, a)
|
||||||
|
---@class ColorRGBA : CColorRGBA
|
||||||
|
---@field r integer
|
||||||
|
---@field g integer
|
||||||
|
---@field b integer
|
||||||
|
---@field a integer
|
||||||
|
local o = {
|
||||||
|
r = r or 0,
|
||||||
|
g = g or r,
|
||||||
|
b = b or r,
|
||||||
|
a = a or 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(o, ColorRGBA)
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
---Mix two colors
|
||||||
|
---@param color1 ColorRGBA
|
||||||
|
---@param color2 ColorRGBA
|
||||||
|
---@param factor number
|
||||||
|
---@return ColorRGBA
|
||||||
|
mix = function (color1, color2, factor)
|
||||||
|
local r = math.floor(util.mix(color1.r, color2.r, factor))
|
||||||
|
local g = math.floor(util.mix(color1.g, color2.g, factor))
|
||||||
|
local b = math.floor(util.mix(color1.b, color2.b, factor))
|
||||||
|
local a = math.floor(util.mix(color1.a, color2.a, factor))
|
||||||
|
return ColorRGBA.new(r, g, b, a)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorRGBA.__index = ColorRGBA
|
||||||
|
ColorRGBA.BLACK = ColorRGBA.new(0)
|
||||||
|
ColorRGBA.GREY = ColorRGBA.new(128)
|
||||||
|
ColorRGBA.WHITE = ColorRGBA.new(255)
|
||||||
|
ColorRGBA.RED = ColorRGBA.new(255, 0, 0)
|
||||||
|
ColorRGBA.GREEN = ColorRGBA.new(0, 255, 0)
|
||||||
|
ColorRGBA.BLUE = ColorRGBA.new(0, 0, 255)
|
||||||
|
ColorRGBA.YELLOW = ColorRGBA.new(255, 255, 0)
|
||||||
|
ColorRGBA.CYAN = ColorRGBA.new(0, 255, 255)
|
||||||
|
ColorRGBA.MAGENTA = ColorRGBA.new(255, 0, 255)
|
||||||
|
|
||||||
|
---Split to components
|
||||||
|
---@return integer # red
|
||||||
|
---@return integer # green
|
||||||
|
---@return integer # blue
|
||||||
|
---@return integer # alpha
|
||||||
|
function ColorRGBA:components()
|
||||||
|
---@cast self ColorRGBA
|
||||||
|
|
||||||
|
return self.r, self.g, self.b, self.a
|
||||||
|
end
|
||||||
|
|
||||||
|
---Split to components scaled to [0.0, 1.0]
|
||||||
|
---@return number # red
|
||||||
|
---@return number # green
|
||||||
|
---@return number # blue
|
||||||
|
---@return number # alpha
|
||||||
|
function ColorRGBA:componentsFloat()
|
||||||
|
---@cast self ColorRGBA
|
||||||
|
local scale = 255
|
||||||
|
|
||||||
|
return self.r / scale, self.g / scale, self.b / scale, self.a / scale
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
---@class CPoint2D
|
||||||
|
Point2D = {
|
||||||
|
---Create a Point2D instance
|
||||||
|
---@param x? number # default 0.0
|
||||||
|
---@param y? number # default 0.0
|
||||||
|
---@return Point2D
|
||||||
|
new = function(x, y)
|
||||||
|
---@class Point2D : CPoint2D
|
||||||
|
---@field x number
|
||||||
|
---@field y number
|
||||||
|
local o = {
|
||||||
|
x = x + .0 or .0,
|
||||||
|
y = y + .0 or .0,
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(o, Point2D)
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
Point2D.__index = Point2D
|
||||||
|
Point2D.ZERO = Point2D.new(0, 0)
|
||||||
|
|
||||||
|
function Point2D:coords()
|
||||||
|
---@cast self Point2D
|
||||||
|
|
||||||
|
return self.x, self.y
|
||||||
|
end
|
|
@ -59,6 +59,10 @@ local function lerp(x, x0, y0, x1, y1)
|
||||||
return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
return y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function mix(x, y, a)
|
||||||
|
return (1 - a) * x + a * y
|
||||||
|
end
|
||||||
|
|
||||||
--modulo operation for index value
|
--modulo operation for index value
|
||||||
local function modIndex(index, mod)
|
local function modIndex(index, mod)
|
||||||
return (index - 1) % mod + 1
|
return (index - 1) % mod + 1
|
||||||
|
@ -75,6 +79,41 @@ local function firstAlphaNum(s)
|
||||||
return '';
|
return '';
|
||||||
end
|
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 {
|
return {
|
||||||
split = split,
|
split = split,
|
||||||
filter = filter,
|
filter = filter,
|
||||||
|
@ -84,6 +123,10 @@ return {
|
||||||
roundToZero = roundToZero,
|
roundToZero = roundToZero,
|
||||||
areaOverlap = areaOverlap,
|
areaOverlap = areaOverlap,
|
||||||
lerp = lerp,
|
lerp = lerp,
|
||||||
|
mix = mix,
|
||||||
modIndex = modIndex,
|
modIndex = modIndex,
|
||||||
firstAlphaNum = firstAlphaNum,
|
firstAlphaNum = firstAlphaNum,
|
||||||
|
dump = dump,
|
||||||
|
all = all,
|
||||||
|
any = any
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,619 @@
|
||||||
|
--[[
|
||||||
|
S2 song attribute radar component
|
||||||
|
Original code thanks to RealFD, he's a real homie
|
||||||
|
]]
|
||||||
|
|
||||||
|
require("common.globals")
|
||||||
|
require("api.point2d")
|
||||||
|
require("api.color")
|
||||||
|
|
||||||
|
local Dim = require("common.dimensions")
|
||||||
|
local Util = require("common.util")
|
||||||
|
|
||||||
|
Dim.updateResolution()
|
||||||
|
|
||||||
|
local RADAR_PURPLE = ColorRGBA.new(238, 130, 238)
|
||||||
|
local RADAR_MAGENTA = ColorRGBA.new(191, 70, 235)
|
||||||
|
local RADAR_GREEN = ColorRGBA.new(0, 255, 100)
|
||||||
|
|
||||||
|
local maxScaleFactor = 1.8
|
||||||
|
|
||||||
|
---@param p1 Point2D
|
||||||
|
---@param p2 Point2D
|
||||||
|
---@param width number
|
||||||
|
---@param color ColorRGBA
|
||||||
|
local function drawLine(p1, p2, width, color)
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.MoveTo(p1:coords())
|
||||||
|
gfx.LineTo(p2:coords())
|
||||||
|
gfx.StrokeColor(color:components())
|
||||||
|
gfx.StrokeWidth(width)
|
||||||
|
gfx.Stroke()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param pos Point2D
|
||||||
|
---@param text string
|
||||||
|
---@param outlineWidth number
|
||||||
|
---@param color ColorRGBA
|
||||||
|
local function renderOutlinedText(pos, text, outlineWidth, color)
|
||||||
|
local x, y = pos:coords()
|
||||||
|
|
||||||
|
local dimColor = color:mix(ColorRGBA.BLACK, 0.8)
|
||||||
|
gfx.FillColor(dimColor:components());
|
||||||
|
gfx.Text(text, x - outlineWidth, y + outlineWidth);
|
||||||
|
gfx.Text(text, x - outlineWidth, y - outlineWidth);
|
||||||
|
gfx.Text(text, x + outlineWidth, y + outlineWidth);
|
||||||
|
gfx.Text(text, x + outlineWidth, y - outlineWidth);
|
||||||
|
|
||||||
|
gfx.FillColor(color:components());
|
||||||
|
gfx.Text(text, x, y);
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param pos Point2D
|
||||||
|
---@param graphdata table
|
||||||
|
local function drawDebugText(pos, graphdata)
|
||||||
|
local color = ColorRGBA.WHITE
|
||||||
|
gfx.Save()
|
||||||
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_CENTER)
|
||||||
|
--renderOutlinedText(x, 20, '"' .. txtFilePath .. '"', 1, 255, 255, 255)
|
||||||
|
renderOutlinedText(pos, "NOTES = " .. graphdata.notes, 1, color)
|
||||||
|
renderOutlinedText(pos, "PEAK = " .. graphdata.peak, 1, color)
|
||||||
|
renderOutlinedText(pos, "TSUMAMI = " .. graphdata.tsumami, 1, color)
|
||||||
|
renderOutlinedText(pos, "TRICKY = " .. graphdata.tricky, 1, color)
|
||||||
|
renderOutlinedText(pos, "ONE-HAND = " .. graphdata.onehand, 1, color)
|
||||||
|
renderOutlinedText(pos, "HAND-TRIP = " .. graphdata.handtrip, 1, color)
|
||||||
|
--renderOutlinedText(pos, "NOTES (Relative) = " .. graphdata.notes_relative, 1, color)
|
||||||
|
--renderOutlinedText(pos, "TOTAL-MESURES = " .. graphdata.measures, 1, color)
|
||||||
|
gfx.Restore()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class CRadarAttributes
|
||||||
|
RadarAttributes = {
|
||||||
|
---Create RadarAttributes instance
|
||||||
|
---@param text? string # default ""
|
||||||
|
---@param offset? Point2D # default (0, 0)
|
||||||
|
---@param color? ColorRGBA # default BLACK
|
||||||
|
---@param align? integer # gfx.TEXT_ALIGN_<...> values, default gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE
|
||||||
|
---@return RadarAttributes
|
||||||
|
new = function (text, offset, color, align)
|
||||||
|
---@class RadarAttributes
|
||||||
|
---@field text string
|
||||||
|
---@field offset Point2D
|
||||||
|
---@field color ColorRGBA
|
||||||
|
local o = {
|
||||||
|
text = text or "",
|
||||||
|
offset = offset or Point2D.ZERO,
|
||||||
|
color = color or ColorRGBA.BLACK,
|
||||||
|
align = align or gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(o, RadarAttributes)
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
}
|
||||||
|
RadarAttributes.__index = RadarAttributes
|
||||||
|
|
||||||
|
|
||||||
|
---@class CRadar
|
||||||
|
Radar = {
|
||||||
|
---@type RadarAttributes[][]
|
||||||
|
ATTRIBUTES = {
|
||||||
|
{RadarAttributes.new("notes", Point2D.new(0, 0), ColorRGBA.CYAN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),},
|
||||||
|
{RadarAttributes.new("peak", Point2D.new(0, 0), ColorRGBA.RED, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM), },
|
||||||
|
{RadarAttributes.new("tsumami", Point2D.new(0, 0), RADAR_PURPLE, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),},
|
||||||
|
{RadarAttributes.new("tricky", Point2D.new(0, 0), ColorRGBA.YELLOW, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),},
|
||||||
|
{
|
||||||
|
RadarAttributes.new("hand", Point2D.new(0, 0), RADAR_MAGENTA, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),
|
||||||
|
RadarAttributes.new("trip", Point2D.new(5, 16), RADAR_MAGENTA, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_TOP),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RadarAttributes.new("one", Point2D.new(6, -16), RADAR_GREEN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),
|
||||||
|
RadarAttributes.new("hand", Point2D.new(0, 0), RADAR_GREEN, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RADIUS = 100.0,
|
||||||
|
|
||||||
|
---Create Radar instance
|
||||||
|
---@param pos Point2D
|
||||||
|
---@param radius? number
|
||||||
|
---@return Radar
|
||||||
|
new = function (pos, radius)
|
||||||
|
---@class Radar : CRadar
|
||||||
|
local o = {
|
||||||
|
_graphdata = {
|
||||||
|
notes = 0,
|
||||||
|
peak = 0,
|
||||||
|
tsumami = 0,
|
||||||
|
tricky = 0,
|
||||||
|
handtrip = 0,
|
||||||
|
onehand = 0,
|
||||||
|
},
|
||||||
|
_hexagonMesh = gfx.CreateShadedMesh("radar"),
|
||||||
|
_outlineVertices = {},
|
||||||
|
_attributePositions = {}, ---@type Point2D[][]
|
||||||
|
_angleStep = (2 * math.pi) / #Radar.ATTRIBUTES, -- 360° / no. attributes, in radians
|
||||||
|
_initRotation = math.pi / 2, -- 90°, in radians
|
||||||
|
pos = pos or Point2D.ZERO,
|
||||||
|
scale = radius and radius / Radar.RADIUS or 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local sides = #Radar.ATTRIBUTES
|
||||||
|
|
||||||
|
local outlineRadius = Radar.RADIUS
|
||||||
|
local attributeRadius = Radar.RADIUS + 30
|
||||||
|
|
||||||
|
for i = 0, sides - 1 do
|
||||||
|
local attrIdx = i + 1
|
||||||
|
local angle = i * o._angleStep - o._initRotation
|
||||||
|
local cosAngle = math.cos(angle)
|
||||||
|
local sinAngle = math.sin(angle)
|
||||||
|
-- cache outline vertices
|
||||||
|
table.insert(o._outlineVertices, Point2D.new(outlineRadius * cosAngle, outlineRadius * sinAngle))
|
||||||
|
-- cache attribute positions
|
||||||
|
table.insert(o._attributePositions, {})
|
||||||
|
for j = 1, #Radar.ATTRIBUTES[attrIdx] do
|
||||||
|
local attr = Radar.ATTRIBUTES[attrIdx][j]
|
||||||
|
local attributePos = Point2D.new(attributeRadius * cosAngle, attributeRadius * sinAngle)
|
||||||
|
attributePos.x = attributePos.x + attr.offset.x
|
||||||
|
attributePos.y = attributePos.y + attr.offset.y
|
||||||
|
table.insert(o._attributePositions[attrIdx], j, attributePos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(o, Radar)
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
Radar.__index = Radar
|
||||||
|
|
||||||
|
---@param w number
|
||||||
|
---@param color ColorRGBA
|
||||||
|
function Radar:drawOutline(w, color)
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
for i = 1, #self._outlineVertices do
|
||||||
|
local j = i % #self._outlineVertices + 1
|
||||||
|
drawLine(self._outlineVertices[i], self._outlineVertices[j], w, color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param color ColorRGBA
|
||||||
|
---@param ticks? integer
|
||||||
|
function Radar:drawRadialTicks(color, ticks)
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
ticks = ticks or 3
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
gfx.StrokeColor(color:components())
|
||||||
|
|
||||||
|
for i, vertex in ipairs(self._outlineVertices) do
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.MoveTo(0, 0)
|
||||||
|
gfx.LineTo(vertex.x, vertex.y)
|
||||||
|
gfx.Stroke()
|
||||||
|
|
||||||
|
local lineLength = math.sqrt(vertex.x * vertex.x + vertex.y * vertex.y)
|
||||||
|
local tinyLineLength = 10
|
||||||
|
|
||||||
|
local tinyLineAngle = math.atan(vertex.y / vertex.x)
|
||||||
|
if vertex.x < 0 then
|
||||||
|
tinyLineAngle = tinyLineAngle + math.pi
|
||||||
|
end
|
||||||
|
|
||||||
|
local halfTinyLineLength = tinyLineLength / 2
|
||||||
|
|
||||||
|
for j = 1, ticks do
|
||||||
|
local distanceFromCenter = j * lineLength / (ticks + 1) -- Adjusted for 3 middle lines
|
||||||
|
|
||||||
|
local offsetX = distanceFromCenter * (vertex.x / lineLength)
|
||||||
|
local offsetY = distanceFromCenter * (vertex.y / lineLength)
|
||||||
|
|
||||||
|
local endX = halfTinyLineLength * math.cos(tinyLineAngle - math.pi / 2) -- Rotate by -90 degrees
|
||||||
|
local endY = halfTinyLineLength * math.sin(tinyLineAngle - math.pi / 2) -- Rotate by -90 degrees
|
||||||
|
|
||||||
|
local offsetX2 = halfTinyLineLength * math.cos(tinyLineAngle + math.pi / 2)
|
||||||
|
local offsetY2 = halfTinyLineLength * math.sin(tinyLineAngle + math.pi / 2)
|
||||||
|
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.MoveTo(offsetX - offsetX2, offsetY - offsetY2)
|
||||||
|
gfx.LineTo(endX + offsetX + offsetX2 + offsetX2, endY + offsetY + offsetY2 + offsetY2)
|
||||||
|
gfx.Stroke()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.Restore()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param fillColor ColorRGBA
|
||||||
|
function Radar:drawBackground(fillColor)
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.MoveTo(self._outlineVertices[1].x, self._outlineVertices[1].y)
|
||||||
|
for i = 2, #self._outlineVertices do
|
||||||
|
gfx.LineTo(self._outlineVertices[i].x, self._outlineVertices[i].y)
|
||||||
|
end
|
||||||
|
gfx.ClosePath()
|
||||||
|
|
||||||
|
gfx.FillColor(fillColor:components())
|
||||||
|
gfx.Fill()
|
||||||
|
|
||||||
|
gfx.Restore()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Radar:drawAttributes()
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
|
||||||
|
gfx.LoadSkinFont("contb.ttf")
|
||||||
|
gfx.FontSize(21)
|
||||||
|
for i = 1, #self._attributePositions do
|
||||||
|
local attrPos = self._attributePositions[i]
|
||||||
|
for j = 1, #attrPos do
|
||||||
|
local pos = attrPos[j]
|
||||||
|
local attr = Radar.ATTRIBUTES[i][j]
|
||||||
|
gfx.TextAlign(attr.align)
|
||||||
|
renderOutlinedText(pos, string.upper(attr.text), 1, attr.color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.Restore()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Draw shaded radar mesh
|
||||||
|
---
|
||||||
|
---Bug: ForceRender resets every transformation, you need to re-setup view transform afterwards.
|
||||||
|
---ForceRender also resets the gfx stack, USC will crash if you try to call gfx.Restore(),
|
||||||
|
---make sure the gfx stack is clean before calling radar:drawRadarMesh()
|
||||||
|
function Radar:drawRadarMesh()
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
local scaleFact = {
|
||||||
|
self._graphdata.notes,
|
||||||
|
self._graphdata.peak,
|
||||||
|
self._graphdata.tsumami,
|
||||||
|
self._graphdata.tricky,
|
||||||
|
self._graphdata.handtrip,
|
||||||
|
self._graphdata.onehand,
|
||||||
|
}
|
||||||
|
|
||||||
|
local colorMax = ColorRGBA.new(255, 12, 48, 230) -- magenta-ish
|
||||||
|
local colorCenter = ColorRGBA.new(112, 119, 255, 230) -- light blue-ish purple
|
||||||
|
|
||||||
|
-- Calculate the maximum size based on the constraint
|
||||||
|
local maxSize = self.RADIUS * self.scale
|
||||||
|
local maxLineLength = maxSize * maxScaleFactor
|
||||||
|
self._hexagonMesh:SetParam("maxSize", maxLineLength + .0)
|
||||||
|
|
||||||
|
-- Set the color of the hexagon
|
||||||
|
self._hexagonMesh:SetParamVec4("colorMax", colorMax:componentsFloat())
|
||||||
|
self._hexagonMesh:SetParamVec4("colorCenter", colorCenter:componentsFloat())
|
||||||
|
|
||||||
|
-- Set the primitive type to triangles
|
||||||
|
self._hexagonMesh:SetPrimitiveType(self._hexagonMesh.PRIM_TRIFAN)
|
||||||
|
|
||||||
|
-- Calculate the vertices of the hexagon
|
||||||
|
local sides = #Radar.ATTRIBUTES
|
||||||
|
|
||||||
|
local vertices = {}
|
||||||
|
table.insert(vertices, {{0, 0}, {0, 0}})
|
||||||
|
for i = 0, sides do
|
||||||
|
local j = i % sides + 1
|
||||||
|
local angle = i * self._angleStep - self._initRotation
|
||||||
|
|
||||||
|
--local angle = math.rad(60 * (i-1)) + rotationAngle
|
||||||
|
local scale = scaleFact[j]
|
||||||
|
local lineLength = maxSize * scale
|
||||||
|
local px = lineLength * math.cos(angle)
|
||||||
|
local py = lineLength * math.sin(angle)
|
||||||
|
table.insert(vertices, {{px, py}, {0, 0}})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set the hexagon's vertices
|
||||||
|
self._hexagonMesh:SetData(vertices)
|
||||||
|
self._hexagonMesh:Draw()
|
||||||
|
|
||||||
|
-- YOU! You are the reason for all my pain!
|
||||||
|
gfx.ForceRender()
|
||||||
|
end
|
||||||
|
|
||||||
|
--NOTE: THIS IS BUGGY, ForceRender fucks up so many things, call the individual draw functions at top level
|
||||||
|
function Radar:drawGraph()
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
game.Log("Radar:drawGraph() SHOULD NOT BE CALLED", game.LOGGER_WARNING)
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
gfx.Reset()
|
||||||
|
gfx.ResetScissor()
|
||||||
|
|
||||||
|
Dim.updateResolution()
|
||||||
|
Dim.transformToScreenSpace()
|
||||||
|
|
||||||
|
gfx.FontSize(28)
|
||||||
|
gfx.Translate(self.pos.x, self.pos.y)
|
||||||
|
gfx.Scale(self.scale, self.scale)
|
||||||
|
|
||||||
|
local strokeColor = ColorRGBA.new(255, 255, 255, 100)
|
||||||
|
local fillColor = ColorRGBA.new(0, 0, 0, 191)
|
||||||
|
|
||||||
|
self:drawBackground(fillColor)
|
||||||
|
self:drawOutline(3, strokeColor)
|
||||||
|
self:drawRadarMesh()
|
||||||
|
self:drawRadialTicks(strokeColor)
|
||||||
|
self:drawAttributes()
|
||||||
|
|
||||||
|
local pos = Point2D.new(self.pos:coords())
|
||||||
|
pos.y = pos.y - self.RADIUS
|
||||||
|
--drawDebugText(pos, self._graphdata)
|
||||||
|
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
--NOTE: Bug workaround: forcerender resets every transformation, re-setup view transform
|
||||||
|
Dim.transformToScreenSpace()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Compute radar attribute values from ksh
|
||||||
|
---@param info string # chart directory path
|
||||||
|
---@param dif string # chart name without extension
|
||||||
|
function Radar:updateGraph(info, dif)
|
||||||
|
---@cast self Radar
|
||||||
|
|
||||||
|
--local pattern = "(.*[\\/])"
|
||||||
|
--local extractedSubstring = info:match(pattern)
|
||||||
|
--local txtFilePath = extractedSubstring .. "radar\\" .. dif .. ".txt"
|
||||||
|
|
||||||
|
--local song = io.open(txtFilePath, "r")
|
||||||
|
local fullPath = info.."/"..dif..".ksh"
|
||||||
|
local song = io.open(fullPath)
|
||||||
|
game.Log('Reading chart data from "'..fullPath..'"', game.LOGGER_DEBUG)
|
||||||
|
game.Log(song and "file open" or "file not found", game.LOGGER_DEBUG)
|
||||||
|
if song then
|
||||||
|
local chartData = song:read("*all")
|
||||||
|
song:close()
|
||||||
|
|
||||||
|
local notesCount, knobCount, oneHandCount, handTripCount = 0, 0, 0, 0
|
||||||
|
local chartLineCount = 0
|
||||||
|
local notesValue = 0
|
||||||
|
local peakValue = 0
|
||||||
|
local tsumamiValue = 0
|
||||||
|
local trickyValue = 0
|
||||||
|
local totalMeasures = 0
|
||||||
|
|
||||||
|
local lastNotes = {}
|
||||||
|
local lastFx = {}
|
||||||
|
local measureLength = 0
|
||||||
|
|
||||||
|
---@cast chartData string
|
||||||
|
for line in chartData:gmatch("[^\r\n]+") do
|
||||||
|
-- <bt-lanes x 4>|<fx-lanes x 2>|<laser-lanes x 2><lane-spin (optional)>
|
||||||
|
|
||||||
|
--game.Log(line, game.LOGGER_DEBUG)
|
||||||
|
|
||||||
|
local patternBt = "([012][012][012][012])"
|
||||||
|
local patternFx = "([012ABDFGHIJKLPQSTUVWX][012ABDFGHIJKLPQSTUVWX])"
|
||||||
|
local patternLaser = "([%-:%dA-Za-o][%-:%dA-Za-o])"
|
||||||
|
local patternLaneSpin = "([@S][%(%)<>]%d+)" -- optional
|
||||||
|
local pattern = patternBt.."|"..patternFx.."|"..patternLaser
|
||||||
|
|
||||||
|
-- match line format
|
||||||
|
|
||||||
|
local noteType, fxType, laserType = line:match(pattern)
|
||||||
|
local laneSpin = line:match(patternLaneSpin)
|
||||||
|
|
||||||
|
if noteType and fxType and laserType then
|
||||||
|
chartLineCount = chartLineCount + 1
|
||||||
|
|
||||||
|
-- convert strings to array, to be easily indexable
|
||||||
|
|
||||||
|
noteType = {noteType:match("([012])([012])([012])([012])")}
|
||||||
|
fxType = {fxType:match("([012ABDFGHIJKLPQSTUVWX])([012ABDFGHIJKLPQSTUVWX])")}
|
||||||
|
laserType = {laserType:match("([%-:%dA-Za-o])([%-:%dA-Za-o])")}
|
||||||
|
|
||||||
|
---@cast noteType string[]
|
||||||
|
---@cast fxType string[]
|
||||||
|
---@cast laserType string[]
|
||||||
|
|
||||||
|
-- parse notes
|
||||||
|
|
||||||
|
local function isNewNote(idx, note)
|
||||||
|
if note == "2" and lastNotes[idx] ~= note then
|
||||||
|
-- a new hold note
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if note == "1" then
|
||||||
|
-- a chip
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for noteIdx, note in ipairs(noteType) do
|
||||||
|
if isNewNote(noteIdx, note) then
|
||||||
|
notesCount = notesCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse fx
|
||||||
|
|
||||||
|
local function isNewFx(idx, fx)
|
||||||
|
if fx:match("[1ABDFGHIJKLPQSTUVWX]") and lastFx[idx] ~= fx then
|
||||||
|
-- a new hold note
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if fx == "2" then
|
||||||
|
-- a chip
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for fxIdx, fx in ipairs(fxType) do
|
||||||
|
if isNewFx(fxIdx, fx) then
|
||||||
|
notesCount = notesCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse laser
|
||||||
|
|
||||||
|
for _, laser in ipairs(laserType) do
|
||||||
|
if laser ~= "-" then
|
||||||
|
knobCount = knobCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- figure out one-handed notes (there's a BT or FX while a hand is manipulating a knob)
|
||||||
|
-- also try to figure out cross-handed notes (one-handed notes, but on the same side as knob)
|
||||||
|
|
||||||
|
local function countBtFx()
|
||||||
|
local count = 0
|
||||||
|
for noteIdx, note in ipairs(noteType) do
|
||||||
|
if isNewNote(noteIdx, note) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for fxIdx, fx in ipairs(fxType) do
|
||||||
|
if isNewFx(fxIdx, fx) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
---@param side "left"|"right"
|
||||||
|
local function countSide(side)
|
||||||
|
local count = 0
|
||||||
|
local notes = {}
|
||||||
|
local fx = ""
|
||||||
|
if side == "left" then
|
||||||
|
notes = {noteType[1], noteType[2]}
|
||||||
|
fx = fxType[1]
|
||||||
|
if isNewFx(1, fx) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
elseif side == "right" then
|
||||||
|
notes = {noteType[3], noteType[4]}
|
||||||
|
fx = fxType[2]
|
||||||
|
if isNewFx(2, fx) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
game.Log("countSide: Invalid side parameter", game.LOGGER_ERROR)
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
for noteIdx, note in ipairs(notes) do
|
||||||
|
if isNewNote(noteIdx, note) then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
if laserType[1] ~= "-" and laserType[2] == "-" then
|
||||||
|
oneHandCount = oneHandCount + countBtFx()
|
||||||
|
handTripCount = handTripCount + countSide("left")
|
||||||
|
end
|
||||||
|
if laserType[1] == "-" and laserType[2] ~= "-" then
|
||||||
|
oneHandCount = oneHandCount + countBtFx()
|
||||||
|
handTripCount = handTripCount + countSide("right")
|
||||||
|
end
|
||||||
|
|
||||||
|
lastNotes = noteType
|
||||||
|
lastFx = fxType
|
||||||
|
|
||||||
|
measureLength = measureLength + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if line == "--" then
|
||||||
|
-- end of measure
|
||||||
|
measureLength = math.max(1, measureLength)
|
||||||
|
|
||||||
|
local relativeMeasureLength = measureLength / 192
|
||||||
|
|
||||||
|
-- calculate peak density
|
||||||
|
local peak = (notesCount / 6) / relativeMeasureLength
|
||||||
|
peakValue = math.max(peakValue, peak)
|
||||||
|
--[[
|
||||||
|
local debuglog = {
|
||||||
|
measureLength = measureLength,
|
||||||
|
notesCount = notesCount,
|
||||||
|
relativeMeasureLength = relativeMeasureLength,
|
||||||
|
peak = peak,
|
||||||
|
}
|
||||||
|
for k, v in pairs(debuglog) do
|
||||||
|
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- cumulate "time" spent operating the knobs
|
||||||
|
local tsumami = (knobCount / 2) / relativeMeasureLength
|
||||||
|
tsumamiValue = tsumamiValue + tsumami
|
||||||
|
|
||||||
|
measureLength = 0
|
||||||
|
notesCount = 0
|
||||||
|
|
||||||
|
-- cumulate peak values (used to average notes over the length of the song)
|
||||||
|
notesValue = notesValue + peak
|
||||||
|
|
||||||
|
totalMeasures = totalMeasures + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local beat = line:match("beat=(%d+/%d+)")
|
||||||
|
if beat then
|
||||||
|
beat = {beat:match("(%d+)/(%d+)")}
|
||||||
|
end
|
||||||
|
|
||||||
|
--BUG: This is not correct, it needs to account for effect length
|
||||||
|
local function isTricky()
|
||||||
|
local tricks = {
|
||||||
|
"beat",
|
||||||
|
"stop",
|
||||||
|
"zoom_top",
|
||||||
|
"zoom_bottom",
|
||||||
|
"zoom_side",
|
||||||
|
"center_split",
|
||||||
|
}
|
||||||
|
return Util.any(tricks, function(e) return line:match("e") end)
|
||||||
|
end
|
||||||
|
if laneSpin or isTricky() then
|
||||||
|
trickyValue = trickyValue + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local graphValues = {
|
||||||
|
notes = notesValue / totalMeasures,
|
||||||
|
peak = peakValue,
|
||||||
|
tsumami = tsumamiValue / totalMeasures,
|
||||||
|
tricky = trickyValue,
|
||||||
|
handtrip = handTripCount,
|
||||||
|
onehand = oneHandCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
game.Log("graphValues", game.LOGGER_DEBUG)
|
||||||
|
for k,v in pairs(graphValues) do
|
||||||
|
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||||
|
end
|
||||||
|
|
||||||
|
local calibration = {
|
||||||
|
notes = 10,
|
||||||
|
peak = 48,
|
||||||
|
tsumami = 20000,
|
||||||
|
tricky = 128,
|
||||||
|
handtrip = 300,
|
||||||
|
onehand = 300,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, factor in pairs(calibration) do
|
||||||
|
-- Apply the scaling factor to each value
|
||||||
|
self._graphdata[key] = graphValues[key] / factor
|
||||||
|
|
||||||
|
-- Limit to maximum scale factor
|
||||||
|
self._graphdata[key] = math.min(self._graphdata[key], maxScaleFactor)
|
||||||
|
end
|
||||||
|
|
||||||
|
game.Log("_graphdata", game.LOGGER_DEBUG)
|
||||||
|
for k,v in pairs(self._graphdata) do
|
||||||
|
game.Log(k..": "..v, game.LOGGER_DEBUG)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Radar
|
|
@ -9,6 +9,9 @@ local Numbers = require('components.numbers')
|
||||||
|
|
||||||
local VolforceCalc = require('components.volforceCalc')
|
local VolforceCalc = require('components.volforceCalc')
|
||||||
|
|
||||||
|
require("api.point2d")
|
||||||
|
require("components.radar")
|
||||||
|
|
||||||
local dataPanelImage = gfx.CreateSkinImage("song_select/data_bg_overlay.png", 1)
|
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 dataGlowOverlayImage = gfx.CreateSkinImage("song_select/data_panel/data_glow_overlay.png", 1)
|
||||||
local gradeBgImage = gfx.CreateSkinImage("song_select/data_panel/grade_bg.png", 1)
|
local gradeBgImage = gfx.CreateSkinImage("song_select/data_panel/grade_bg.png", 1)
|
||||||
|
@ -39,8 +42,6 @@ local searchInfoPanelImage = gfx.CreateSkinImage("song_select/search_info_panel.
|
||||||
|
|
||||||
local defaultJacketImage = gfx.CreateSkinImage("song_select/loading.png", 0)
|
local defaultJacketImage = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local difficultyLabelImages = {
|
local difficultyLabelImages = {
|
||||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/novice.png", 1),
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/novice.png", 1),
|
||||||
gfx.CreateSkinImage("song_select/plate/difficulty_labels/advanced.png", 1),
|
gfx.CreateSkinImage("song_select/plate/difficulty_labels/advanced.png", 1),
|
||||||
|
@ -113,6 +114,8 @@ game.LoadSkinSample('song_wheel/diff_change.wav')
|
||||||
local scoreNumbers = Numbers.load_number_image("score_num")
|
local scoreNumbers = Numbers.load_number_image("score_num")
|
||||||
local difficultyNumbers = Numbers.load_number_image("diff_num")
|
local difficultyNumbers = Numbers.load_number_image("diff_num")
|
||||||
|
|
||||||
|
local songselect_showEffectRadar = game.GetSkinSetting("songselect_showEffectRadar") or false
|
||||||
|
|
||||||
local LEADERBOARD_PLACE_NAMES = {
|
local LEADERBOARD_PLACE_NAMES = {
|
||||||
'1st',
|
'1st',
|
||||||
'2nd',
|
'2nd',
|
||||||
|
@ -125,13 +128,16 @@ local songPlateHeight = 172
|
||||||
local selectedIndex = 1
|
local selectedIndex = 1
|
||||||
local selectedDifficulty = 1
|
local selectedDifficulty = 1
|
||||||
|
|
||||||
|
local radar = Radar.new(Point2D.new(0, 0))
|
||||||
|
local updateRadar = true
|
||||||
|
|
||||||
local jacketCache = {}
|
local jacketCache = {}
|
||||||
|
|
||||||
local top50diffs = {}
|
local top50diffs = {}
|
||||||
|
|
||||||
local irRequestStatus = 1 -- 0=unused, 1=not requested, 2=loading, others are status codes
|
local irRequestStatus = 1 -- 0=unused, 1=not requested, 2=loading, others are status codes
|
||||||
local irRequestTimeout = 2
|
local irRequestTimeout = 2
|
||||||
local irLeaderboard = {}
|
local irLeaderboard = {} ---@type ServerScore[]|{}
|
||||||
local irLeaderboardsCache = {}
|
local irLeaderboardsCache = {}
|
||||||
|
|
||||||
local transitionScrollScale = 0
|
local transitionScrollScale = 0
|
||||||
|
@ -158,6 +164,7 @@ local transitionSearchInfoEnterScale = 0
|
||||||
local transitionSearchBackgroundAlpha = 0
|
local transitionSearchBackgroundAlpha = 0
|
||||||
local transitionSearchbarOffsetY = 0
|
local transitionSearchbarOffsetY = 0
|
||||||
local transitionSearchInfoOffsetY = 0
|
local transitionSearchInfoOffsetY = 0
|
||||||
|
local transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
|
||||||
|
|
||||||
local transitionLaserScale = 0
|
local transitionLaserScale = 0
|
||||||
local transitionLaserY = 0
|
local transitionLaserY = 0
|
||||||
|
@ -195,8 +202,8 @@ local resolutionChange = function(x, y)
|
||||||
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR)
|
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR)
|
||||||
end
|
end
|
||||||
|
|
||||||
function getCorrectedIndex(from, offset)
|
local function getCorrectedIndex(from, offset)
|
||||||
total = #songwheel.songs
|
local total = #songwheel.songs
|
||||||
|
|
||||||
if (math.abs(offset) > total) then
|
if (math.abs(offset) > total) then
|
||||||
if (offset < 0) then
|
if (offset < 0) then
|
||||||
|
@ -206,21 +213,21 @@ function getCorrectedIndex(from, offset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
index = from + offset
|
local index = from + offset
|
||||||
|
|
||||||
if index < 1 then
|
if index < 1 then
|
||||||
index = total + (from+offset) -- this only happens if the offset is negative
|
index = total + (from+offset) -- this only happens if the offset is negative
|
||||||
end
|
end
|
||||||
|
|
||||||
if index > total then
|
if index > total then
|
||||||
indexesUntilEnd = total - from
|
local indexesUntilEnd = total - from
|
||||||
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
||||||
end
|
end
|
||||||
|
|
||||||
return index
|
return index
|
||||||
end
|
end
|
||||||
|
|
||||||
function getJacketImage(song)
|
local function getJacketImage(song)
|
||||||
if not jacketCache[song.id] or jacketCache[song.id]==defaultJacketImage then
|
if not jacketCache[song.id] or jacketCache[song.id]==defaultJacketImage then
|
||||||
jacketCache[song.id] = gfx.LoadImageJob(song.difficulties[
|
jacketCache[song.id] = gfx.LoadImageJob(song.difficulties[
|
||||||
math.min(selectedDifficulty, #song.difficulties)
|
math.min(selectedDifficulty, #song.difficulties)
|
||||||
|
@ -230,7 +237,7 @@ function getJacketImage(song)
|
||||||
return jacketCache[song.id]
|
return jacketCache[song.id]
|
||||||
end
|
end
|
||||||
|
|
||||||
function getGradeImageForScore(score)
|
local function getGradeImageForScore(score)
|
||||||
local gradeImage = gradeImages.none
|
local gradeImage = gradeImages.none
|
||||||
local bestGradeCutoff = 0
|
local bestGradeCutoff = 0
|
||||||
for gradeName, scoreCutoff in pairs(gradeCutoffs) do
|
for gradeName, scoreCutoff in pairs(gradeCutoffs) do
|
||||||
|
@ -245,7 +252,7 @@ function getGradeImageForScore(score)
|
||||||
return gradeImage
|
return gradeImage
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawLaserAnim()
|
local function drawLaserAnim()
|
||||||
gfx.Save()
|
gfx.Save()
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
|
|
||||||
|
@ -256,7 +263,7 @@ function drawLaserAnim()
|
||||||
gfx.Restore()
|
gfx.Restore()
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawBackground(deltaTime)
|
local function drawBackground(deltaTime)
|
||||||
Background.draw(deltaTime)
|
Background.draw(deltaTime)
|
||||||
|
|
||||||
local song = songwheel.songs[selectedIndex]
|
local song = songwheel.songs[selectedIndex]
|
||||||
|
@ -269,7 +276,6 @@ function drawBackground(deltaTime)
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
gfx.ImageRect(transitionJacketBgScrollPosX, 0, 900, 900, jacketImage or defaultJacketImage, transitionJacketBgScrollAlpha, 0)
|
gfx.ImageRect(transitionJacketBgScrollPosX, 0, 900, 900, jacketImage or defaultJacketImage, transitionJacketBgScrollAlpha, 0)
|
||||||
|
|
||||||
|
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64))
|
gfx.FillColor(0,0,0,math.floor(transitionJacketBgScrollAlpha*64))
|
||||||
gfx.Rect(0,0,900,900)
|
gfx.Rect(0,0,900,900)
|
||||||
|
@ -300,7 +306,9 @@ function drawBackground(deltaTime)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawSong(song, y)
|
---@param song SongWheelSong
|
||||||
|
---@param y number
|
||||||
|
local function drawSong(song, y)
|
||||||
if (not song) then return end
|
if (not song) then return end
|
||||||
|
|
||||||
local songX = desw/2+28
|
local songX = desw/2+28
|
||||||
|
@ -372,13 +380,13 @@ function drawSong(song, y)
|
||||||
gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0)
|
gfx.ImageRect(songX+391, y+47, 60, 60, gradeImage, gradeAlpha, 0)
|
||||||
|
|
||||||
-- Draw top 50 label if applicable
|
-- Draw top 50 label if applicable
|
||||||
if (top50diffs[selectedSongDifficulty.id]) then
|
if (top50diffs[selectedSongDifficulty.hash]) then
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0)
|
gfx.ImageRect(songX+82, y+109, 506*0.85, 26*0.85, top50OverlayImage, 1, 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawSongList()
|
local function drawSongList()
|
||||||
gfx.GlobalAlpha(1-transitionLeaveScale)
|
gfx.GlobalAlpha(1-transitionLeaveScale)
|
||||||
|
|
||||||
local numOfSongsAround = 7 -- How many songs should be up and how many should be down of the selected one
|
local numOfSongsAround = 7 -- How many songs should be up and how many should be down of the selected one
|
||||||
|
@ -405,128 +413,8 @@ function drawSongList()
|
||||||
gfx.GlobalAlpha(1)
|
gfx.GlobalAlpha(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawData() -- Draws the song data on the left panel
|
|
||||||
|
|
||||||
if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end
|
|
||||||
|
|
||||||
local song = songwheel.songs[selectedIndex]
|
|
||||||
local diff = song and song.difficulties[selectedDifficulty] or false
|
|
||||||
local bestScore = diff and diff.scores[1]
|
|
||||||
|
|
||||||
if not song then return false end
|
|
||||||
|
|
||||||
local jacketImage = getJacketImage(song)
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0)
|
|
||||||
|
|
||||||
if (top50diffs[diff.id]) then
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
gfx.Save()
|
|
||||||
-- Draw best score
|
|
||||||
gfx.BeginPath()
|
|
||||||
|
|
||||||
local scoreNumber = 0
|
|
||||||
if bestScore then
|
|
||||||
scoreNumber = bestScore.score
|
|
||||||
end
|
|
||||||
|
|
||||||
Numbers.draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12)
|
|
||||||
Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12)
|
|
||||||
|
|
||||||
-- Draw grade
|
|
||||||
local gradeImage = gradeImages.none
|
|
||||||
local gradeAlpha = transitionAfterscrollGradeAlpha
|
|
||||||
if bestScore then
|
|
||||||
gradeImage = getGradeImageForScore(bestScore.score)
|
|
||||||
|
|
||||||
if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then
|
|
||||||
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0)
|
|
||||||
|
|
||||||
-- Draw badge
|
|
||||||
badgeImage = badgeImages[diff.topBadge+1]
|
|
||||||
|
|
||||||
local badgeAlpha = transitionAfterscrollBadgeAlpha
|
|
||||||
if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then
|
|
||||||
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition
|
|
||||||
end
|
|
||||||
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, badgeAlpha, 0)
|
|
||||||
|
|
||||||
gfx.Restore()
|
|
||||||
|
|
||||||
-- Draw BPM
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.FontSize(24)
|
|
||||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
||||||
gfx.Save()
|
|
||||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
|
||||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out
|
|
||||||
gfx.Text(song.bpm, 85, 920)
|
|
||||||
gfx.Restore()
|
|
||||||
|
|
||||||
-- Draw song title
|
|
||||||
gfx.FontSize(28)
|
|
||||||
gfx.GlobalAlpha(transitionAfterscrollTextSongTitle)
|
|
||||||
gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955)
|
|
||||||
|
|
||||||
-- Draw artist
|
|
||||||
gfx.GlobalAlpha(transitionAfterscrollTextSongArtist)
|
|
||||||
gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997)
|
|
||||||
|
|
||||||
gfx.GlobalAlpha(1)
|
|
||||||
|
|
||||||
-- Draw difficulties
|
|
||||||
local DIFF_X_START = 98.5
|
|
||||||
local DIFF_GAP = 114.8
|
|
||||||
gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha)
|
|
||||||
for i, diff in ipairs(song.difficulties) do
|
|
||||||
gfx.BeginPath()
|
|
||||||
|
|
||||||
local index = diff.difficulty+1
|
|
||||||
|
|
||||||
if i == selectedDifficulty then
|
|
||||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-(163*0.8)/2, 1028, 163*0.8, 163*0.8, diffCursorImage, 1, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1)
|
|
||||||
|
|
||||||
local diffLabelImage = difficultyLabelUnderImages[
|
|
||||||
Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty)
|
|
||||||
]
|
|
||||||
local tw, th = gfx.ImageSize(diffLabelImage)
|
|
||||||
tw=tw*0.9
|
|
||||||
th=th*0.9
|
|
||||||
gfx.BeginPath()
|
|
||||||
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0)
|
|
||||||
end
|
|
||||||
gfx.GlobalAlpha(1)
|
|
||||||
|
|
||||||
|
|
||||||
-- Scoreboard
|
|
||||||
|
|
||||||
drawLocalLeaderboard(diff)
|
|
||||||
drawIrLeaderboard()
|
|
||||||
|
|
||||||
gfx.FontSize(22)
|
|
||||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
|
||||||
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha)
|
|
||||||
gfx.Text(diff.effector, 270, 1180) -- effected by
|
|
||||||
gfx.Text(diff.illustrator, 270, 1210) -- illustrated by
|
|
||||||
gfx.GlobalAlpha(1)
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param diff SongWheelDifficulty
|
---@param diff SongWheelDifficulty
|
||||||
function drawLocalLeaderboard(diff)
|
local function drawLocalLeaderboard(diff)
|
||||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||||
gfx.FontSize(26)
|
gfx.FontSize(26)
|
||||||
|
|
||||||
|
@ -568,11 +456,12 @@ function drawLocalLeaderboard(diff)
|
||||||
gfx.Text(username or "-", sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
gfx.Text(username or "-", sbBarContentLeftX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||||
|
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
gfx.Text(score or "- - - - - - - -", sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
local scoreText = score and tostring(score) or "- - - - - - - -"
|
||||||
|
gfx.Text(scoreText, sbBarContentRightX, scoreBoardY + sbBarHeight/2 + i*sbBarHeight)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawIrLeaderboard()
|
local function drawIrLeaderboard()
|
||||||
if not IRData.Active then
|
if not IRData.Active then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -649,7 +538,130 @@ function drawIrLeaderboard()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawFilterInfo(deltatime)
|
local function drawData() -- Draws the song data on the left panel
|
||||||
|
|
||||||
|
if isFilterWheelActive or transitionLeaveReappearTimer ~= 0 then return false end
|
||||||
|
|
||||||
|
local song = songwheel.songs[selectedIndex]
|
||||||
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||||
|
local bestScore = diff and diff.scores[1]
|
||||||
|
|
||||||
|
if not song then return false end
|
||||||
|
|
||||||
|
local jacketImage = getJacketImage(song)
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.ImageRect(96, 324, 348, 348, jacketImage or defaultJacketImage, 1, 0)
|
||||||
|
|
||||||
|
if (top50diffs[diff.hash]) then
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.ImageRect(96, 529, 410*0.85, 168*0.85, top50JacketOverlayImage, 1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw best score
|
||||||
|
gfx.Save()
|
||||||
|
gfx.BeginPath()
|
||||||
|
|
||||||
|
local scoreNumber = 0
|
||||||
|
if bestScore then
|
||||||
|
scoreNumber = bestScore.score
|
||||||
|
end
|
||||||
|
|
||||||
|
Numbers.draw_number(100, 793, 1.0, math.floor(scoreNumber / 10000), 4, scoreNumbers, true, 0.3, 1.12)
|
||||||
|
Numbers.draw_number(253, 798, 1.0, scoreNumber, 4, scoreNumbers, true, 0.22, 1.12)
|
||||||
|
|
||||||
|
-- Draw grade
|
||||||
|
local gradeImage = gradeImages.none
|
||||||
|
local gradeAlpha = transitionAfterscrollGradeAlpha
|
||||||
|
if bestScore then
|
||||||
|
gradeImage = getGradeImageForScore(bestScore.score)
|
||||||
|
|
||||||
|
if (transitionAfterscrollGradeAlpha == 1 and bestScore.score >= gradeCutoffs.S) then
|
||||||
|
gradeAlpha = transitionFlashAlpha -- If S, flash the badge
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.ImageRect(360, 773, 45, 45, gradeImage, gradeAlpha, 0)
|
||||||
|
|
||||||
|
-- Draw badge
|
||||||
|
local badgeImage = badgeImages[diff.topBadge+1]
|
||||||
|
|
||||||
|
local badgeAlpha = transitionAfterscrollBadgeAlpha
|
||||||
|
if (transitionAfterscrollBadgeAlpha == 1 and diff.topBadge >= 3) then
|
||||||
|
badgeAlpha = transitionFlashAlpha -- If hard clear or above, flash the badge, but only after the initial transition
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.ImageRect(425, 724, 93/1.1, 81/1.1, badgeImage, badgeAlpha, 0)
|
||||||
|
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
-- Draw BPM
|
||||||
|
gfx.Save()
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.FontSize(24)
|
||||||
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||||
|
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||||
|
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha) -- TODO: split this out
|
||||||
|
gfx.Text(song.bpm, 85, 920)
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
-- Draw song title
|
||||||
|
gfx.Save()
|
||||||
|
gfx.FontSize(28)
|
||||||
|
gfx.GlobalAlpha(transitionAfterscrollTextSongTitle)
|
||||||
|
gfx.Text(song.title, 30+(1-transitionAfterscrollTextSongTitle)*20, 955)
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
-- Draw artist
|
||||||
|
gfx.Save()
|
||||||
|
gfx.GlobalAlpha(transitionAfterscrollTextSongArtist)
|
||||||
|
gfx.Text(song.artist, 30+(1-transitionAfterscrollTextSongArtist)*30, 997)
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
-- Draw difficulties
|
||||||
|
local DIFF_X_START = 98.5
|
||||||
|
local DIFF_GAP = 114.8
|
||||||
|
gfx.Save()
|
||||||
|
gfx.GlobalAlpha(transitionAfterscrollDifficultiesAlpha)
|
||||||
|
for i, diff in ipairs(song.difficulties) do
|
||||||
|
gfx.BeginPath()
|
||||||
|
|
||||||
|
local index = diff.difficulty+1
|
||||||
|
|
||||||
|
if i == selectedDifficulty then
|
||||||
|
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-(163*0.8)/2, 1028, 163*0.8, 163*0.8, diffCursorImage, 1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
Numbers.draw_number(85+(index-1)*DIFF_GAP, 1085, 1.0, diff.level, 2, difficultyNumbers, false, 0.8, 1)
|
||||||
|
|
||||||
|
local diffLabelImage = difficultyLabelUnderImages[
|
||||||
|
Charting.GetDisplayDifficulty(diff.jacketPath, diff.difficulty)
|
||||||
|
]
|
||||||
|
local tw, th = gfx.ImageSize(diffLabelImage)
|
||||||
|
tw=tw*0.9
|
||||||
|
th=th*0.9
|
||||||
|
gfx.BeginPath()
|
||||||
|
gfx.ImageRect(DIFF_X_START+(index-1)*DIFF_GAP-tw/2, 1050, tw, th, diffLabelImage, 1, 0)
|
||||||
|
end
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
-- Scoreboard
|
||||||
|
|
||||||
|
drawLocalLeaderboard(diff)
|
||||||
|
drawIrLeaderboard()
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
gfx.FontSize(22)
|
||||||
|
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||||
|
gfx.GlobalAlpha(transitionAfterscrollDataOverlayAlpha)
|
||||||
|
gfx.Text(diff.effector, 270, 1180) -- effected by
|
||||||
|
gfx.Text(diff.illustrator, 270, 1210) -- illustrated by
|
||||||
|
gfx.Restore()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawFilterInfo(deltatime)
|
||||||
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
||||||
|
|
||||||
if (songwheel.searchInputActive) then
|
if (songwheel.searchInputActive) then
|
||||||
|
@ -679,7 +691,7 @@ function drawFilterInfo(deltatime)
|
||||||
gfx.Text(sortOptionLabel or '', desw-150, 130)
|
gfx.Text(sortOptionLabel or '', desw-150, 130)
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawCursor()
|
local function drawCursor()
|
||||||
if isFilterWheelActive or transitionLeaveScale ~= 0 then return false end
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return false end
|
||||||
|
|
||||||
gfx.BeginPath()
|
gfx.BeginPath()
|
||||||
|
@ -690,7 +702,7 @@ function drawCursor()
|
||||||
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
|
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawSearch()
|
local function drawSearch()
|
||||||
if (not songwheel.searchInputActive and searchPreviousActiveState) then
|
if (not songwheel.searchInputActive and searchPreviousActiveState) then
|
||||||
searchPreviousActiveState = false
|
searchPreviousActiveState = false
|
||||||
game.PlaySample('sort_wheel/enter.wav')
|
game.PlaySample('sort_wheel/enter.wav')
|
||||||
|
@ -760,7 +772,7 @@ function drawSearch()
|
||||||
gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2)
|
gfx.Text(songwheel.searchText, xPos + 160, yPos + 83.2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function drawScrollbar()
|
local function drawScrollbar()
|
||||||
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
||||||
|
|
||||||
-- Scrollbar Background
|
-- Scrollbar Background
|
||||||
|
@ -803,7 +815,33 @@ function drawScrollbar()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function refreshIrLeaderboard(deltaTime)
|
---Called on IR Leaderboard fetch complete
|
||||||
|
---@param res IRLeaderboardResponse
|
||||||
|
local function onIrLeaderboardFetched(res)
|
||||||
|
irRequestStatus = res.statusCode
|
||||||
|
|
||||||
|
local song = songwheel.songs[selectedIndex]
|
||||||
|
local diff = song and song.difficulties[selectedDifficulty] or false
|
||||||
|
|
||||||
|
if res.statusCode == IRData.States.Success then
|
||||||
|
local tempIrLB = res.body
|
||||||
|
|
||||||
|
table.sort(tempIrLB, function (a,b)
|
||||||
|
return a.score > b.score
|
||||||
|
end)
|
||||||
|
|
||||||
|
irLeaderboard = tempIrLB
|
||||||
|
irLeaderboardsCache[diff.hash] = irLeaderboard
|
||||||
|
else
|
||||||
|
local httpStatus = (res.statusCode // 10) * 100 + res.statusCode % 10 -- convert to 100 range
|
||||||
|
game.Log("IR error (" .. httpStatus .. "): " .. res.description, game.LOGGER_ERROR)
|
||||||
|
if res.body then
|
||||||
|
game.Log(common.dump(res.body), game.LOGGER_ERROR)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refreshIrLeaderboard(deltaTime)
|
||||||
if not IRData.Active then
|
if not IRData.Active then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -832,56 +870,10 @@ function refreshIrLeaderboard(deltaTime)
|
||||||
end
|
end
|
||||||
|
|
||||||
irRequestStatus = 2 -- Loading
|
irRequestStatus = 2 -- Loading
|
||||||
-- onIrLeaderboardFetched({
|
|
||||||
-- statusCode = 20,
|
|
||||||
-- body = {}
|
|
||||||
-- })
|
|
||||||
IR.Leaderboard(diff.hash, 'best', 4, onIrLeaderboardFetched)
|
IR.Leaderboard(diff.hash, 'best', 4, onIrLeaderboardFetched)
|
||||||
end
|
end
|
||||||
|
|
||||||
function dump(o)
|
local function tickTransitions(deltaTime)
|
||||||
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
|
if transitionScrollScale < 1 then
|
||||||
transitionScrollScale = transitionScrollScale + deltaTime / 0.1 -- transition should last for that time in seconds
|
transitionScrollScale = transitionScrollScale + deltaTime / 0.1 -- transition should last for that time in seconds
|
||||||
else
|
else
|
||||||
|
@ -942,8 +934,6 @@ function tickTransitions(deltaTime)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
transitionSearchBackgroundInfoAlpha = Easing.inOutQuad(transitionSearchInfoEnterScale)
|
|
||||||
|
|
||||||
-- Grade alpha
|
-- Grade alpha
|
||||||
if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then
|
if transitionAfterscrollScale >= 0.03 and transitionAfterscrollScale < 0.033 then
|
||||||
transitionAfterscrollGradeAlpha = 0.5
|
transitionAfterscrollGradeAlpha = 0.5
|
||||||
|
@ -1014,22 +1004,14 @@ function tickTransitions(deltaTime)
|
||||||
|
|
||||||
-- Flash transition
|
-- Flash transition
|
||||||
if transitionFlashScale < 1 then
|
if transitionFlashScale < 1 then
|
||||||
|
---@type number|string
|
||||||
local songBpm = 120
|
local songBpm = 120
|
||||||
|
|
||||||
if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then
|
if (songwheel.songs[selectedIndex] and game.GetSkinSetting('animations_affectWithBPM')) then
|
||||||
songBpm = songwheel.songs[selectedIndex].bpm
|
local songBpmStr = songwheel.songs[selectedIndex].bpm
|
||||||
|
local songBpmStrs = common.split(songBpmStr, '-')
|
||||||
-- Is a variable BPM
|
local minBpm = tonumber(songBpmStrs[1]) -- Lowest bpm value
|
||||||
if (type(songBpm) == "string") then
|
songBpm = minBpm or songBpm
|
||||||
local s = common.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
|
end
|
||||||
|
|
||||||
transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds
|
transitionFlashScale = transitionFlashScale + deltaTime / (60/songBpm) -- transition should last for that time in seconds
|
||||||
|
@ -1069,7 +1051,41 @@ function tickTransitions(deltaTime)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
draw_songwheel = function(deltaTime)
|
---This function is basically a workaround for the ForceRender call
|
||||||
|
local function drawRadar()
|
||||||
|
if not songselect_showEffectRadar then return end
|
||||||
|
if isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
||||||
|
|
||||||
|
local x, y = 375, 650
|
||||||
|
local scale = 0.666
|
||||||
|
|
||||||
|
gfx.FontSize(28)
|
||||||
|
gfx.Translate(x, y)
|
||||||
|
gfx.Scale(scale, scale)
|
||||||
|
|
||||||
|
local strokeColor = ColorRGBA.new(255, 255, 255, 128)
|
||||||
|
local fillColor = ColorRGBA.new(0, 0, 0, 191)
|
||||||
|
|
||||||
|
gfx.ResetScissor()
|
||||||
|
radar:drawBackground(fillColor)
|
||||||
|
radar:drawOutline(3, strokeColor)
|
||||||
|
|
||||||
|
--Bug: ForceRender resets every transformation, need to re-setup view transform afterwards
|
||||||
|
--ForceRender also resets gfx stack, USC will crash if you try to call gfx.Restore(),
|
||||||
|
--make sure the gfx stack is clean before calling radar:drawRadarMesh()
|
||||||
|
radar:drawRadarMesh()
|
||||||
|
|
||||||
|
Dim.transformToScreenSpace()
|
||||||
|
|
||||||
|
gfx.Save()
|
||||||
|
gfx.Translate(x, y)
|
||||||
|
gfx.Scale(scale, scale)
|
||||||
|
radar:drawRadialTicks(strokeColor)
|
||||||
|
radar:drawAttributes()
|
||||||
|
gfx.Restore()
|
||||||
|
end
|
||||||
|
|
||||||
|
local draw_songwheel = function(deltaTime)
|
||||||
drawBackground(deltaTime)
|
drawBackground(deltaTime)
|
||||||
|
|
||||||
drawSongList()
|
drawSongList()
|
||||||
|
@ -1077,6 +1093,9 @@ draw_songwheel = function(deltaTime)
|
||||||
isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1
|
isFilterWheelActive = game.GetSkinSetting('_songWheelOverlayActive') == 1
|
||||||
|
|
||||||
drawData()
|
drawData()
|
||||||
|
|
||||||
|
drawRadar()
|
||||||
|
|
||||||
drawCursor()
|
drawCursor()
|
||||||
|
|
||||||
drawFilterInfo(deltaTime)
|
drawFilterInfo(deltaTime)
|
||||||
|
@ -1098,6 +1117,7 @@ draw_songwheel = function(deltaTime)
|
||||||
gfx.ResetTransform()
|
gfx.ResetTransform()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line:lowercase-global
|
||||||
render = function (deltaTime)
|
render = function (deltaTime)
|
||||||
tickTransitions(deltaTime)
|
tickTransitions(deltaTime)
|
||||||
|
|
||||||
|
@ -1105,6 +1125,13 @@ render = function (deltaTime)
|
||||||
|
|
||||||
Sound.stopMusic()
|
Sound.stopMusic()
|
||||||
|
|
||||||
|
if 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()
|
Dim.updateResolution()
|
||||||
|
|
||||||
Wallpaper.render()
|
Wallpaper.render()
|
||||||
|
@ -1116,6 +1143,7 @@ render = function (deltaTime)
|
||||||
refreshIrLeaderboard(deltaTime)
|
refreshIrLeaderboard(deltaTime)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line:lowercase-global
|
||||||
songs_changed = function (withAll)
|
songs_changed = function (withAll)
|
||||||
|
|
||||||
irLeaderboardsCache = {} -- Reset LB cache
|
irLeaderboardsCache = {} -- Reset LB cache
|
||||||
|
@ -1130,24 +1158,28 @@ songs_changed = function (withAll)
|
||||||
local song = songwheel.allSongs[i]
|
local song = songwheel.allSongs[i]
|
||||||
for j = 1, #song.difficulties do
|
for j = 1, #song.difficulties do
|
||||||
local diff = song.difficulties[j]
|
local diff = song.difficulties[j]
|
||||||
diff.force = VolforceCalc.calc(diff)
|
table.insert(diffs, {hash = diff.hash, force = VolforceCalc.calc(diff)})
|
||||||
table.insert(diffs, diff)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(diffs, function (l, r)
|
table.sort(diffs, function (l, r)
|
||||||
return l.force > r.force
|
return l.force > r.force
|
||||||
end)
|
end)
|
||||||
totalForce = 0
|
|
||||||
|
local totalForce = 0
|
||||||
for i = 1, 50 do
|
for i = 1, 50 do
|
||||||
if diffs[i] then
|
local diff = diffs[i]
|
||||||
top50diffs[diffs[i].id] = true
|
if not diff then
|
||||||
totalForce = totalForce + diffs[i].force
|
break
|
||||||
end
|
end
|
||||||
|
top50diffs[diff.hash] = true
|
||||||
|
totalForce = totalForce + diff.force
|
||||||
end
|
end
|
||||||
|
|
||||||
game.SetSkinSetting('_volforce', totalForce)
|
game.SetSkinSetting('_volforce', totalForce)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line:lowercase-global
|
||||||
set_index = function(newIndex)
|
set_index = function(newIndex)
|
||||||
transitionScrollScale = 0
|
transitionScrollScale = 0
|
||||||
transitionAfterscrollScale = 0
|
transitionAfterscrollScale = 0
|
||||||
|
@ -1162,11 +1194,16 @@ set_index = function(newIndex)
|
||||||
scrollingUp = true
|
scrollingUp = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
updateRadar = true
|
||||||
|
|
||||||
game.PlaySample('song_wheel/cursor_change.wav')
|
game.PlaySample('song_wheel/cursor_change.wav')
|
||||||
|
|
||||||
selectedIndex = newIndex
|
selectedIndex = newIndex
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local json = require("common.json")
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line:lowercase-global
|
||||||
set_diff = function(newDiff)
|
set_diff = function(newDiff)
|
||||||
if newDiff ~= selectedDifficulty then
|
if newDiff ~= selectedDifficulty then
|
||||||
jacketCache = {} -- Clear the jacket cache for the new diff jackets
|
jacketCache = {} -- Clear the jacket cache for the new diff jackets
|
||||||
|
@ -1174,6 +1211,8 @@ set_diff = function(newDiff)
|
||||||
game.PlaySample('song_wheel/diff_change.wav')
|
game.PlaySample('song_wheel/diff_change.wav')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
updateRadar = true
|
||||||
|
|
||||||
selectedDifficulty = newDiff
|
selectedDifficulty = newDiff
|
||||||
irLeaderboard = {}
|
irLeaderboard = {}
|
||||||
irRequestStatus = 1
|
irRequestStatus = 1
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
#version 330
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
const float PI = 3.1415926535897932384626433832795;
|
||||||
|
const float PI_2 = 1.57079632679489661923;
|
||||||
|
|
||||||
|
layout(location=1) in vec2 fsTex;
|
||||||
|
layout(location=2) in vec3 fragPos;
|
||||||
|
|
||||||
|
layout(location=0) out vec4 target;
|
||||||
|
|
||||||
|
uniform vec4 colorCenter;
|
||||||
|
uniform vec4 colorMax;
|
||||||
|
|
||||||
|
uniform float maxSize;
|
||||||
|
|
||||||
|
float lerp(float x, vec2 p0, vec2 p1)
|
||||||
|
{
|
||||||
|
return p0.y + (p1.y - p0.y) * ((x - p0.x)/(p1.x - p0.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toHSV(vec4 rgb)
|
||||||
|
{
|
||||||
|
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||||
|
vec4 p = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g));
|
||||||
|
vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r));
|
||||||
|
|
||||||
|
float d = q.x - min(q.w, q.y);
|
||||||
|
float e = 1.0e-10;
|
||||||
|
return vec4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, rgb.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 toRGB(vec4 hsv)
|
||||||
|
{
|
||||||
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
|
vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return vec4(hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y), hsv.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// x is in [0.0, 1.0]
|
||||||
|
vec4 lerpColor(float x, vec4 color1, vec4 color2)
|
||||||
|
{
|
||||||
|
// convert RGB color space to HSV
|
||||||
|
vec4 hsv1 = toHSV(color1), hsv2 = toHSV(color2);
|
||||||
|
float hue = (mod(mod((hsv2.x-hsv1.x), 1.) + 1.5, 1.)-0.5)*x + hsv1.x;
|
||||||
|
vec4 hsv = vec4(hue, mix(hsv1.yzw, hsv2.yzw, x));
|
||||||
|
// convert HSV color space back to RGB
|
||||||
|
return toRGB(hsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
float EaseInSine(float x)
|
||||||
|
{
|
||||||
|
return 1.0 - cos((x * PI) / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 coordToColor(vec2 pos, float maxSize)
|
||||||
|
{
|
||||||
|
float r = length(pos);
|
||||||
|
float factor = lerp(r, vec2(0,0), vec2(maxSize,1));
|
||||||
|
return lerpColor(EaseInSine(factor), colorCenter, colorMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
target = coordToColor(fragPos.xy, maxSize);
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#version 330
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout(location=0) in vec2 inPos;
|
||||||
|
layout(location=1) in vec2 inTex;
|
||||||
|
|
||||||
|
out gl_PerVertex
|
||||||
|
{
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
layout(location=1) out vec2 fsTex;
|
||||||
|
layout(location=2) out vec3 fragPos;
|
||||||
|
|
||||||
|
uniform mat4 proj;
|
||||||
|
uniform mat4 world;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
fsTex = inTex;
|
||||||
|
gl_Position = proj * world * vec4(inPos.xy, 0, 1);
|
||||||
|
fragPos = vec3(inPos.xy, 0);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#version 330
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout(location=1) in vec2 fsTex;
|
||||||
|
layout(location=0) out vec4 target;
|
||||||
|
layout(location=2) in vec4 inColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
target = inColor;
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
#version 330
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
const float PI = 3.1415926535897932384626433832795;
|
||||||
|
const float PI_2 = 1.57079632679489661923;
|
||||||
|
|
||||||
|
layout(location=0) in vec2 inPos;
|
||||||
|
layout(location=1) in vec2 inTex;
|
||||||
|
|
||||||
|
out gl_PerVertex
|
||||||
|
{
|
||||||
|
vec4 gl_Position;
|
||||||
|
};
|
||||||
|
layout(location=1) out vec2 fsTex;
|
||||||
|
layout(location=2) out vec4 vertexColor;
|
||||||
|
|
||||||
|
uniform mat4 proj;
|
||||||
|
uniform mat4 world;
|
||||||
|
|
||||||
|
uniform vec4 colorCenter;
|
||||||
|
uniform vec4 colorMax;
|
||||||
|
|
||||||
|
uniform float maxSize;
|
||||||
|
|
||||||
|
// Polar coordinate utility functions
|
||||||
|
float hypot(vec2 pos)
|
||||||
|
{
|
||||||
|
return sqrt(pos.x * pos.x + pos.y * pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
float atan2(vec2 pos)
|
||||||
|
{
|
||||||
|
if (pos.x > 0)
|
||||||
|
{
|
||||||
|
return atan(pos.y / pos.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.x < 0 && pos.y >= 0)
|
||||||
|
{
|
||||||
|
return atan(pos.y / pos.x) + PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.x < 0 && pos.y < 0)
|
||||||
|
{
|
||||||
|
return atan(pos.y / pos.x) - PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.x == 0 && pos.y > 0)
|
||||||
|
{
|
||||||
|
return PI_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos.x == 0 && pos.y < 0)
|
||||||
|
{
|
||||||
|
return -PI_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is not mathematically correct, as it's undefined
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 toPolar(vec2 cartesian)
|
||||||
|
{
|
||||||
|
return vec2(hypot(pos), atan2(pos));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
float lerp(float x, vec2 p0, vec2 p1)
|
||||||
|
{
|
||||||
|
return p0.y + (p1.y - p0.y) * ((x - p0.x)/(p1.x - p0.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// x is in [0.0, 1.0]
|
||||||
|
vec4 lerpColor(float x, vec4 color1, vec4 color2)
|
||||||
|
{
|
||||||
|
return color1 + (color2 - color1) * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 coordToColor(vec2 pos, float maxSize)
|
||||||
|
{
|
||||||
|
float r = hypot(pos);
|
||||||
|
//float phi = atan2(pos);
|
||||||
|
|
||||||
|
float factor = lerp(r, vec2(0,0), vec2(maxSize,1));
|
||||||
|
|
||||||
|
return lerpColor(factor, colorCenter, colorMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = proj * world * vec4(inPos.xy, 0, 1);
|
||||||
|
vertexColor = coordToColor(inPos.xy, maxSize);
|
||||||
|
}
|
Loading…
Reference in New Issue