Compare commits
No commits in common. "master" and "fdigl-develop" have entirely different histories.
master
...
fdigl-deve
|
@ -1 +0,0 @@
|
|||
* text=auto
|
|
@ -1,21 +1,7 @@
|
|||
# IDE files
|
||||
.vscode
|
||||
|
||||
# secret(?) assets
|
||||
_asset
|
||||
song-assets
|
||||
skin-assets
|
||||
|
||||
# generated skin files
|
||||
nautica.json
|
||||
skin.cfg
|
||||
|
||||
# any crew that's not the default one, we do not package crew
|
||||
textures/crew/*
|
||||
!textures/crew/appeal_card.png
|
||||
!textures/crew/frame.png
|
||||
!textures/crew/portrait.png
|
||||
!textures/crew/make-a-crew/frame_glow.png
|
||||
!textures/crew/make-a-crew/frame_metal.png
|
||||
!textures/crew/make-a-crew/instructions.txt
|
||||
!textures/crew/anim/nothing
|
||||
.vscode
|
||||
/textures/crew/
|
75
README.md
75
README.md
|
@ -1,38 +1,37 @@
|
|||
# ExperimentalGear skin for USC
|
||||
|
||||
Project Starter: GSK Bladez
|
||||
|
||||
## Coding
|
||||
- [REDACTED]
|
||||
- Hersi
|
||||
- RealFD
|
||||
- fdigl
|
||||
- Hoshikara
|
||||
- GSK Bladez
|
||||
- Local
|
||||
- Kuenaimaku
|
||||
|
||||
## Graphics
|
||||
- GSK Bladez
|
||||
- Neardayo
|
||||
- YellowBird
|
||||
- Dengekiko
|
||||
|
||||
## Translation
|
||||
- GSK Bladez
|
||||
- Neardayo
|
||||
- RealFD
|
||||
|
||||
## Misc. Help
|
||||
- Neardayo
|
||||
- DDX
|
||||
- GM*DEO
|
||||
|
||||
## Beta Testing
|
||||
- Gam
|
||||
- TealStar
|
||||
- Dengikiko
|
||||
- Adamyes
|
||||
- Gio
|
||||
- Mattadome
|
||||
|
||||
# ExperimentalGear skin for USC
|
||||
|
||||
Project Starter: GSK Bladez
|
||||
|
||||
## Coding
|
||||
- [REDACTED]
|
||||
- Hersi
|
||||
- RealFD
|
||||
- fdigl
|
||||
- Hoshikara
|
||||
- GSK Bladez
|
||||
- Local
|
||||
|
||||
## Graphics
|
||||
- GSK Bladez
|
||||
- Neardayo
|
||||
- YellowBird
|
||||
- Dengekiko
|
||||
|
||||
## Translation
|
||||
- GSK Bladez
|
||||
- Neardayo
|
||||
- RealFD
|
||||
|
||||
## Misc. Help
|
||||
- Neardayo
|
||||
- DDX
|
||||
- GM*DEO
|
||||
|
||||
## Beta Testing
|
||||
- Gam
|
||||
- TealStar
|
||||
- Dengikiko
|
||||
- Adamyes
|
||||
- Gio
|
||||
- Mattadome
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,86 +1,86 @@
|
|||
local newFormatTemplate = {
|
||||
{
|
||||
Bg={
|
||||
Base={
|
||||
Tex={{"",""}},
|
||||
Tilt=false,
|
||||
OffsetY=0.17,
|
||||
Anim=true,
|
||||
ScaleSoft=false,
|
||||
ClampTiling=false,
|
||||
},
|
||||
Overlay={ -- is meant to be displayed overtop Base
|
||||
Tex="",
|
||||
Float=true,
|
||||
FloatFactor=2.0,
|
||||
OffsetY=0.12,
|
||||
FlashEffect=true,
|
||||
Tilt=true,
|
||||
},
|
||||
Layer={
|
||||
Tex={{"",""}},
|
||||
ScaleHard=false,
|
||||
Brighten=0.6,
|
||||
},
|
||||
u={
|
||||
Pivot=0.36
|
||||
},
|
||||
},
|
||||
Center={
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Scale=2.8,
|
||||
Pulse=true,
|
||||
Float=true,
|
||||
FloatFactor=0.5,
|
||||
FloatXFactor=0,
|
||||
FloatRotationFactor=0,
|
||||
FadeEffect=true,
|
||||
Tilt=false,
|
||||
Anim=true,
|
||||
OffsetY=-0.1,
|
||||
Glow=false,
|
||||
Rotate=false,
|
||||
},
|
||||
LayerEffect={
|
||||
Tex="",
|
||||
Fade=true,
|
||||
Rotate=true,
|
||||
RotateSpeed=-2.0,
|
||||
DodgeBlend=true,
|
||||
Glow=true,
|
||||
Alpha=0.7,
|
||||
Scale=0.5,
|
||||
},
|
||||
},
|
||||
Tunnel={ -- todo: give option to do sth with blend mode
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Stretch=0.3,
|
||||
ScaleX=0.8,
|
||||
ScaleY=0.9,
|
||||
Fog=10.0,
|
||||
FlashEffect=true,
|
||||
VortexEffect=false,
|
||||
VortexFactor=1.0,
|
||||
DodgeBlend=false
|
||||
}
|
||||
},
|
||||
Particle={
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Speed=0.3,
|
||||
OffsetY=-0.02,
|
||||
Amount=3,
|
||||
Scale=1.0,
|
||||
ExtraRotation=0.125
|
||||
},
|
||||
},
|
||||
luaParticleEffect={
|
||||
particles={
|
||||
{"star-particle.png", 32}
|
||||
}
|
||||
},
|
||||
speed=0.6,
|
||||
}
|
||||
local newFormatTemplate = {
|
||||
{
|
||||
Bg={
|
||||
Base={
|
||||
Tex={{"",""}},
|
||||
Tilt=false,
|
||||
OffsetY=0.17,
|
||||
Anim=true,
|
||||
ScaleSoft=false,
|
||||
ClampTiling=false,
|
||||
},
|
||||
Overlay={ -- is meant to be displayed overtop Base
|
||||
Tex="",
|
||||
Float=true,
|
||||
FloatFactor=2.0,
|
||||
OffsetY=0.12,
|
||||
FlashEffect=true,
|
||||
Tilt=true,
|
||||
},
|
||||
Layer={
|
||||
Tex={{"",""}},
|
||||
ScaleHard=false,
|
||||
Brighten=0.6,
|
||||
},
|
||||
u={
|
||||
Pivot=0.36
|
||||
},
|
||||
},
|
||||
Center={
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Scale=2.8,
|
||||
Pulse=true,
|
||||
Float=true,
|
||||
FloatFactor=0.5,
|
||||
FloatXFactor=0,
|
||||
FloatRotationFactor=0,
|
||||
FadeEffect=true,
|
||||
Tilt=false,
|
||||
Anim=true,
|
||||
OffsetY=-0.1,
|
||||
Glow=false,
|
||||
Rotate=false,
|
||||
},
|
||||
LayerEffect={
|
||||
Tex="",
|
||||
Fade=true,
|
||||
Rotate=true,
|
||||
RotateSpeed=-2.0,
|
||||
DodgeBlend=true,
|
||||
Glow=true,
|
||||
Alpha=0.7,
|
||||
Scale=0.5,
|
||||
},
|
||||
},
|
||||
Tunnel={ -- todo: give option to do sth with blend mode
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Stretch=0.3,
|
||||
ScaleX=0.8,
|
||||
ScaleY=0.9,
|
||||
Fog=10.0,
|
||||
FlashEffect=true,
|
||||
VortexEffect=false,
|
||||
VortexFactor=1.0,
|
||||
DodgeBlend=false
|
||||
}
|
||||
},
|
||||
Particle={
|
||||
Tex={{"",""}},
|
||||
u={
|
||||
Speed=0.3,
|
||||
OffsetY=-0.02,
|
||||
Amount=3,
|
||||
Scale=1.0,
|
||||
ExtraRotation=0.125
|
||||
},
|
||||
},
|
||||
luaParticleEffect={
|
||||
particles={
|
||||
{"star-particle.png", 32}
|
||||
}
|
||||
},
|
||||
speed=0.6,
|
||||
}
|
||||
}
|
|
@ -1,124 +1,68 @@
|
|||
{
|
||||
"User information": {"type": "label"},
|
||||
"username": {
|
||||
"type": "text",
|
||||
"label": "Username (max 8 characters)",
|
||||
"default": "GUEST"
|
||||
},
|
||||
|
||||
"separator_a": {},
|
||||
|
||||
"MSG": {
|
||||
"type": "text",
|
||||
"label": "Message (max 8 characters)",
|
||||
"default": "Hellooooooo"
|
||||
},
|
||||
|
||||
"separator_b": {},
|
||||
"Animations": {"type": "label"},
|
||||
|
||||
"animations_affectWithBPM": {
|
||||
"type": "bool",
|
||||
"label": "Affect speed of some animations with the current song's BPM",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"animations_skipIntro": {
|
||||
"type": "bool",
|
||||
"label": "Skip intro splash screens",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_c": {},
|
||||
|
||||
"Crew": { "type": "label" },
|
||||
"single_idol": {
|
||||
"label": "Crew idol animations, folder name in `crew/anim/`",
|
||||
"type": "text",
|
||||
"default": "nothing"
|
||||
},
|
||||
|
||||
"words": {
|
||||
"type": "selection",
|
||||
"label": "Language",
|
||||
"default": "EN",
|
||||
"values": ["EN", "DE", "SK", "test2"]
|
||||
},
|
||||
|
||||
"separator_d": {},
|
||||
"Audio": { "type": "label" },
|
||||
|
||||
"audio_systemVoice": {
|
||||
"label": "Turn on Rasis",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_e": {},
|
||||
"Gameplay": { "type": "label" },
|
||||
|
||||
"gameplay_ucDifferentColor": {
|
||||
"label": "Use different colors for UC and PUC chain numbers",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"gameplay_showSearchControls": {
|
||||
"label": "Show song select controls when searching",
|
||||
"type": "bool",
|
||||
"default": true
|
||||
},
|
||||
|
||||
"gameplay_earlyLateFor": {
|
||||
"label": "Show Early/Late display for",
|
||||
"type": "selection",
|
||||
"default": "NEAR (or worse)",
|
||||
"values": ["CRITICAL (or worse)", "NEAR (or worse)", "OFF"]
|
||||
},
|
||||
|
||||
"gameplay_earlyLatePosition": {
|
||||
"label": "Early/Late display position",
|
||||
"type": "selection",
|
||||
"default": "STANDARD",
|
||||
"values": ["UPPER+", "UPPER", "STANDARD", "LOWER"]
|
||||
},
|
||||
|
||||
"gameplay_msFor": {
|
||||
"label": "Show millisecond display for",
|
||||
"type": "selection",
|
||||
"default": "NEAR (or worse)",
|
||||
"values": ["ALL", "CRITICAL (or worse)", "NEAR (or worse)", "NONE"]
|
||||
},
|
||||
|
||||
"separator_f": {},
|
||||
"Debug": { "type": "label" },
|
||||
|
||||
"debug_showInformation": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
{
|
||||
"User information": {"type": "label"},
|
||||
"username": {
|
||||
"type": "text",
|
||||
"label": "Username (max 8 characters)",
|
||||
"default": "GUEST"
|
||||
},
|
||||
|
||||
"separator_a": {},
|
||||
|
||||
"MSG": {
|
||||
"type": "text",
|
||||
"label": "Message (max 8 characters)",
|
||||
"default": "Hellooooooo"
|
||||
},
|
||||
|
||||
"separator_b": {},
|
||||
"Animations": {"type": "label"},
|
||||
|
||||
"animations_affectWithBPM": {
|
||||
"type": "bool",
|
||||
"label": "Affect speed of some animations with the current song's BPM",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_c": {},
|
||||
|
||||
"Crew": { "type": "label" },
|
||||
"single_idol": {
|
||||
"label": "!!!ALWAYS MATCH THE NAME!!!",
|
||||
"type": "text",
|
||||
"default": "nothing"
|
||||
},
|
||||
|
||||
"words": {
|
||||
"type": "selection",
|
||||
"label": "Language",
|
||||
"default": "EN",
|
||||
"values": ["EN", "DE", "SK", "HU", "test2"]
|
||||
},
|
||||
|
||||
"separator_d": {},
|
||||
"Audio": { "type": "label" },
|
||||
|
||||
"audio_systemVoice": {
|
||||
"label": "Turn on Rasis",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_e": {},
|
||||
"Gameplay": { "type": "label" },
|
||||
|
||||
"gameplay_ucDifferentColor": {
|
||||
"label": "Use different colors for UC and PUC chain numbers",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
|
||||
"separator_f": {},
|
||||
"Debug": { "type": "label" },
|
||||
|
||||
"debug_showInformation": {
|
||||
"label": "Show debug information (sometimes in the middle of the screen when you're playing)",
|
||||
"type": "bool",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
-- IR State enum
|
||||
---@class States
|
||||
local States = {
|
||||
Unused = 0,
|
||||
Pending = 10,
|
||||
Success = 20,
|
||||
Accepted = 22,
|
||||
BadRequest = 40,
|
||||
Unauthorized = 41,
|
||||
ChartRefused = 42,
|
||||
Forbidden = 43,
|
||||
NotFound = 44,
|
||||
ServerError = 50,
|
||||
RequestFailure = 60
|
||||
}
|
||||
|
||||
---@class IRData
|
||||
---@field Active boolean # USC IR configured and active
|
||||
---@field States States # IR reposonse state enum
|
||||
IRData = {}
|
||||
|
||||
---@class IRHeartbeatResponseBody
|
||||
---@field serverTime integer
|
||||
---@field serverName string
|
||||
---@field irVersion string
|
||||
|
||||
---@class IRRecordResponseBody
|
||||
---@field record ServerScore
|
||||
|
||||
---@alias IRLeaderboardResponseBody ServerScore[]
|
||||
|
||||
---@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[]
|
||||
|
||||
-- Performs a Heartbeat request.
|
||||
---@param callback fun(res: IRHeartbeatResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Heartbeat(callback) end
|
||||
|
||||
-- Performs a Chart Tracked request for the chart with the provided hash.
|
||||
---@param hash string # song hash
|
||||
---@param callback fun(res: IRChartTrackedResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function ChartTracked(hash, callback) end
|
||||
|
||||
-- Performs a Record request for the chart with the provided hash.
|
||||
---@param hash string # song hash
|
||||
---@param callback fun(res: IRRecordResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Record(hash, callback) end
|
||||
|
||||
-- Performs a Leaderboard request for the chart with the provided hash, with parameters mode and n.
|
||||
---@param hash string # song hash
|
||||
---@param mode "best"|"rivals" # request leaderboard mode
|
||||
---@param n integer # limit the number of requested scores
|
||||
---@param callback fun(res: IRLeaderboardResponse) # Callback function receives IRResponse as it's first parameter
|
||||
local function Leaderboard(hash, mode, n, callback) end
|
||||
|
||||
---@class IR
|
||||
IR = {
|
||||
Heartbeat = Heartbeat,
|
||||
ChartTracked = ChartTracked,
|
||||
Record = Record,
|
||||
Leaderboard = Leaderboard
|
||||
}
|
|
@ -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
|
|
@ -7,7 +7,7 @@ GetButton = function(button) end
|
|||
|
||||
-- Gets the absolute rotation of the specified knob
|
||||
---@param knob integer # `0 = left`, `1 = right`
|
||||
---@return number angle # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW)
|
||||
---@return number angle # in radians, `0.0` to `2*pi`
|
||||
GetKnob = function(knob) end
|
||||
|
||||
-- Gets the color of the specified laser
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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,60 +25,42 @@
|
|||
---@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.
|
||||
---@field gaugeOpt integer # Reserved
|
||||
---@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.
|
||||
|
||||
---@class ServerScore
|
||||
---@field score integer # Submitted score
|
||||
---@field gauge number # Submitted Gauge result
|
||||
---@field timestamp integer # Unix timestamp of the score
|
||||
---@field crit integer # Hits inside the critical window
|
||||
---@field near integer # Hits inside the near window
|
||||
---@field early integer # Hits inside the near window which were early
|
||||
---@field late integer # Hits inside the near window which were late
|
||||
---@field combo integer # Best combo reached
|
||||
---@field error integer # Missed notes
|
||||
---@field options ServerScoreOptions # The options in use. Includes gauge type, etc.
|
||||
---@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
|
||||
|
||||
---@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,18 +68,16 @@
|
|||
---@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
|
||||
---@field irState integer # Current state of the IR score submission request (a USC-IR code, including extensions 0/10/60)
|
||||
---@field irDescription string # The description in the IR response (nil if irState is 0 or 10)
|
||||
---@field irScores ServerScore[]|nil # Score submission result, nil if irState != 20
|
||||
---@field irState integer # Internet ranking flag
|
||||
---@field isSelf boolean # Only for multiplayer, `false` if score is of another player
|
||||
---@field jacketPath string # Full filepath to the jacket image on the disk
|
||||
---@field laserHitStats HitStat[]|nil # Hit stats for every laser object, only available for singleplayer if `isSelf = true`
|
||||
---@field 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 +87,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 = {};
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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 = {};
|
|
@ -1,35 +0,0 @@
|
|||
local function Exit() end
|
||||
|
||||
local function Settings() end
|
||||
|
||||
local function Start() end
|
||||
|
||||
local function DLScreen() end
|
||||
|
||||
local function Update() end
|
||||
|
||||
local function Multiplayer() end
|
||||
|
||||
local function Challenges() end
|
||||
|
||||
Menu = {
|
||||
Exit = Exit,
|
||||
Settings = Settings,
|
||||
Start = Start,
|
||||
DLScreen = DLScreen,
|
||||
Update = Update,
|
||||
Multiplayer = Multiplayer,
|
||||
Challenges = Challenges
|
||||
}
|
||||
|
||||
--- Render frame for titlescreen
|
||||
---@param deltaTime number Elapsed frametime since last frame
|
||||
function render(deltaTime) end
|
||||
|
||||
--- Button event handler for titlescreen
|
||||
---@param buttonCode integer Corresponds to game.Button_*
|
||||
function button_pressed(buttonCode) end
|
||||
|
||||
--- Mouse event handler for titlescreen
|
||||
---@param button integer
|
||||
function mouse_pressed(button) end
|
|
@ -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.
BIN
fonts/contb.ttf
BIN
fonts/contb.ttf
Binary file not shown.
BIN
fonts/contl.ttf
BIN
fonts/contl.ttf
Binary file not shown.
BIN
fonts/contm.ttf
BIN
fonts/contm.ttf
Binary file not shown.
Binary file not shown.
|
@ -1,202 +0,0 @@
|
|||
|
||||
require "common.class"
|
||||
|
||||
require "api.graphics"
|
||||
|
||||
local Image = require "api.image"
|
||||
|
||||
---@class AnimationParams
|
||||
---@field fps number?
|
||||
---@field loop boolean?
|
||||
---@field loopPoint integer?
|
||||
---@field width number?
|
||||
---@field height number?
|
||||
---@field x number?
|
||||
---@field y number?
|
||||
---@field scaleX number?
|
||||
---@field scaleY number?
|
||||
---@field centered boolean?
|
||||
---@field blendOp integer?
|
||||
---@field color number[]?
|
||||
---@field alpha number?
|
||||
---@field stroke StrokeParams?
|
||||
|
||||
---@class Animation
|
||||
---@field frames Image[]
|
||||
---@field frameCount integer
|
||||
---@field frameTime number
|
||||
---@field loop boolean
|
||||
---@field loopPoint integer
|
||||
---@field width number?
|
||||
---@field height number?
|
||||
---@field x number?
|
||||
---@field y number?
|
||||
---@field scaleX number?
|
||||
---@field scaleY number?
|
||||
---@field centered boolean?
|
||||
---@field blendOp integer?
|
||||
---@field color number[]?
|
||||
---@field alpha number?
|
||||
---@field stroke StrokeParams?
|
||||
local Animation = { };
|
||||
|
||||
---@class AnimationState
|
||||
---@field animation Animation # The animation data this state is playing through
|
||||
---@field frameIndex integer # Current frame in the animation
|
||||
---@field timer number # Timer used to determine when to change to the next frame
|
||||
---@field running boolean # Is the animation currently running and accepting updates?
|
||||
---@field callback function? # Called when the animation completes
|
||||
local AnimationState = { };
|
||||
|
||||
local function loadSequentialAnimationFrames(animPath)
|
||||
local frames = { };
|
||||
local count = 0;
|
||||
|
||||
local detectedFormat = nil;
|
||||
|
||||
while (true) do
|
||||
local frame = nil;
|
||||
if (detectedFormat) then
|
||||
frame = Image.new(detectedFormat:format(animPath, count + 1), true);
|
||||
else
|
||||
for i = 1, 4 do
|
||||
local format = '%s/%0' .. i .. 'd.png';
|
||||
frame = Image.new(format:format(animPath, count + 1), true);
|
||||
|
||||
if (frame) then
|
||||
detectedFormat = format;
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (not frame) then
|
||||
break;
|
||||
end
|
||||
|
||||
count = count + 1;
|
||||
frames[count] = frame;
|
||||
end
|
||||
|
||||
return frames, count;
|
||||
end
|
||||
|
||||
---Animation constructor
|
||||
---@param animPath string
|
||||
---@param params AnimationParams
|
||||
---@return Animation
|
||||
function Animation.new(animPath, params)
|
||||
local frames, frameCount = loadSequentialAnimationFrames(animPath);
|
||||
|
||||
local instance = {
|
||||
frames = frames,
|
||||
frameCount = frameCount,
|
||||
|
||||
frameTime = 1 / (params.fps or 30),
|
||||
loop = params.loop or false,
|
||||
loopPoint = params.loopPoint or 1,
|
||||
};
|
||||
|
||||
if (params.width ~= nil) then instance.width = params.width; end
|
||||
if (params.height ~= nil) then instance.height = params.height; end
|
||||
if (params.x ~= nil) then instance.x = params.x; end
|
||||
if (params.y ~= nil) then instance.y = params.y; end
|
||||
if (params.scaleX ~= nil) then instance.scaleX = params.scaleX; end
|
||||
if (params.scaleY ~= nil) then instance.scaleY = params.scaleY; end
|
||||
if (params.centered ~= nil) then instance.centered = params.centered; end
|
||||
if (params.blendOp ~= nil) then instance.blendOp = params.blendOp; end
|
||||
if (params.color ~= nil) then instance.color = params.color; end
|
||||
if (params.alpha ~= nil) then instance.alpha = params.alpha; end
|
||||
if (params.stroke ~= nil) then instance.stroke = params.stroke; end
|
||||
|
||||
return CreateInstance(Animation, instance);
|
||||
end
|
||||
|
||||
---Create an AnimationState to play this animation.
|
||||
---The AnimationState is not started.
|
||||
---@param callback function?
|
||||
---@return AnimationState
|
||||
function Animation:createState(callback)
|
||||
---@type AnimationState
|
||||
local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false };
|
||||
return CreateInstance(AnimationState, state);
|
||||
end
|
||||
|
||||
---Create an AnimationState to play this animation and start it.
|
||||
---@param callback function?
|
||||
---@return AnimationState
|
||||
function Animation:start(callback)
|
||||
local state = self:createState(callback);
|
||||
state:start();
|
||||
|
||||
return state;
|
||||
end
|
||||
|
||||
---Start this AnimationState.
|
||||
---Does nothing if it's already running.
|
||||
function AnimationState:start()
|
||||
self.running = true;
|
||||
end
|
||||
|
||||
---Restart this AnimationState.
|
||||
---The frame index is reset to 1.
|
||||
function AnimationState:restart()
|
||||
self.running = true;
|
||||
self.frameIndex = 1;
|
||||
self.timer = 0;
|
||||
end
|
||||
|
||||
---Stop this AnimationState.
|
||||
function AnimationState:stop()
|
||||
self.running = false;
|
||||
end
|
||||
|
||||
---Updates this AnimationState and then rendersit, passing on the given ImageParams to each frame.
|
||||
---@param deltaTime number
|
||||
---@param params? ImageParams
|
||||
function AnimationState:render(deltaTime, params)
|
||||
if (not self.running) then return; end;
|
||||
|
||||
self.timer = self.timer + deltaTime;
|
||||
|
||||
while (self.timer > self.animation.frameTime) do
|
||||
self.timer = self.timer - self.animation.frameTime;
|
||||
self.frameIndex = self.frameIndex + 1;
|
||||
|
||||
if (self.frameIndex > self.animation.frameCount) then
|
||||
if (self.animation.loop) then
|
||||
self.frameIndex = self.animation.loopPoint;
|
||||
else
|
||||
self.running = false;
|
||||
|
||||
if (self.callback) then
|
||||
self.callback();
|
||||
end
|
||||
|
||||
return;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (params) then
|
||||
if (params.width == nil) then params.width = self.animation.width; end
|
||||
if (params.height == nil) then params.height = self.animation.height; end
|
||||
if (params.x == nil) then params.x = self.animation.x; end
|
||||
if (params.y == nil) then params.y = self.animation.y; end
|
||||
if (params.scaleX == nil) then params.scaleX = self.animation.scaleX; end
|
||||
if (params.scaleY == nil) then params.scaleY = self.animation.scaleY; end
|
||||
if (params.centered == nil) then params.centered = self.animation.centered; end
|
||||
if (params.blendOp == nil) then params.blendOp = self.animation.blendOp; end
|
||||
if (params.alpha == nil) then params.alpha = self.animation.alpha; end
|
||||
if (params.stroke == nil) then params.stroke = self.animation.stroke; end
|
||||
end
|
||||
|
||||
local frame = self.animation.frames[self.frameIndex];
|
||||
if (not frame) then
|
||||
-- TODO(local): what do
|
||||
else
|
||||
frame:render(params);
|
||||
end
|
||||
end
|
||||
|
||||
return Animation;
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
-- TODO(local): put these class types somewhere more common
|
||||
---@class StrokeParams
|
||||
---@field color number[]?
|
||||
---@field alpha number?
|
||||
---@field size number?
|
|
@ -1,171 +0,0 @@
|
|||
|
||||
require "common.class"
|
||||
|
||||
require "api.graphics"
|
||||
|
||||
---@class ImageParams
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field x number?
|
||||
---@field y number?
|
||||
---@field scaleX number?
|
||||
---@field scaleY number?
|
||||
---@field centered boolean?
|
||||
---@field blendOp integer?
|
||||
---@field color number[]?
|
||||
---@field alpha number?
|
||||
---@field stroke StrokeParams?
|
||||
|
||||
---@class Image
|
||||
---@field handle integer
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field x number?
|
||||
---@field y number?
|
||||
---@field scaleX number?
|
||||
---@field scaleY number?
|
||||
---@field centered boolean?
|
||||
---@field blendOp integer?
|
||||
---@field color number[]?
|
||||
---@field alpha number?
|
||||
---@field stroke StrokeParams?
|
||||
local Image = { };
|
||||
|
||||
---Image constructor
|
||||
---@param imagePath string # The path to the skin image to load
|
||||
---@return Image
|
||||
function Image.new(imagePath, noFallback)
|
||||
local handle = gfx.CreateSkinImage(imagePath or '', 0);
|
||||
if (not handle) then
|
||||
game.Log('Failed to load image "' .. imagePath .. '"', game.LOGGER_ERROR);
|
||||
|
||||
if (noFallback) then return nil; end
|
||||
|
||||
handle = gfx.CreateSkinImage('missing.png', 0);
|
||||
if (not handle) then
|
||||
game.Log('Failed to load fallback image "missing.png"', game.LOGGER_ERROR);
|
||||
end
|
||||
end
|
||||
|
||||
local width, height = 64, 64;
|
||||
if (handle) then
|
||||
width, height = gfx.ImageSize(handle);
|
||||
end
|
||||
|
||||
local instance = {
|
||||
handle = handle,
|
||||
width = width,
|
||||
height = height,
|
||||
};
|
||||
|
||||
return CreateInstance(Image, instance);
|
||||
end
|
||||
|
||||
---Set the width and height of this Image.
|
||||
---@param width number
|
||||
---@param height number
|
||||
---@return Image # Returns self for method chaining
|
||||
function Image:setSize(width, height)
|
||||
if (type(width) ~= "number") then width = 0; end
|
||||
if (type(height) ~= "number") then height = 0; end
|
||||
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
|
||||
return self;
|
||||
end
|
||||
|
||||
---Set the stored position for this Image.
|
||||
---If the position of this Image will not change frequently,
|
||||
---using this method allows you to cache the render position
|
||||
---instead of passing it to the render method on each invocation.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@return Image # Returns self for method chaining
|
||||
function Image:setPosition(x, y)
|
||||
if (type(x) ~= "number") then x = 0; end
|
||||
if (type(y) ~= "number") then y = 0; end
|
||||
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
|
||||
return self;
|
||||
end
|
||||
|
||||
---Renders this Image, applying any of the given ImageParams,
|
||||
---then any of the cached Image fields, then any default values.
|
||||
---@param params? ImageParams
|
||||
function Image:render(params)
|
||||
params = params or { };
|
||||
|
||||
local sx = params.scaleX or self.scaleX or 1;
|
||||
local sy = params.scaleY or self.scaleY or 1;
|
||||
|
||||
local x = params.x or self.x or 0;
|
||||
local y = params.y or self.y or 0;
|
||||
|
||||
local w = (params.width or self.width ) * sx;
|
||||
local h = (params.height or self.height) * sy;
|
||||
|
||||
if (params.centered or self.centered) then
|
||||
x = x - w / 2;
|
||||
y = y - h / 2;
|
||||
end
|
||||
|
||||
local blendOp = params.blendOp or self.blendOp or gfx.BLEND_OP_SOURCE_OVER;
|
||||
|
||||
local r = 255;
|
||||
local g = 255;
|
||||
local b = 255;
|
||||
|
||||
if (params.color) then
|
||||
r = params.color[1];
|
||||
g = params.color[2];
|
||||
b = params.color[3];
|
||||
elseif (self.color) then
|
||||
r = self.color[1];
|
||||
g = self.color[2];
|
||||
b = self.color[3];
|
||||
end
|
||||
|
||||
local a = params.alpha or self.alpha or 1;
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.GlobalCompositeOperation(blendOp);
|
||||
|
||||
if (not self.handle) then
|
||||
gfx.FillColor(r, g, b, a);
|
||||
gfx.Rect(x, y, w, h);
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
else
|
||||
gfx.SetImageTint(r, g, b);
|
||||
gfx.ImageRect(x, y, w, h, self.handle, a, 0);
|
||||
gfx.SetImageTint(255, 255, 255);
|
||||
end
|
||||
|
||||
if (params.stroke or self.stroke) then
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
|
||||
if (params.stroke.color) then
|
||||
r = params.stroke.color[1];
|
||||
g = params.stroke.color[2];
|
||||
b = params.stroke.color[3];
|
||||
elseif (self.stroke and self.stroke.color) then
|
||||
r = self.stroke.color[1];
|
||||
g = self.stroke.color[2];
|
||||
b = self.stroke.color[3];
|
||||
end
|
||||
|
||||
a = params.stroke.alpha or (self.stroke and self.stroke.alpha) or 255;
|
||||
|
||||
local size = params.stroke.size or (self.stroke and self.stroke.size) or 1;
|
||||
|
||||
gfx.StrokeColor(r, g, b, a);
|
||||
gfx.StrokeWidth(size);
|
||||
gfx.Stroke();
|
||||
end
|
||||
end
|
||||
|
||||
return Image;
|
|
@ -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
|
|
@ -1,325 +1,302 @@
|
|||
local Easing = require("common.easing");
|
||||
local Footer = require("components.footer");
|
||||
local DiffRectangle = require('components.diff_rectangle');
|
||||
local common = require('common.util');
|
||||
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
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
|
||||
local function resolutionChange(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
end
|
||||
|
||||
local bgSfxPlayed = false;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
local HEADER_HEIGHT = 100
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
local resultBgImage = gfx.CreateSkinImage("challenge_result/bg.png", 0);
|
||||
local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_info_overlay_bg.png", 0);
|
||||
|
||||
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 appealCardImage = gfx.CreateSkinImage("crew/appeal_card.png", 0);
|
||||
local danBadgeImage = gfx.CreateSkinImage("dan/inf.png", 0);
|
||||
local crewImage = gfx.CreateSkinImage("crew/portrait.png", 0);
|
||||
|
||||
local notchesImage = gfx.CreateSkinImage("challenge_result/notches.png", 0);
|
||||
local trackBarsImage = gfx.CreateSkinImage("challenge_result/track_bars.png", 0);
|
||||
|
||||
local completionFailImage = gfx.CreateSkinImage("challenge_result/pass_states/fail.png", 0);
|
||||
local completionPassImage = gfx.CreateSkinImage("challenge_result/pass_states/pass.png", 0);
|
||||
|
||||
local irHeartbeatRequested = false;
|
||||
local IRserverName = "";
|
||||
|
||||
local badgeImages = {
|
||||
gfx.CreateSkinImage("song_select/medal/nomedal.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/played.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/clear.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/hard.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/uc.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/puc.png", 1),
|
||||
}
|
||||
|
||||
local gradeImages = {
|
||||
S = gfx.CreateSkinImage("common/grades/S.png", 0),
|
||||
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
|
||||
AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0),
|
||||
AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0),
|
||||
AA = gfx.CreateSkinImage("common/grades/AA.png", 0),
|
||||
A_P = gfx.CreateSkinImage("common/grades/A+.png", 0),
|
||||
A = gfx.CreateSkinImage("common/grades/A.png", 0),
|
||||
B = gfx.CreateSkinImage("common/grades/B.png", 0),
|
||||
C = gfx.CreateSkinImage("common/grades/C.png", 0),
|
||||
D = gfx.CreateSkinImage("common/grades/D.png", 0),
|
||||
none = gfx.CreateSkinImage("common/grades/none.png", 0),
|
||||
}
|
||||
|
||||
local percRequired = nil;
|
||||
local percGet = nil;
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample("challenge_result.wav")
|
||||
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
desw = 1080
|
||||
desh = 1920
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
local function handleSfx()
|
||||
if not bgSfxPlayed then
|
||||
Sound.stopMusic();
|
||||
game.PlaySample("challenge_result.wav", true)
|
||||
bgSfxPlayed = true
|
||||
end
|
||||
if game.GetButton(game.BUTTON_STA) then
|
||||
game.StopSample("challenge_result.wav")
|
||||
end
|
||||
if game.GetButton(game.BUTTON_BCK) then
|
||||
game.StopSample("challenge_result.wav")
|
||||
end
|
||||
end
|
||||
|
||||
function drawBackground()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, desw, desh, resultBgImage, 1, 0);
|
||||
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 - 209, HEADER_HEIGHT / 2 - 52, 419, 105, headerTitleImage, 1, 0)
|
||||
end
|
||||
|
||||
function drawPlayerInfo()
|
||||
-- Draw crew
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(460, 215, 522, 362, crewImage, 1, 0);
|
||||
|
||||
-- Draw the info bg
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(300, 352, 374 * 0.85, 222 * 0.85, playerInfoOverlayBgImage, 1, 0);
|
||||
|
||||
-- Draw appeal card
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(145, 364, 103 * 1.25, 132 * 1.25, appealCardImage, 1, 0);
|
||||
|
||||
-- Draw description
|
||||
gfx.FontSize(28)
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text("Hellooooooo", 310, 370);
|
||||
|
||||
-- Draw username
|
||||
gfx.FontSize(40)
|
||||
gfx.Text(username, 310, 413);
|
||||
|
||||
-- Draw IR server name
|
||||
gfx.FontSize(28)
|
||||
gfx.Text(IRserverName, 310, 453);
|
||||
|
||||
-- Draw dan badge
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(311, 490, 107 * 1.25, 29 * 1.25, danBadgeImage, 1, 0);
|
||||
end
|
||||
|
||||
local scoreNumber = Numbers.load_number_image("score_num");
|
||||
|
||||
function drawChartResult(deltaTime, x, y, chartResult)
|
||||
gfx.Save()
|
||||
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
||||
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
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)
|
||||
|
||||
local score = chartResult.score or 0;
|
||||
|
||||
Numbers.draw_number(x + 500, y+80, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.30, 1.12)
|
||||
Numbers.draw_number(x + 655, y+85, 1.0, score, 4, scoreNumber, true, 0.22, 1.12)
|
||||
|
||||
|
||||
local gradeImageKey = string.gsub(chartResult.grade, '+', '_P');
|
||||
local gradeImage = gradeImages[gradeImageKey] or gradeImages.D
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+800, y+12, 79, 79, gradeImage, 1, 0)
|
||||
|
||||
|
||||
if chartResult.badge then
|
||||
local badgeImage = badgeImages[chartResult.badge+1];
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+900, y+16, 79*1.05, 69*1.05, badgeImage, 1, 0)
|
||||
end
|
||||
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function drawScorePanelContent(deltaTime)
|
||||
-- game.Log("Drawing scores...", game.LOGGER_INFO) -- debug
|
||||
for i, chart in ipairs(result.charts) do
|
||||
-- if chart.score == nil then
|
||||
-- game.Log("Score does not exist? Quitting loop...", game.LOGGER_WARNING)
|
||||
-- break
|
||||
-- end
|
||||
|
||||
drawChartResult(deltaTime, 0, 836+(165*(i-1)), chart);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function drawDecorations()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(118, 846.5, 43*0.855, 429*0.855, notchesImage, 1, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(400, 807, 367*0.857, 429*0.857, trackBarsImage, 1, 0)
|
||||
end
|
||||
|
||||
function drawCompletion()
|
||||
local completitionImage = completionFailImage;
|
||||
if result.passed then
|
||||
completitionImage = completionPassImage;
|
||||
end
|
||||
|
||||
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);
|
||||
gfx.Fill()
|
||||
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)
|
||||
percRequired = percNumber;
|
||||
end
|
||||
end
|
||||
|
||||
if (percRequired == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
game.Log(percRequired, game.LOGGER_ERROR);
|
||||
|
||||
local a = 0;
|
||||
for i, chart in ipairs(result.charts) do
|
||||
a = a + chart.percent;
|
||||
game.Log('#' .. i .. ' got ' .. chart.percent .. '% // ACC at ' .. a, game.LOGGER_ERROR);
|
||||
end
|
||||
percGet = a / #result.charts;
|
||||
end
|
||||
|
||||
|
||||
local IR_HeartbeatResponse = function(res)
|
||||
if res.statusCode == IRData.States.Success then
|
||||
IRserverName = 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
|
||||
|
||||
local function drawResultScreen(x, y, w, h, deltaTime)
|
||||
gfx.BeginPath()
|
||||
|
||||
gfx.Translate(x, y);
|
||||
gfx.Scale(w / 1080, h / 1920);
|
||||
gfx.Scissor(0, 0, 1080, 1920);
|
||||
|
||||
handleSfx()
|
||||
IR_Handle()
|
||||
|
||||
drawBackground()
|
||||
|
||||
drawDecorations()
|
||||
|
||||
drawPlayerInfo()
|
||||
|
||||
drawScorePanelContent(deltaTime)
|
||||
|
||||
drawCompletion()
|
||||
|
||||
drawHeader()
|
||||
Footer.draw(deltaTime);
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution()
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
gfx.Rect(0, 0, resX, resY)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
|
||||
gfx.Fill()
|
||||
|
||||
drawResultScreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
|
||||
end
|
||||
local Easing = require("common.easing");
|
||||
local Footer = require("components.footer");
|
||||
local DiffRectangle = require('components.diff_rectangle');
|
||||
local common = require('common.common');
|
||||
local Numbers = require('common.numbers')
|
||||
|
||||
local VolforceWindow = require("components.volforceWindow")
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
|
||||
local function resolutionChange(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
end
|
||||
|
||||
local bgSfxPlayed = false;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
local HEADER_HEIGHT = 100
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
local resultBgImage = gfx.CreateSkinImage("challenge_result/bg.png", 0);
|
||||
local playerInfoOverlayBgImage = gfx.CreateSkinImage("challenge_result/player_info_overlay_bg.png", 0);
|
||||
|
||||
local headerTitleImage = gfx.CreateSkinImage("challenge_result/header/title.png", 0);
|
||||
|
||||
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);
|
||||
|
||||
local notchesImage = gfx.CreateSkinImage("challenge_result/notches.png", 0);
|
||||
local trackBarsImage = gfx.CreateSkinImage("challenge_result/track_bars.png", 0);
|
||||
|
||||
local completionFailImage = gfx.CreateSkinImage("challenge_result/pass_states/fail.png", 0);
|
||||
local completionPassImage = gfx.CreateSkinImage("challenge_result/pass_states/pass.png", 0);
|
||||
|
||||
local irHeartbeatRequested = false;
|
||||
local IRserverName = "";
|
||||
|
||||
local badgeImages = {
|
||||
gfx.CreateSkinImage("song_select/medal/nomedal.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/played.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/clear.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/hard.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/uc.png", 1),
|
||||
gfx.CreateSkinImage("song_select/medal/puc.png", 1),
|
||||
}
|
||||
|
||||
local gradeImages = {
|
||||
S = gfx.CreateSkinImage("common/grades/S.png", 0),
|
||||
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
|
||||
AAA = gfx.CreateSkinImage("common/grades/AAA.png", 0),
|
||||
AA_P = gfx.CreateSkinImage("common/grades/AA+.png", 0),
|
||||
AA = gfx.CreateSkinImage("common/grades/AA.png", 0),
|
||||
A_P = gfx.CreateSkinImage("common/grades/A+.png", 0),
|
||||
A = gfx.CreateSkinImage("common/grades/A.png", 0),
|
||||
B = gfx.CreateSkinImage("common/grades/B.png", 0),
|
||||
C = gfx.CreateSkinImage("common/grades/C.png", 0),
|
||||
D = gfx.CreateSkinImage("common/grades/D.png", 0),
|
||||
none = gfx.CreateSkinImage("common/grades/none.png", 0),
|
||||
}
|
||||
|
||||
local percRequired = 1;
|
||||
local percGet = 0;
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample("challenge_result.wav")
|
||||
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
desw = 1080
|
||||
desh = 1920
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
local function handleSfx()
|
||||
if not bgSfxPlayed then
|
||||
common.stopMusic();
|
||||
game.PlaySample("challenge_result.wav", true)
|
||||
bgSfxPlayed = true
|
||||
end
|
||||
if game.GetButton(game.BUTTON_STA) then
|
||||
game.StopSample("challenge_result.wav")
|
||||
end
|
||||
if game.GetButton(game.BUTTON_BCK) then
|
||||
game.StopSample("challenge_result.wav")
|
||||
end
|
||||
end
|
||||
|
||||
function drawBackground()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, desw, desh, resultBgImage, 1, 0);
|
||||
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 - 209, HEADER_HEIGHT / 2 - 52, 419, 105, headerTitleImage, 1, 0)
|
||||
end
|
||||
|
||||
function drawPlayerInfo()
|
||||
-- Draw crew
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(460, 215, 522, 362, crewImage, 1, 0);
|
||||
|
||||
-- Draw the info bg
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(300, 352, 374 * 0.85, 222 * 0.85, playerInfoOverlayBgImage, 1, 0);
|
||||
|
||||
-- Draw appeal card
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(145, 364, 103 * 1.25, 132 * 1.25, appealCardImage, 1, 0);
|
||||
|
||||
-- Draw description
|
||||
gfx.FontSize(28)
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text("Hellooooooo", 310, 370);
|
||||
|
||||
-- Draw username
|
||||
gfx.FontSize(40)
|
||||
gfx.Text(username, 310, 413);
|
||||
|
||||
-- Draw IR server name
|
||||
gfx.FontSize(28)
|
||||
gfx.Text(IRserverName, 310, 453);
|
||||
|
||||
-- Draw dan badge
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(311, 490, 107 * 1.25, 29 * 1.25, danBadgeImage, 1, 0);
|
||||
end
|
||||
|
||||
local scoreNumber = Numbers.load_number_image("score_num");
|
||||
|
||||
function drawChartResult(deltaTime, x, y, chartResult)
|
||||
gfx.Save()
|
||||
gfx.LoadSkinFont('NotoSans-Regular.ttf')
|
||||
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath()
|
||||
gfx.GlobalAlpha(1);
|
||||
gfx.Text(chartResult.title, x+160,y+32);
|
||||
|
||||
DiffRectangle.render(deltaTime, x+287.5, y+67, 0.85, chartResult.difficulty, chartResult.level)
|
||||
|
||||
local score = chartResult.score or 0;
|
||||
|
||||
Numbers.draw_number(x + 500, y+80, 1.0, math.floor(score / 10000), 4, scoreNumber, true, 0.30, 1.12)
|
||||
Numbers.draw_number(x + 655, y+85, 1.0, score, 4, scoreNumber, true, 0.22, 1.12)
|
||||
|
||||
|
||||
local gradeImageKey = string.gsub(chartResult.grade, '+', '_P');
|
||||
local gradeImage = gradeImages[gradeImageKey] or gradeImages.D
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+800, y+12, 79, 79, gradeImage, 1, 0)
|
||||
|
||||
|
||||
if chartResult.badge then
|
||||
local badgeImage = badgeImages[chartResult.badge+1];
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+900, y+16, 79*1.05, 69*1.05, badgeImage, 1, 0)
|
||||
end
|
||||
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function drawScorePanelContent(deltaTime)
|
||||
-- game.Log("Drawing scores...", game.LOGGER_INFO) -- debug
|
||||
for i, chart in ipairs(result.charts) do
|
||||
-- if chart.score == nil then
|
||||
-- game.Log("Score does not exist? Quitting loop...", game.LOGGER_WARNING)
|
||||
-- break
|
||||
-- end
|
||||
|
||||
drawChartResult(deltaTime, 0, 836+(165*(i-1)), chart);
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function drawDecorations()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(118, 846.5, 43*0.855, 429*0.855, notchesImage, 1, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(400, 807, 367*0.857, 429*0.857, trackBarsImage, 1, 0)
|
||||
end
|
||||
|
||||
function drawCompletion()
|
||||
local completitionImage = completionFailImage;
|
||||
if result.passed then
|
||||
completitionImage = completionPassImage;
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(63, 1331, 766*0.85, 130*0.85, completitionImage, 1, 0)
|
||||
|
||||
Numbers.draw_number(925, 1370, 1.0, percGet, 3, scoreNumber, true, 0.3, 1.12)
|
||||
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.Rect(741, 1402, 278*math.min(1, percGet / percRequired), 6);
|
||||
gfx.FillColor(255, 128, 0, 255);
|
||||
gfx.Fill()
|
||||
end
|
||||
|
||||
|
||||
function result_set()
|
||||
local reqTextWords = common.splitString(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)
|
||||
percRequired = percNumber;
|
||||
end
|
||||
end
|
||||
|
||||
game.Log(percRequired, game.LOGGER_ERROR);
|
||||
|
||||
local a = 0;
|
||||
for i, chart in ipairs(result.charts) do
|
||||
a = a + chart.percent;
|
||||
game.Log('#' .. i .. ' got ' .. chart.percent .. '% // ACC at ' .. a, game.LOGGER_ERROR);
|
||||
end
|
||||
percGet = a / #result.charts;
|
||||
end
|
||||
|
||||
|
||||
local IR_HeartbeatResponse = function(res)
|
||||
if res.statusCode == IRData.States.Success then
|
||||
IRserverName = 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
|
||||
|
||||
local function drawResultScreen(x, y, w, h, deltaTime)
|
||||
gfx.BeginPath()
|
||||
|
||||
gfx.Translate(x, y);
|
||||
gfx.Scale(w / 1080, h / 1920);
|
||||
gfx.Scissor(0, 0, 1080, 1920);
|
||||
|
||||
handleSfx()
|
||||
IR_Handle()
|
||||
|
||||
drawBackground()
|
||||
|
||||
drawDecorations()
|
||||
|
||||
drawPlayerInfo()
|
||||
|
||||
drawScorePanelContent(deltaTime)
|
||||
|
||||
drawCompletion()
|
||||
|
||||
drawHeader()
|
||||
Footer.draw(deltaTime);
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution()
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
gfx.Rect(0, 0, resX, resY)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
|
||||
gfx.Fill()
|
||||
|
||||
drawResultScreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
|
||||
end
|
||||
|
|
|
@ -1,107 +1,107 @@
|
|||
options = {}
|
||||
yscale = 0.0
|
||||
selectedIndex = 0
|
||||
resx, resy = game.GetResolution()
|
||||
width = 500
|
||||
titleText = ""
|
||||
scale = 1.0
|
||||
|
||||
function make_option(name)
|
||||
return function()
|
||||
menu.Confirm(name)
|
||||
end
|
||||
end
|
||||
|
||||
function open()
|
||||
yscale = 0.0
|
||||
options = {}
|
||||
selectedIndex = 0
|
||||
resx, resy = game.GetResolution()
|
||||
|
||||
index = 1
|
||||
if #dialog.collections == 0 then
|
||||
options[index] = {"Favourites", make_option("Favourites"), {255,255,255}}
|
||||
end
|
||||
|
||||
for i,value in ipairs(dialog.collections) do
|
||||
options[i] = {value.name, make_option(value.name), {255,255,255}}
|
||||
if value.exists then options[i][3] = {255,0,0} end
|
||||
|
||||
end
|
||||
table.insert(options, {"New Collection", menu.ChangeState, {0, 255, 128}})
|
||||
table.insert(options, {"Cancel", menu.Cancel, {200,200,200}})
|
||||
|
||||
gfx.FontFace("fallback")
|
||||
gfx.FontSize(50)
|
||||
titleText = string.format("Add %s to collection:", dialog.title)
|
||||
xmi,ymi,xma,yma = gfx.TextBounds(0,0, titleText)
|
||||
width = xma - xmi + 50
|
||||
width = math.max(500, width)
|
||||
scale = math.min(resy / 1280, 1.0)
|
||||
scale = math.min(scale, resx / width)
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
if dialog.closing then
|
||||
yscale = math.min(yscale - deltaTime * 4, 1.0)
|
||||
else
|
||||
yscale = math.min(yscale + deltaTime * 4, 1.0)
|
||||
end
|
||||
gfx.Translate(resx / 2, resy / 2)
|
||||
gfx.Scale(scale, yscale * scale)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-width/2, -250, width, 500)
|
||||
gfx.FillColor(50,50,50)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontFace("fallback")
|
||||
gfx.FontSize(50)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER)
|
||||
gfx.Text(titleText, 0, -240)
|
||||
|
||||
if dialog.isTextEntry then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-width/2 + 20, -30, width - 40, 60)
|
||||
gfx.FillColor(25,25,25)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(dialog.newName, 0, 0)
|
||||
else
|
||||
local yshift = 20
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
for i, option in ipairs(options) do
|
||||
local y = yshift + 60 * ((i-1) - selectedIndex)
|
||||
if y > -190 and y < 220 then
|
||||
gfx.FillColor(option[3][1], option[3][2], option[3][3])
|
||||
gfx.Text(option[1], 40 - width/2, y)
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(20 - width/2, -10 + yshift)
|
||||
gfx.LineTo(30 - width/2, 0 + yshift)
|
||||
gfx.LineTo(20 - width/2, 10 + yshift)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Fill()
|
||||
end
|
||||
if dialog.closing == true and yscale <= 0.0 then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function button_pressed(button)
|
||||
if button == game.BUTTON_BCK then
|
||||
menu.Cancel()
|
||||
elseif button == game.BUTTON_STA then
|
||||
options[selectedIndex+1][2]()
|
||||
end
|
||||
end
|
||||
|
||||
function advance_selection(value)
|
||||
selectedIndex = (selectedIndex + value) % #options
|
||||
options = {}
|
||||
yscale = 0.0
|
||||
selectedIndex = 0
|
||||
resx, resy = game.GetResolution()
|
||||
width = 500
|
||||
titleText = ""
|
||||
scale = 1.0
|
||||
|
||||
function make_option(name)
|
||||
return function()
|
||||
menu.Confirm(name)
|
||||
end
|
||||
end
|
||||
|
||||
function open()
|
||||
yscale = 0.0
|
||||
options = {}
|
||||
selectedIndex = 0
|
||||
resx, resy = game.GetResolution()
|
||||
|
||||
index = 1
|
||||
if #dialog.collections == 0 then
|
||||
options[index] = {"Favourites", make_option("Favourites"), {255,255,255}}
|
||||
end
|
||||
|
||||
for i,value in ipairs(dialog.collections) do
|
||||
options[i] = {value.name, make_option(value.name), {255,255,255}}
|
||||
if value.exists then options[i][3] = {255,0,0} end
|
||||
|
||||
end
|
||||
table.insert(options, {"New Collection", menu.ChangeState, {0, 255, 128}})
|
||||
table.insert(options, {"Cancel", menu.Cancel, {200,200,200}})
|
||||
|
||||
gfx.FontFace("fallback")
|
||||
gfx.FontSize(50)
|
||||
titleText = string.format("Add %s to collection:", dialog.title)
|
||||
xmi,ymi,xma,yma = gfx.TextBounds(0,0, titleText)
|
||||
width = xma - xmi + 50
|
||||
width = math.max(500, width)
|
||||
scale = math.min(resy / 1280, 1.0)
|
||||
scale = math.min(scale, resx / width)
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
if dialog.closing then
|
||||
yscale = math.min(yscale - deltaTime * 4, 1.0)
|
||||
else
|
||||
yscale = math.min(yscale + deltaTime * 4, 1.0)
|
||||
end
|
||||
gfx.Translate(resx / 2, resy / 2)
|
||||
gfx.Scale(scale, yscale * scale)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-width/2, -250, width, 500)
|
||||
gfx.FillColor(50,50,50)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontFace("fallback")
|
||||
gfx.FontSize(50)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_CENTER)
|
||||
gfx.Text(titleText, 0, -240)
|
||||
|
||||
if dialog.isTextEntry then
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-width/2 + 20, -30, width - 40, 60)
|
||||
gfx.FillColor(25,25,25)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(dialog.newName, 0, 0)
|
||||
else
|
||||
local yshift = 20
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
for i, option in ipairs(options) do
|
||||
local y = yshift + 60 * ((i-1) - selectedIndex)
|
||||
if y > -190 and y < 220 then
|
||||
gfx.FillColor(option[3][1], option[3][2], option[3][3])
|
||||
gfx.Text(option[1], 40 - width/2, y)
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(20 - width/2, -10 + yshift)
|
||||
gfx.LineTo(30 - width/2, 0 + yshift)
|
||||
gfx.LineTo(20 - width/2, 10 + yshift)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Fill()
|
||||
end
|
||||
if dialog.closing == true and yscale <= 0.0 then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function button_pressed(button)
|
||||
if button == game.BUTTON_BCK then
|
||||
menu.Cancel()
|
||||
elseif button == game.BUTTON_STA then
|
||||
options[selectedIndex+1][2]()
|
||||
end
|
||||
end
|
||||
|
||||
function advance_selection(value)
|
||||
selectedIndex = (selectedIndex + value) % #options
|
||||
end
|
|
@ -1,6 +1,156 @@
|
|||
-- NOTE(local): DO NOT PUT ANYTHING HERE PLEASE THANKS
|
||||
-- IF YOU DO THE GAME WILL IMMEDIATELY CRASH AND IT'S NOT THE BEST
|
||||
-- THANK YOU HAVE A GOOD LUA
|
||||
gfx.LoadSkinFont("segoeui.ttf")
|
||||
|
||||
function useFootGun() while true do end end
|
||||
-- Memo class
|
||||
-------------
|
||||
Memo = {}
|
||||
Memo.new = function()
|
||||
local this = {
|
||||
cache = {}
|
||||
}
|
||||
setmetatable(this, {__index = Memo})
|
||||
return this
|
||||
end
|
||||
|
||||
Memo.memoize = function(this, key, generator)
|
||||
local value = this.cache[key]
|
||||
if not value then
|
||||
value = generator()
|
||||
this.cache[key] = value
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
-- Image class
|
||||
--------------
|
||||
Image = {
|
||||
ANCHOR_LEFT = 1,
|
||||
ANCHOR_CENTER = 2,
|
||||
ANCHOR_RIGHT = 4,
|
||||
ANCHOR_TOP = 8,
|
||||
ANCHOR_BOTTOM = 16
|
||||
}
|
||||
Image.skin = function(filename, imageFlags)
|
||||
imageFlags = imageFlags or 0
|
||||
local image = gfx.CreateSkinImage(filename, imageFlags)
|
||||
return Image.wrap(image)
|
||||
end
|
||||
Image.wrap = function(image)
|
||||
local this = {
|
||||
image = image
|
||||
}
|
||||
local w, h = gfx.ImageSize(this.image)
|
||||
this.w = w
|
||||
this.h = h
|
||||
setmetatable(this, {__index = Image})
|
||||
return this
|
||||
end
|
||||
|
||||
Image.draw = function(this, params)
|
||||
local x = params.x
|
||||
local y = params.y
|
||||
local w = params.w or this.w
|
||||
local h = params.h or this.h
|
||||
local alpha = params.alpha or 1
|
||||
local angle = params.angle or 0
|
||||
local anchor_h = params.anchor_h or Image.ANCHOR_CENTER
|
||||
local anchor_v = params.anchor_v or Image.ANCHOR_CENTER
|
||||
local scale = params.scale or 1;
|
||||
|
||||
w = w * scale;
|
||||
h = h * scale;
|
||||
|
||||
if anchor_h == Image.ANCHOR_CENTER then
|
||||
x = x - w / 2
|
||||
elseif anchor_h == Image.ANCHOR_RIGHT then
|
||||
x = x - w
|
||||
end
|
||||
|
||||
if anchor_v == Image.ANCHOR_CENTER then
|
||||
y = y - h / 2
|
||||
elseif anchor_v == Image.ANCHOR_BOTTOM then
|
||||
y = y - h
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x, y, w, h, this.image, alpha, angle)
|
||||
end
|
||||
|
||||
|
||||
-- ImageFont class
|
||||
------------------
|
||||
ImageFont = {}
|
||||
ImageFont.new = function(path, chars)
|
||||
local this = {
|
||||
images = {}
|
||||
}
|
||||
-- load character images
|
||||
for i = 1, chars:len() do
|
||||
local c = chars:sub(i, i)
|
||||
local n = c
|
||||
if c == "." then
|
||||
n = "dot"
|
||||
end
|
||||
local image = Image.skin(string.format("%s/%s.png", path, n), 0)
|
||||
this.images[c] = image
|
||||
end
|
||||
-- use size of first char as font size
|
||||
local w, h = gfx.ImageSize(this.images[chars:sub(1, 1)].image)
|
||||
this.w = w
|
||||
this.h = h
|
||||
|
||||
setmetatable(this, {__index = ImageFont})
|
||||
return this
|
||||
end
|
||||
ImageFont.draw = function(this, text, x, y, alpha, hFlag, vFlag)
|
||||
local totalW = text:len() * this.w
|
||||
|
||||
-- adjust horizontal alignment
|
||||
if hFlag == gfx.TEXT_ALIGN_CENTER then
|
||||
x = x - totalW / 2
|
||||
elseif hFlag == gfx.TEXT_ALIGN_RIGHT then
|
||||
x = x - totalW
|
||||
end
|
||||
|
||||
-- adjust vertical alignment
|
||||
if vFlag == gfx.TEXT_ALIGN_MIDDLE then
|
||||
y = y - this.h / 2
|
||||
elseif vFlag == gfx.TEXT_ALIGN_BOTTOM then
|
||||
y = y - this.h
|
||||
end
|
||||
|
||||
for i = 1, text:len() do
|
||||
local c = text:sub(i, i)
|
||||
local image = this.images[c]
|
||||
if image ~= nil then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x, y, this.w, this.h, image.image, alpha, 0)
|
||||
end
|
||||
x = x + this.w
|
||||
end
|
||||
end
|
||||
|
||||
function GetDisplayDifficulty(jacketPath, difficulty)
|
||||
local strippedPath = string.match(jacketPath:lower(), "[/\\][^\\/]+$")
|
||||
if difficulty == 3 and strippedPath then
|
||||
if string.find(strippedPath, "inf") ~= nil then
|
||||
return 5
|
||||
elseif string.find(strippedPath, "grv") ~= nil then
|
||||
return 6
|
||||
elseif string.find(strippedPath, "hvn") ~= nil then
|
||||
return 7
|
||||
elseif string.find(strippedPath, "vvd") ~= nil then
|
||||
return 8
|
||||
end
|
||||
end
|
||||
|
||||
return difficulty+1
|
||||
end
|
||||
|
||||
function split(s, delimiter)
|
||||
result = {};
|
||||
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
||||
table.insert(result, match);
|
||||
end
|
||||
return result;
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
local Charting = { };
|
||||
|
||||
function Charting.GetDisplayDifficulty(jacketPath, difficulty)
|
||||
if jacketPath == nil then
|
||||
return difficulty
|
||||
end
|
||||
|
||||
local strippedPath = string.match(jacketPath:lower(), "[/\\][^\\/]+$")
|
||||
if difficulty == 3 and strippedPath then
|
||||
if string.find(strippedPath, "inf") ~= nil then
|
||||
return 5
|
||||
elseif string.find(strippedPath, "grv") ~= nil then
|
||||
return 6
|
||||
elseif string.find(strippedPath, "hvn") ~= nil then
|
||||
return 7
|
||||
elseif string.find(strippedPath, "vvd") ~= nil then
|
||||
return 8
|
||||
elseif string.find(strippedPath, "xcd") ~= nil then
|
||||
return 9
|
||||
end
|
||||
end
|
||||
|
||||
return difficulty + 1
|
||||
end
|
||||
|
||||
return Charting;
|
|
@ -1,37 +0,0 @@
|
|||
---Member lookup helper function
|
||||
---@param key string
|
||||
---@param bases any
|
||||
---@return any
|
||||
local function search(key, bases)
|
||||
for _, base in ipairs(bases) do
|
||||
local v = base[key] -- try `i'-th superclass
|
||||
if v then return v end
|
||||
end
|
||||
end
|
||||
|
||||
---Create polimorphic class
|
||||
---@generic BaseT, T
|
||||
---@param cls T # class metatable
|
||||
---@param o? table # initial parameters
|
||||
---@param ... BaseT # base class metatables (if any)
|
||||
---@return T # class instance
|
||||
function CreateInstance(cls, o, ...)
|
||||
o = o or {}
|
||||
local nargs = select("#", ...)
|
||||
local vargs = { select(1, ...) }
|
||||
cls.__index = cls
|
||||
if nargs == 1 then
|
||||
-- single inheritance
|
||||
local base = vargs[1]
|
||||
setmetatable(cls, {__index = base})
|
||||
o = base.new(o)
|
||||
elseif nargs > 1 then
|
||||
-- multiple inheritance (note: slow(er) member lookup)
|
||||
setmetatable(cls, {__index = function(t, k) return search(k, vargs) end})
|
||||
for _, base in ipairs(vargs) do
|
||||
o = base.new(o)
|
||||
end
|
||||
end
|
||||
setmetatable(o, cls)
|
||||
return o
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
local stopMusic = function ()
|
||||
local musicPlaying = game.GetSkinSetting('_musicPlaying');
|
||||
if musicPlaying and musicPlaying ~= '' then
|
||||
game.StopSample(musicPlaying);
|
||||
game.SetSkinSetting("_musicPlaying", "")
|
||||
end
|
||||
end
|
||||
|
||||
local function splitString(inputstr, sep)
|
||||
if sep == nil then
|
||||
sep = "%s"
|
||||
end
|
||||
local t={}
|
||||
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||
table.insert(t, str)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function filter(tableIn, predicate)
|
||||
local out = {}
|
||||
for _, val in ipairs(tableIn) do
|
||||
if predicate(val) then
|
||||
table.insert(out, val)
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
return {
|
||||
stopMusic = stopMusic,
|
||||
splitString = splitString,
|
||||
filter = filter
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
local dimtable = {
|
||||
design = {width = 1080, height = 1920},
|
||||
screen = {width = nil, height = nil},
|
||||
view = {width = nil, height = nil},
|
||||
ratio = {landscapeUW = 21 / 9, landscapeWide = 16 / 9, landscapeStd = 4 / 3, portrait = 9 / 16},
|
||||
}
|
||||
|
||||
dimtable.transformToScreenSpace = function()
|
||||
gfx.Translate((dimtable.screen.width - dimtable.view.width) / 2, 0);
|
||||
gfx.Scale(dimtable.view.width / dimtable.design.width, dimtable.view.height / dimtable.design.height);
|
||||
gfx.Scissor(0, 0, dimtable.design.width, dimtable.design.height);
|
||||
end
|
||||
|
||||
dimtable.updateResolution = function(ratio)
|
||||
if not ratio then ratio = dimtable.ratio.portrait end
|
||||
|
||||
local screenWidth, screenHeight = game.GetResolution()
|
||||
if screenWidth ~= dimtable.screen.width or screenHeight ~= dimtable.screen.height then
|
||||
dimtable.screen.width, dimtable.screen.height = screenWidth, screenHeight
|
||||
dimtable.view.width, dimtable.view.height = ratio * dimtable.screen.height, dimtable.screen.height
|
||||
end
|
||||
end
|
||||
|
||||
---Convert screenspace coordinates to viewspace coordinates
|
||||
---@param screenX number
|
||||
---@param screenY number
|
||||
---@param offsetX? number Viewport offset from the left side (defaults to the portrait viewport offset)
|
||||
---@param offsetY? number Viewport offset from the top side (defaults to 0)
|
||||
---@return number, number
|
||||
dimtable.toViewSpace = function(screenX, screenY, offsetX, offsetY)
|
||||
offsetX = offsetX or (dimtable.screen.width - dimtable.view.width) / 2
|
||||
offsetY = offsetY or 0
|
||||
|
||||
local viewX, viewY, scaleX, scaleY
|
||||
|
||||
scaleX = dimtable.design.width / dimtable.view.width
|
||||
scaleY = dimtable.design.height / dimtable.view.height
|
||||
|
||||
viewX = (screenX - offsetX) * scaleX
|
||||
viewY = (screenY - offsetY) * scaleY
|
||||
|
||||
return viewX, viewY
|
||||
end
|
||||
|
||||
---Set's up scaled transforms based on the current resolution.
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param rotation number
|
||||
---@return number, boolean # The scale applied to the transform and the current landscape state
|
||||
function dimtable.setUpTransforms(x, y, rotation)
|
||||
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);
|
||||
gfx.Scale(scale, scale);
|
||||
|
||||
return scale, isLandscape;
|
||||
end
|
||||
|
||||
return dimtable
|
|
@ -1,105 +0,0 @@
|
|||
require("common.globals")
|
||||
--file reader utility functions
|
||||
|
||||
---Get game path
|
||||
---@return string, string
|
||||
local function getGamePath()
|
||||
return debug.getinfo(1,"S").source:sub(2):match("(.*)([\\/])skins") -- this is very hacky :)
|
||||
end
|
||||
|
||||
local function readBytes(_file)
|
||||
local out = {}
|
||||
repeat
|
||||
local buffer = _file:read(4*1024)
|
||||
for c in (buffer or ''):gmatch(".") do
|
||||
table.insert(out, c:byte())
|
||||
end
|
||||
until not buffer
|
||||
return out
|
||||
end
|
||||
|
||||
---Read a file in the game folder by lines
|
||||
---@param path string relative path to game file
|
||||
---@param mode? openmode default "r"
|
||||
---@return nil|string[]
|
||||
function ReadGameFileLines(path, mode)
|
||||
mode = mode or "r"
|
||||
|
||||
local gamepath, sep = getGamePath()
|
||||
|
||||
local lines = {}
|
||||
|
||||
local f = io.open(gamepath .. sep .. path, mode)
|
||||
if not f then return nil end
|
||||
|
||||
for line in f:lines("l") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
f:close()
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
---Read a file in the game folder
|
||||
---@param path string # relative path to game file
|
||||
---@param mode? openmode # default "r"
|
||||
---@return nil|string|integer[]
|
||||
function ReadGameFile(path, mode)
|
||||
mode = mode or "r"
|
||||
|
||||
local gamepath, sep = getGamePath()
|
||||
local out
|
||||
|
||||
local f = io.open(gamepath .. sep .. path, mode)
|
||||
if not f then return nil end
|
||||
|
||||
if mode:match(".*b") then
|
||||
out = readBytes(f)
|
||||
else
|
||||
out = f:read("a")
|
||||
end
|
||||
f:close()
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
---Find patterns in file
|
||||
---@param path string # relative path to game file
|
||||
---@param pattern string # search pattern
|
||||
---@return table # {{group1, group2, ...}, ...}
|
||||
function FindPatterns(path, pattern)
|
||||
local matches = {}
|
||||
for _, line in ipairs(ReadGameFileLines(path, "r")) do
|
||||
if line:match(pattern) then
|
||||
table.insert(matches, {line:match(pattern)})
|
||||
end
|
||||
end
|
||||
return matches
|
||||
end
|
||||
|
||||
--- Check if a file or directory exists in this path
|
||||
---@param file string # relative path to game file
|
||||
---@return boolean # file exists
|
||||
---@return string # error message
|
||||
function IsFileExists(file)
|
||||
local gamepath, sep = getGamePath()
|
||||
file = gamepath .. sep .. file
|
||||
|
||||
local ok, err, code = os.rename(file, file)
|
||||
if not ok then
|
||||
game.Log("err: "..err..", code: "..code, game.LOGGER_DEBUG)
|
||||
if code == 13 then
|
||||
-- Permission denied, but it exists
|
||||
return true
|
||||
end
|
||||
end
|
||||
return ok, err
|
||||
end
|
||||
|
||||
--- Check if a directory exists in this path
|
||||
---@param path string # relative path to game directory
|
||||
---@return boolean # directory exists
|
||||
function IsDir(path)
|
||||
-- "/" works on both Unix and Windows
|
||||
return IsFileExists(path .. "/")
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
require("common.filereader")
|
||||
|
||||
GameConfig = {}
|
||||
|
||||
function RefreshConfig()
|
||||
for _, match in ipairs(FindPatterns("Main.cfg", "(%w*)%s*=%s*\"?([^\"%s]*)\"?")) do
|
||||
GameConfig[match[1]] = match[2]
|
||||
end
|
||||
end
|
||||
|
||||
RefreshConfig()
|
|
@ -1,2 +0,0 @@
|
|||
---Drewol, what are you doing? Why is there no game.LOGGER_DEBUG?
|
||||
game.LOGGER_DEBUG = 0
|
|
@ -1,11 +0,0 @@
|
|||
local stopMusic = function ()
|
||||
local musicPlaying = game.GetSkinSetting('_musicPlaying');
|
||||
if musicPlaying and musicPlaying ~= '' then
|
||||
game.StopSample(musicPlaying);
|
||||
game.SetSkinSetting("_musicPlaying", "")
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
stopMusic = stopMusic
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
local function split(s, delimiter)
|
||||
local result = {};
|
||||
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
|
||||
table.insert(result, match);
|
||||
end
|
||||
return result;
|
||||
end
|
||||
|
||||
local function filter(tableIn, predicate)
|
||||
local out = {}
|
||||
for _, val in ipairs(tableIn) do
|
||||
if predicate(val) then
|
||||
table.insert(out, val)
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function clamp(x, min, max)
|
||||
if x < min then
|
||||
x = min
|
||||
end
|
||||
if x > max then
|
||||
x = max
|
||||
end
|
||||
|
||||
return x
|
||||
end
|
||||
|
||||
local function round(num)
|
||||
return num + (2^52 + 2^51) - (2^52 + 2^51)
|
||||
end
|
||||
|
||||
local function sign(x)
|
||||
return (
|
||||
(x > 0) and 1
|
||||
or
|
||||
(x < 0) and -1
|
||||
or
|
||||
0
|
||||
)
|
||||
end
|
||||
|
||||
local function roundToZero(x)
|
||||
if x < 0 then
|
||||
return math.ceil(x)
|
||||
elseif x > 0 then
|
||||
return math.floor(x)
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
local function areaOverlap(x, y, areaX, areaY, areaW, areaH)
|
||||
return x > areaX and y > areaY and x < areaX + areaW and y < areaY + areaH
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local function firstAlphaNum(s)
|
||||
for i = 1, string.len(s) do
|
||||
local byte = string.byte(s, i);
|
||||
if ((byte >= 65 and byte <= 90) or (byte >= 97 and byte <= 122) or (byte >= 48 and byte <= 57)) then
|
||||
return string.sub(s, i, i);
|
||||
end
|
||||
end
|
||||
|
||||
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,
|
||||
clamp = clamp,
|
||||
round = round,
|
||||
sign = sign,
|
||||
roundToZero = roundToZero,
|
||||
areaOverlap = areaOverlap,
|
||||
lerp = lerp,
|
||||
mix = mix,
|
||||
modIndex = modIndex,
|
||||
firstAlphaNum = firstAlphaNum,
|
||||
dump = dump,
|
||||
all = all,
|
||||
any = any
|
||||
}
|
|
@ -1,21 +1,6 @@
|
|||
local MAJOR = 0
|
||||
local MINOR = 3
|
||||
local PATCH = 0
|
||||
|
||||
local function getLongVersion()
|
||||
return "USC:E:G:S:" .. MAJOR .. MINOR .. PATCH
|
||||
end
|
||||
|
||||
---Get version string
|
||||
---@return string
|
||||
local function getVersion()
|
||||
return table.concat({MAJOR, MINOR, PATCH}, ".")
|
||||
end
|
||||
|
||||
return {
|
||||
MAJOR = MAJOR,
|
||||
MINOR = MINOR,
|
||||
PATCH = PATCH,
|
||||
getLongVersion = getLongVersion,
|
||||
getVersion = getVersion
|
||||
}
|
||||
MAJOR = 0,
|
||||
MINOR = 2,
|
||||
PATCH = 2
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw, desh = 1080,1920;
|
||||
local scale = 1;
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -16,20 +15,12 @@ local difficultyLabelTexts = {
|
|||
"ADV",
|
||||
"EXH",
|
||||
"MXM",
|
||||
"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)
|
||||
|
@ -38,7 +29,7 @@ function render(deltatime, x, y, scale, diff, level)
|
|||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(0, 0, 140, 31 ,
|
||||
difficultyLabelImages[diff] or
|
||||
difficultyLabelImages[diff+1] or
|
||||
difficultyLabelImages[4], 1, 0);
|
||||
|
||||
|
||||
|
@ -51,7 +42,7 @@ function render(deltatime, x, y, scale, diff, level)
|
|||
gfx.FontSize(22)
|
||||
gfx.Scale(1.2,1); -- Make the diff text more W I D E
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(difficultyLabelTexts[diff], 18, 17);
|
||||
gfx.Text(difficultyLabelTexts[diff+1], 18, 17);
|
||||
|
||||
|
||||
|
||||
|
@ -69,4 +60,4 @@ end
|
|||
|
||||
return {
|
||||
render = render
|
||||
}
|
||||
}
|
|
@ -1,154 +1,91 @@
|
|||
require("common.globals")
|
||||
require("common.gameconfig")
|
||||
|
||||
local version = require("common.version")
|
||||
local Dim = require("common.dimensions")
|
||||
local Num = require("components.numbers")
|
||||
local version = require('common.version')
|
||||
|
||||
local BAR_ALPHA = 191
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw, desh = 1080,1920;
|
||||
local scale = 1;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
|
||||
local FOOTER_HEIGHT = 128
|
||||
local footerY = Dim.design.height - FOOTER_HEIGHT
|
||||
local footerY = desh - 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)
|
||||
local footerRightImage = gfx.CreateSkinImage("components/bars/footer_right.png", 0);
|
||||
|
||||
-- Animation related
|
||||
local entryTransitionScale = 0
|
||||
local entryTransitionFooterYOffset = 0
|
||||
local entryTransitionScale = 0;
|
||||
local entryTransitionFooterYOffset = 0;
|
||||
|
||||
local legend = {{control = "START", text = "Confirm selection"}, {control = "KNOB", text = "Scroll"}}
|
||||
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
|
||||
local set = function ()
|
||||
|
||||
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
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
desw = 1080
|
||||
desh = 1920
|
||||
scale = resx / desw
|
||||
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)
|
||||
local drawFooter = function ()
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0,0,0,BAR_ALPHA);
|
||||
gfx.Rect(0,footerY,desw, FOOTER_HEIGHT);
|
||||
gfx.Fill();
|
||||
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(desw-275, footerY-25, 328*0.85, 188*0.85, footerRightImage, 1, 0);
|
||||
|
||||
-- Draw minutes:seconds display
|
||||
local minutes, seconds = time_s // 60, time_s % 60
|
||||
|
||||
local tens, ones = math.floor(minutes // 10), math.floor(minutes % 10)
|
||||
w, h = 90, 90
|
||||
xpos, ypos = xpos + 55, footerY - 16
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
|
||||
xpos = xpos + 2 * w
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos - 29, ypos, w, h, timeColon, 1, 0)
|
||||
|
||||
xpos = xpos + 32
|
||||
tens, ones = math.floor(seconds // 10), math.floor(seconds % 10)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
else
|
||||
gfx.BeginPath()
|
||||
local xpos, ypos = Dim.design.width - 270, footerY + 55
|
||||
local w, h = gfx.ImageSize(timeImage)
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeImage, 1, 0)
|
||||
|
||||
-- Draw only seconds
|
||||
local tens, ones = math.floor(time_s // 10), math.floor(time_s % 10)
|
||||
w, h = 90, 90
|
||||
xpos, ypos = xpos + 55, footerY - 16
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos, ypos, w, h, timeNumbers[tens + 1], 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(xpos + w, ypos, w, h, timeNumbers[ones + 1], 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function drawFooter()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0, 0, 0, BAR_ALPHA)
|
||||
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
|
||||
|
||||
-- Version String
|
||||
gfx.BeginPath()
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf");
|
||||
gfx.FontSize(20)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.Text("EXPERIMENTALGEAR " .. version.MAJOR .. "." .. version.MINOR .. "." .. version.PATCH .. "", 8, 1895)
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
gfx.Text('EXPERIMENTALGEAR ' .. version.MAJOR .. '.' .. version.MINOR .. '.' .. version.PATCH .. '', 8, 1895);
|
||||
end
|
||||
|
||||
local function progressTransitions(deltaTime)
|
||||
entryTransitionScale = entryTransitionScale + deltaTime / 0.3
|
||||
if (entryTransitionScale > 1) then entryTransitionScale = 1 end
|
||||
local progressTransitions = function (deltaTime)
|
||||
entryTransitionScale = entryTransitionScale + deltaTime / 0.3;
|
||||
if (entryTransitionScale > 1) then
|
||||
entryTransitionScale = 1;
|
||||
end
|
||||
|
||||
entryTransitionFooterYOffset = FOOTER_HEIGHT * (1 - entryTransitionScale)
|
||||
footerY = Dim.design.height - FOOTER_HEIGHT + entryTransitionFooterYOffset
|
||||
|
||||
timeOut = math.max(timeOut - deltaTime, 0)
|
||||
entryTransitionFooterYOffset = FOOTER_HEIGHT*(1-entryTransitionScale)
|
||||
footerY = desh-FOOTER_HEIGHT+entryTransitionFooterYOffset;
|
||||
end
|
||||
|
||||
local function draw(deltaTime, params)
|
||||
if params and params.noEnterTransition then
|
||||
entryTransitionScale = 1
|
||||
local draw = function (deltaTime, params)
|
||||
if (params) then
|
||||
if params.noEnterTransition then
|
||||
entryTransitionScale = 1;
|
||||
end
|
||||
end
|
||||
|
||||
gfx.Save()
|
||||
|
||||
gfx.ResetTransform()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
|
||||
drawFooter()
|
||||
|
||||
progressTransitions(deltaTime)
|
||||
drawFooter();
|
||||
|
||||
progressTransitions(deltaTime);
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return {set = set, draw = draw, resetTimer = resetTimer}
|
||||
return {
|
||||
set = set,
|
||||
draw = draw
|
||||
};
|
|
@ -1,28 +1,34 @@
|
|||
local Dim = require("common.dimensions")
|
||||
local desw = 1080;
|
||||
local desh = 1920;
|
||||
local scale = 1;
|
||||
|
||||
local BAR_ALPHA = 191
|
||||
local BAR_ALPHA = 191;
|
||||
|
||||
local HEADER_HEIGHT = 100
|
||||
local headerY = 0;
|
||||
|
||||
local animationHeaderGlowScale = 0
|
||||
local animationHeaderGlowAlpha = 0
|
||||
local animationHeaderGlowScale = 0;
|
||||
local animationHeaderGlowAlpha = 0;
|
||||
|
||||
-- Images
|
||||
local headerTitleImage = gfx.CreateSkinImage("challenge_select/skill_analyzer.png", gfx.IMAGE_GENERATE_MIPMAPS)
|
||||
|
||||
local function drawHeader()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0, 0, 0, BAR_ALPHA)
|
||||
gfx.Rect(0, 0, Dim.design.width, HEADER_HEIGHT)
|
||||
gfx.Fill()
|
||||
local drawHeader = function ()
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0,0,0,BAR_ALPHA);
|
||||
gfx.Rect(0,0,desw, HEADER_HEIGHT);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath()
|
||||
|
||||
local headerImageWidth, headerImageHeight = gfx.ImageSize(headerTitleImage)
|
||||
gfx.ImageRect((Dim.design.width - headerImageWidth) / 2, (HEADER_HEIGHT - headerImageHeight) / 2 - 12, -- asset png is not centered on the y axis
|
||||
headerImageWidth, headerImageHeight, headerTitleImage, 1, 0)
|
||||
gfx.ImageRect(
|
||||
(desw - headerImageWidth) / 2, (HEADER_HEIGHT - headerImageHeight) / 2 - 12, -- asset png is not centered on the y axis
|
||||
headerImageWidth, headerImageHeight,
|
||||
headerTitleImage, 1, 0
|
||||
)
|
||||
end
|
||||
|
||||
local function progressTransitions(deltatime)
|
||||
local progressTransitions = function (deltatime)
|
||||
-- HEADER GLOW ANIMATION
|
||||
if animationHeaderGlowScale < 1 then
|
||||
animationHeaderGlowScale = animationHeaderGlowScale + deltatime / 1 -- transition should last for that time in seconds
|
||||
|
@ -31,29 +37,24 @@ local function progressTransitions(deltatime)
|
|||
end
|
||||
|
||||
if animationHeaderGlowScale < 0.5 then
|
||||
animationHeaderGlowAlpha = animationHeaderGlowScale * 2
|
||||
animationHeaderGlowAlpha = animationHeaderGlowScale * 2;
|
||||
else
|
||||
animationHeaderGlowAlpha = 1 - ((animationHeaderGlowScale - 0.5) * 2)
|
||||
animationHeaderGlowAlpha = 1-((animationHeaderGlowScale-0.5) * 2);
|
||||
end
|
||||
animationHeaderGlowAlpha = animationHeaderGlowAlpha * 0.4
|
||||
animationHeaderGlowAlpha = animationHeaderGlowAlpha*0.4
|
||||
end
|
||||
|
||||
local function draw(deltatime)
|
||||
local draw = function (deltatime)
|
||||
gfx.Save()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
|
||||
gfx.ResetTransform()
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
|
||||
drawHeader()
|
||||
|
||||
progressTransitions(deltatime)
|
||||
drawHeader();
|
||||
|
||||
--progressTransitions(deltatime);
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return {draw = draw}
|
||||
return {
|
||||
draw = draw
|
||||
};
|
|
@ -1,28 +1,35 @@
|
|||
local Dim = require("common.dimensions")
|
||||
|
||||
local BAR_ALPHA = 191
|
||||
|
||||
local resx, resy = game.GetResolution();
|
||||
local desw = 1080;
|
||||
local desh = 1920;
|
||||
local scale = 1;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
|
||||
local HEADER_HEIGHT = 100
|
||||
local headerY = 0;
|
||||
|
||||
local animationHeaderGlowScale = 0
|
||||
local animationHeaderGlowAlpha = 0
|
||||
local animationHeaderGlowScale = 0;
|
||||
local animationHeaderGlowAlpha = 0;
|
||||
|
||||
-- Images
|
||||
local headerTitleImage = gfx.CreateSkinImage("song_select/header/title.png", 1)
|
||||
local headerGlowTitleImage = gfx.CreateSkinImage("song_select/header/title_glow.png", 1)
|
||||
|
||||
local drawHeader = function()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0, 0, 0, BAR_ALPHA)
|
||||
gfx.Rect(0, 0, Dim.design.width, HEADER_HEIGHT)
|
||||
gfx.Fill()
|
||||
|
||||
local drawHeader = function ()
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0,0,0,BAR_ALPHA);
|
||||
gfx.Rect(0,0,desw, HEADER_HEIGHT);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath()
|
||||
|
||||
gfx.ImageRect(42, 14, 423 * 0.85, 80 * 0.85, headerTitleImage, 1, 0)
|
||||
gfx.ImageRect(42, 14, 423 * 0.85, 80 * 0.85, headerGlowTitleImage, animationHeaderGlowAlpha, 0)
|
||||
gfx.ImageRect(42, 14, 423*0.85, 80*0.85, headerTitleImage, 1, 0)
|
||||
gfx.ImageRect(42, 14, 423*0.85, 80*0.85, headerGlowTitleImage, animationHeaderGlowAlpha, 0)
|
||||
end
|
||||
|
||||
local progressTransitions = function(deltatime)
|
||||
local progressTransitions = function (deltatime)
|
||||
-- HEADER GLOW ANIMATION
|
||||
if animationHeaderGlowScale < 1 then
|
||||
animationHeaderGlowScale = animationHeaderGlowScale + deltatime / 1 -- transition should last for that time in seconds
|
||||
|
@ -31,29 +38,24 @@ local progressTransitions = function(deltatime)
|
|||
end
|
||||
|
||||
if animationHeaderGlowScale < 0.5 then
|
||||
animationHeaderGlowAlpha = animationHeaderGlowScale * 2
|
||||
animationHeaderGlowAlpha = animationHeaderGlowScale * 2;
|
||||
else
|
||||
animationHeaderGlowAlpha = 1 - ((animationHeaderGlowScale - 0.5) * 2)
|
||||
animationHeaderGlowAlpha = 1-((animationHeaderGlowScale-0.5) * 2);
|
||||
end
|
||||
animationHeaderGlowAlpha = animationHeaderGlowAlpha * 0.4
|
||||
animationHeaderGlowAlpha = animationHeaderGlowAlpha*0.4
|
||||
end
|
||||
|
||||
local draw = function(deltatime)
|
||||
local draw = function (deltatime)
|
||||
gfx.Save()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
|
||||
gfx.ResetTransform()
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
|
||||
drawHeader()
|
||||
|
||||
progressTransitions(deltatime)
|
||||
drawHeader();
|
||||
|
||||
progressTransitions(deltatime);
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return {draw = draw}
|
||||
return {
|
||||
draw = draw
|
||||
};
|
|
@ -1,68 +0,0 @@
|
|||
require("common.class")
|
||||
local Field = require("components.pager.field")
|
||||
|
||||
---@class ContainerField: Field
|
||||
---@field content Field[]
|
||||
local ContainerField = {
|
||||
__tostring = function() return "ContainerField" end,
|
||||
}
|
||||
|
||||
---Create a new ContainerField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return ContainerField
|
||||
function ContainerField.new(o)
|
||||
o = o or {}
|
||||
|
||||
--set instance members
|
||||
|
||||
o.content = o.content or {}
|
||||
|
||||
local this = CreateInstance(ContainerField, o, Field)
|
||||
|
||||
this:refreshFields()
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
---Add content to container
|
||||
---@param field Field
|
||||
function ContainerField:addField(field)
|
||||
field.parent = self
|
||||
table.insert(self.content, field)
|
||||
end
|
||||
|
||||
---Refresh content parameters
|
||||
function ContainerField:refreshFields()
|
||||
for _, child in ipairs(self.content) do
|
||||
child.parent = self
|
||||
end
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function ContainerField:drawBackground(deltaTime) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function ContainerField:drawContent(deltaTime)
|
||||
for _, child in ipairs(self.content) do
|
||||
child:render(deltaTime)
|
||||
end
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function ContainerField:drawForeground(deltaTime) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function ContainerField:render(deltaTime)
|
||||
gfx.Save()
|
||||
|
||||
gfx.Translate(self.posX, self.posY)
|
||||
gfx.Scissor(0, 0, self.aabbW, self.aabbH)
|
||||
|
||||
self:drawBackground(deltaTime)
|
||||
self:drawContent(deltaTime)
|
||||
self:drawForeground(deltaTime)
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return ContainerField
|
|
@ -1,113 +0,0 @@
|
|||
require("common.class")
|
||||
|
||||
---@class Field
|
||||
---@field parent Page|ContainerField
|
||||
---@field posX number
|
||||
---@field posY number
|
||||
---@field aabbW number
|
||||
---@field aabbH number
|
||||
local Field = {
|
||||
__tostring = function() return "Field" end,
|
||||
}
|
||||
|
||||
---Create a new Field instance
|
||||
---@param o? table # initial parameters
|
||||
---@return Field
|
||||
function Field.new(o)
|
||||
o = o or {}
|
||||
|
||||
--set instance members
|
||||
|
||||
o.parent = o.parent or nil
|
||||
o.posX = o.posX or 0
|
||||
o.posY = o.posY or 0
|
||||
o.aabbW = o.aabbW or 0
|
||||
o.aabbH = o.aabbH or 0
|
||||
|
||||
return CreateInstance(Field, o)
|
||||
end
|
||||
|
||||
---Get the containing top-level parent page
|
||||
---@return Field|Page
|
||||
function Field:getParentPage()
|
||||
if self.parent and self.parent.getParentPage then
|
||||
return self.parent:getParentPage()
|
||||
else
|
||||
return self.parent
|
||||
end
|
||||
end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function Field:activate(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function Field:focus(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function Field:deactivate(obj) end
|
||||
|
||||
---@param button integer # options are under the `game` table prefixed with `BUTTON`
|
||||
---@return boolean # true if further button input processing should be stopped, otherwise false
|
||||
function Field:handleButtonInput(button)
|
||||
return false
|
||||
end
|
||||
|
||||
---@param knob integer # `0` = Left, `1` = Right
|
||||
---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW)
|
||||
---@return boolean # true if further button input processing should be stopped, otherwise false
|
||||
function Field:handleKnobInput(knob, delta)
|
||||
return false
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Field:drawContent(deltaTime)
|
||||
-- dummy field content
|
||||
|
||||
gfx.ResetScissor()
|
||||
|
||||
local offX = -50
|
||||
local offY = -50
|
||||
local aabbW = 100
|
||||
local aabbH = 100
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255, 0, 128, 192)
|
||||
gfx.StrokeColor(0, 0, 0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Rect(offX, offY, aabbW, aabbH)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(offX, 0)
|
||||
gfx.LineTo(offX + aabbW, 0)
|
||||
gfx.MoveTo(0, offY)
|
||||
gfx.LineTo(0, offY + aabbH)
|
||||
gfx.StrokeColor(0, 0, 0, 64)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
|
||||
local fontSize = 18
|
||||
local fontMargin = 4
|
||||
gfx.BeginPath()
|
||||
gfx.FontSize(fontSize)
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FillColor(0, 0, 0)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER | gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text("TEXTURE", 0, -fontSize / 2 - fontMargin)
|
||||
gfx.Text("MISSING", 0, fontSize / 2 + fontMargin)
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Field:render(deltaTime)
|
||||
gfx.Save()
|
||||
|
||||
gfx.Translate(self.posX, self.posY)
|
||||
gfx.Scissor(0, 0, self.aabbW, self.aabbH)
|
||||
|
||||
self:drawContent(deltaTime)
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return Field
|
|
@ -1,44 +0,0 @@
|
|||
require("common.class")
|
||||
local Field = require("components.pager.field")
|
||||
|
||||
---@class LinkField: Field
|
||||
---@field link Page
|
||||
local LinkField = {
|
||||
__tostring = function() return "LinkField" end
|
||||
}
|
||||
|
||||
---Create a new LinkField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return LinkField
|
||||
function LinkField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.link = o.link or nil
|
||||
|
||||
return CreateInstance(LinkField, o, Field)
|
||||
end
|
||||
|
||||
---@param button integer # options are under the `game` table prefixed with `BUTTON`
|
||||
---@return boolean # true if further button input processing should be stopped, otherwise false
|
||||
function LinkField:handleButtonInput(button)
|
||||
if not self.link then
|
||||
game.Log(tostring(self) .. " does not have a valid link", game.LOGGER_ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
if button == game.BUTTON_STA then
|
||||
local parentPage = self:getParentPage()
|
||||
if parentPage and parentPage.viewHandler then
|
||||
game.Log(tostring(self) .. " viewHandler:navigate(" .. tostring(self.link) .. ") called", game.LOGGER_INFO)
|
||||
parentPage.viewHandler:navigate(self.link)
|
||||
return true
|
||||
else
|
||||
local target = (parentPage and parentPage.viewHandler or "PageView")
|
||||
game.Log(tostring(self) .. " can't access " .. tostring(target) .. " instance", game.LOGGER_ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return LinkField
|
|
@ -1,72 +0,0 @@
|
|||
require("common.globals")
|
||||
require("common.class")
|
||||
|
||||
---@class Page
|
||||
---@field content Field[]
|
||||
---@field viewHandler nil|PageView
|
||||
local Page = {
|
||||
__tostring = function() return "Page" end,
|
||||
}
|
||||
|
||||
---Create a new Page instance
|
||||
---@param o? table # initial parameters
|
||||
---@return Page
|
||||
function Page.new(o)
|
||||
o = o or {}
|
||||
|
||||
--set instance members
|
||||
|
||||
o.content = o.content or {}
|
||||
o.viewHandler = o.viewHandler or nil
|
||||
|
||||
return CreateInstance(Page, o)
|
||||
end
|
||||
|
||||
---Add field to page
|
||||
---@param field Field
|
||||
function Page:addField(field)
|
||||
field.parent = self
|
||||
table.insert(self.content, field)
|
||||
end
|
||||
|
||||
---Refresh content values
|
||||
function Page:refreshFields()
|
||||
for _, field in ipairs(self.content) do
|
||||
field.parent = self
|
||||
end
|
||||
end
|
||||
|
||||
---@param button integer # options are under the `game` table prefixed with `BUTTON`
|
||||
function Page:handleButtonInput(button)
|
||||
if button == game.BUTTON_BCK then
|
||||
if self.viewHandler then
|
||||
self.viewHandler:back()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param knob integer # `0` = Left, `1` = Right
|
||||
---@param delta number # in radians, `-2*pi` to `0` (turning CCW) and `0` to `2*pi` (turning CW)
|
||||
function Page:handleKnobInput(knob, delta) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Page:drawBackground(deltaTime) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Page:drawContent(deltaTime)
|
||||
for _, child in ipairs(self.content) do
|
||||
child:render(deltaTime)
|
||||
end
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Page:drawForeground(deltaTime) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function Page:render(deltaTime)
|
||||
self:drawBackground(deltaTime)
|
||||
self:drawContent(deltaTime)
|
||||
self:drawForeground(deltaTime)
|
||||
end
|
||||
|
||||
return Page
|
|
@ -1,84 +0,0 @@
|
|||
require("common.class")
|
||||
|
||||
---@class PageView
|
||||
---@field pageStack Page[]
|
||||
local PageView = {
|
||||
__tostring = function() return "PageView" end
|
||||
}
|
||||
|
||||
local function pushStack(t, o)
|
||||
table.insert(t, 1, o)
|
||||
end
|
||||
|
||||
local function popStack(t)
|
||||
return table.remove(t, 1)
|
||||
end
|
||||
|
||||
---Create a new PageView instance
|
||||
---@param rootPage Page
|
||||
---@return PageView
|
||||
function PageView.new(rootPage)
|
||||
local o = {}
|
||||
|
||||
--set viewHandler as this instance for rootPage
|
||||
|
||||
rootPage.viewHandler = o
|
||||
|
||||
--set instance members
|
||||
|
||||
o.pageStack = {}
|
||||
pushStack(o.pageStack, rootPage)
|
||||
|
||||
return CreateInstance(PageView, o)
|
||||
end
|
||||
|
||||
---Get page from pageStack
|
||||
---@param index? integer # defaults to 1 (top of the stack)
|
||||
---@return Page
|
||||
function PageView:get(index)
|
||||
index = index or 1
|
||||
|
||||
return self.pageStack[index]
|
||||
end
|
||||
|
||||
---Navigate to page
|
||||
---@param page Page # page to put on top of the pageStack
|
||||
function PageView:navigate(page)
|
||||
page.viewHandler = self
|
||||
pushStack(self.pageStack, page)
|
||||
end
|
||||
|
||||
---Replace the current pageStack with a new root page
|
||||
---@param rootPage Page
|
||||
function PageView:replace(rootPage)
|
||||
self:clear()
|
||||
self:navigate(rootPage)
|
||||
end
|
||||
|
||||
---Navigate to the previous page
|
||||
function PageView:back()
|
||||
if not self:get() then
|
||||
game.Log(self .. ":back() : pageStack empty, cannot go back", game.LOGGER_WARNING)
|
||||
return
|
||||
end
|
||||
|
||||
self:get().viewHandler = nil
|
||||
popStack(self.pageStack)
|
||||
end
|
||||
|
||||
---Clear the pageStack
|
||||
function PageView:clear()
|
||||
--clear pageStack
|
||||
while self:get() do
|
||||
self:back()
|
||||
end
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function PageView:render(deltaTime)
|
||||
if self:get() then
|
||||
self:get():render(deltaTime)
|
||||
end
|
||||
end
|
||||
|
||||
return PageView
|
|
@ -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
|
|
@ -1,23 +0,0 @@
|
|||
local Dim = require("common.dimensions")
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
|
||||
local patternAngle = 0
|
||||
local patternAlpha = 0.2
|
||||
|
||||
function render()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0, 0, Dim.screen.width, Dim.screen.height)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, patternAngle, backgroundImage, patternAlpha))
|
||||
gfx.Fill()
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
return {
|
||||
render = render
|
||||
}
|
|
@ -1,429 +1,429 @@
|
|||
json = require "common.json"
|
||||
local header = {}
|
||||
header["user-agent"] = "unnamed_sdvx_clone"
|
||||
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local diffColors = {{50,50,127}, {50,127,50}, {127,50,50}, {127, 50, 127}}
|
||||
local entryW = 770
|
||||
local entryH = 320
|
||||
local resX,resY = game.GetResolution()
|
||||
local xCount = math.max(1, math.floor(resX / entryW))
|
||||
local yCount = math.max(1, math.floor(resY / entryH))
|
||||
local xOffset = (resX - xCount * entryW) / 2
|
||||
local cursorPos = 0
|
||||
local cursorPosX = 0
|
||||
local cursorPosY = 0
|
||||
local displayCursorPosX = 0
|
||||
local displayCursorPosY = 0
|
||||
local nextUrl = "https://ksm.dev/app/songs"
|
||||
local screenState = 0 --0 = normal, 1 = level, 2 = sorting
|
||||
local loading = true
|
||||
local downloaded = {}
|
||||
local songs = {}
|
||||
local selectedLevels = {}
|
||||
local selectedSorting = "Uploaded"
|
||||
local lastPlaying = nil
|
||||
for i = 1, 20 do
|
||||
selectedLevels[i] = false
|
||||
end
|
||||
|
||||
local cachepath = path.Absolute("skins/" .. game.GetSkin() .. "/nautica.json")
|
||||
local levelcursor = 0
|
||||
local sortingcursor = 0
|
||||
local sortingOptions = {"Uploaded", "Oldest"}
|
||||
local needsReload = false
|
||||
|
||||
function addsong(song)
|
||||
if song.jacket_url ~= nil then
|
||||
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
|
||||
else
|
||||
song.jacket = jacketFallback
|
||||
end
|
||||
if downloaded[song.id] then
|
||||
song.status = "Downloaded"
|
||||
end
|
||||
table.insert(songs, song)
|
||||
end
|
||||
local yOffset = 0
|
||||
local backgroundImage = gfx.CreateSkinImage("bg.png", 1);
|
||||
|
||||
dlcache = io.open(cachepath, "r")
|
||||
if dlcache then
|
||||
downloaded = json.decode(dlcache:read("*all"))
|
||||
dlcache:close()
|
||||
end
|
||||
function encodeURI(str)
|
||||
if (str) then
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])",
|
||||
function (c)
|
||||
local dontChange = "-/_:."
|
||||
for i = 1, #dontChange do
|
||||
if c == dontChange:sub(i,i) then return c end
|
||||
end
|
||||
return string.format ("%%%02X", string.byte(c))
|
||||
end)
|
||||
str = string.gsub(str, " ", "%%20")
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
|
||||
function gotSongsCallback(response)
|
||||
if response.status ~= 200 then
|
||||
error()
|
||||
return
|
||||
end
|
||||
local jsondata = json.decode(response.text)
|
||||
for i,song in ipairs(jsondata.data) do
|
||||
addsong(song)
|
||||
end
|
||||
nextUrl = jsondata.links.next
|
||||
loading = false
|
||||
end
|
||||
|
||||
Http.GetAsync(nextUrl, header, gotSongsCallback)
|
||||
|
||||
|
||||
function render_song(song, x,y)
|
||||
gfx.Save()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Translate(x,y)
|
||||
gfx.Scissor(0,0,750,300)
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,140)
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(30)
|
||||
gfx.Text(song.title, 2,2)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text(song.artist, 2,26)
|
||||
if song.jacket_url ~= nil and song.jacket == jacketFallback then
|
||||
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 50, 250, 250, song.jacket, 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(250,50,500,250)
|
||||
gfx.FillColor(55,55,55,128)
|
||||
gfx.Fill()
|
||||
for i, diff in ipairs(song.charts) do
|
||||
local col = diffColors[diff.difficulty]
|
||||
local diffY = 50 + 250/4 * (diff.difficulty - 1)
|
||||
gfx.BeginPath()
|
||||
|
||||
gfx.Rect(250,diffY, 500, 250 / 4)
|
||||
gfx.FillColor(col[1], col[2], col[3])
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(string.format("%d Effected by %s", diff.level, diff.effector), 255, diffY + 250 / 8)
|
||||
end
|
||||
if downloaded[song.id] then
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.FillColor(0,0,0,127)
|
||||
gfx.Fill()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(60)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(downloaded[song.id], 375, 150)
|
||||
elseif song.status then
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.FillColor(0,0,0,127)
|
||||
gfx.Fill()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(60)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(song.status, 375, 150)
|
||||
end
|
||||
gfx.ResetScissor()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function load_more()
|
||||
if nextUrl ~= nil and not loading then
|
||||
Http.GetAsync(nextUrl, header, gotSongsCallback)
|
||||
loading = true
|
||||
end
|
||||
end
|
||||
|
||||
function render_cursor()
|
||||
local x = displayCursorPosX * entryW
|
||||
local y = displayCursorPosY * entryH
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x,y,750,300)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(5)
|
||||
gfx.Stroke()
|
||||
end
|
||||
|
||||
function render_loading()
|
||||
if not loading then return end
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(resX, resY)
|
||||
gfx.LineTo(resX - 350, resY)
|
||||
gfx.LineTo(resX - 300, resY - 50)
|
||||
gfx.LineTo(resX, resY - 50)
|
||||
gfx.ClosePath()
|
||||
gfx.FillColor(33,33,33)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.FontSize(70)
|
||||
gfx.Text("LOADING...", resX - 20, resY - 3)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_hotkeys()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,240)
|
||||
gfx.Rect(0,resY - 50, resX, 50)
|
||||
gfx.Fill()
|
||||
gfx.FontSize(30)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.Text("FXR: Sorting", resX/2 + 20, resY - 10)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.Text("FXL: Levels", resX/2 - 20, resY - 10)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_info()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, resY)
|
||||
gfx.LineTo(350, resY)
|
||||
gfx.LineTo(300, resY - 50)
|
||||
gfx.LineTo(0, resY - 50)
|
||||
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("Nautica", 3, resY - 3)
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Nautica")
|
||||
gfx.FontSize(20)
|
||||
gfx.Text("https://ksm.dev/", xmax + 13, resY - 3)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, resX, resY, backgroundImage, 1, 0);
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
displayCursorPosX = displayCursorPosX - (displayCursorPosX - cursorPosX) * deltaTime * 10
|
||||
displayCursorPosY = displayCursorPosY - (displayCursorPosY - cursorPosY) * deltaTime * 10
|
||||
if displayCursorPosY - yOffset > yCount - 1 then --scrolling down
|
||||
yOffset = yOffset - (yOffset - displayCursorPosY) - yCount + 1
|
||||
elseif displayCursorPosY - yOffset < 0 then
|
||||
yOffset = yOffset - (yOffset - displayCursorPosY)
|
||||
end
|
||||
gfx.Translate(xOffset, 50 - yOffset * entryH)
|
||||
for i, song in ipairs(songs) do
|
||||
if math.abs(cursorPos - i) <= xCount * yCount + xCount then
|
||||
i = i - 1
|
||||
local x = entryW * (i % xCount)
|
||||
local y = math.floor(i / xCount) * entryH
|
||||
render_song(song, x, y)
|
||||
if math.abs(#songs - i) < 4 then load_more() end
|
||||
end
|
||||
end
|
||||
render_cursor()
|
||||
if needsReload then reload_songs() end
|
||||
if screenState == 1 then render_level_filters()
|
||||
elseif screenState == 2 then render_sorting_selection()
|
||||
end
|
||||
render_hotkeys()
|
||||
render_loading()
|
||||
render_info()
|
||||
end
|
||||
|
||||
function archive_callback(entries, id)
|
||||
game.Log("Listing entries for " .. id, 0)
|
||||
local songsfolder = dlScreen.GetSongsPath()
|
||||
res = {}
|
||||
folders = { songsfolder .. "/nautica/" }
|
||||
local hasFolder = false
|
||||
for i, entry in ipairs(entries) do
|
||||
for j = 1, #entry do
|
||||
if entry:sub(j,j) == '/' then
|
||||
hasFolder = true
|
||||
table.insert(folders, songsfolder .. "/nautica/" .. entry:sub(1,j))
|
||||
end
|
||||
end
|
||||
game.Log(entry, 0)
|
||||
res[entry] = songsfolder .. "/nautica/" .. entry
|
||||
end
|
||||
|
||||
if not hasFolder then
|
||||
for i, entry in ipairs(entries) do
|
||||
res[entry] = songsfolder .. "/nautica/" .. id .. "/" .. entry
|
||||
end
|
||||
table.insert(folders, songsfolder .. "/nautica/" .. id .. "/")
|
||||
end
|
||||
downloaded[id] = "Downloaded"
|
||||
res[".folders"] = table.concat(folders, "|")
|
||||
return res
|
||||
end
|
||||
|
||||
function reload_songs()
|
||||
needsReload = true
|
||||
if loading then return end
|
||||
local useLevels = false
|
||||
local levelarr = {}
|
||||
|
||||
for i,value in ipairs(selectedLevels) do
|
||||
if value then
|
||||
useLevels = true
|
||||
table.insert(levelarr, i)
|
||||
end
|
||||
end
|
||||
nextUrl = string.format("https://ksm.dev/app/songs?sort=%s", selectedSorting:lower())
|
||||
if useLevels then
|
||||
nextUrl = nextUrl .. "&levels=" .. table.concat(levelarr, ",")
|
||||
end
|
||||
songs = {}
|
||||
cursorPos = 0
|
||||
cursorPosX = 0
|
||||
cursorPosY = 0
|
||||
displayCursorPosX = 0
|
||||
displayCursorPosY = 0
|
||||
load_more()
|
||||
game.Log(nextUrl, 0)
|
||||
needsReload = false
|
||||
|
||||
end
|
||||
|
||||
function button_pressed(button)
|
||||
if button == game.BUTTON_STA then
|
||||
if screenState == 0 then
|
||||
local song = songs[cursorPos + 1]
|
||||
if song == nil then return end
|
||||
dlScreen.DownloadArchive(encodeURI(song.cdn_download_url), header, song.id, archive_callback)
|
||||
downloaded[song.id] = "Downloading..."
|
||||
elseif screenState == 1 then
|
||||
if selectedLevels[levelcursor + 1] then
|
||||
selectedLevels[levelcursor + 1] = false
|
||||
else
|
||||
selectedLevels[levelcursor + 1] = true
|
||||
end
|
||||
reload_songs()
|
||||
elseif screenState == 2 then
|
||||
selectedSorting = sortingOptions[sortingcursor + 1]
|
||||
reload_songs()
|
||||
end
|
||||
|
||||
elseif button == game.BUTTON_BTA then
|
||||
if screenState == 0 then
|
||||
local song = songs[cursorPos + 1]
|
||||
if song == nil then return end
|
||||
dlScreen.PlayPreview(encodeURI(song.preview_url), header, song.id)
|
||||
song.status = "Playing"
|
||||
if lastPlaying ~=nil then
|
||||
lastPlaying.status = nil
|
||||
end
|
||||
lastPlaying = song
|
||||
end
|
||||
|
||||
elseif button == game.BUTTON_FXL then
|
||||
if screenState ~= 1 then
|
||||
screenState = 1
|
||||
else
|
||||
screenState = 0
|
||||
end
|
||||
elseif button == game.BUTTON_FXR then
|
||||
if screenState ~= 2 then
|
||||
screenState = 2
|
||||
else
|
||||
screenState = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function key_pressed(key)
|
||||
if key == 27 then --escape pressed
|
||||
dlcache = io.open(cachepath, "w")
|
||||
dlcache:write(json.encode(downloaded))
|
||||
dlcache:close()
|
||||
dlScreen.Exit()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function advance_selection(steps)
|
||||
if screenState == 0 and #songs > 0 then
|
||||
cursorPos = (cursorPos + steps) % #songs
|
||||
cursorPosX = cursorPos % xCount
|
||||
cursorPosY = math.floor(cursorPos / xCount)
|
||||
if cursorPos > #songs - 6 then
|
||||
load_more()
|
||||
end
|
||||
elseif screenState == 1 then
|
||||
levelcursor = (levelcursor + steps) % 20
|
||||
elseif screenState == 2 then
|
||||
sortingcursor = (sortingcursor + steps) % #sortingOptions
|
||||
end
|
||||
end
|
||||
|
||||
function render_level_filters()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0, resX, resY)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(60)
|
||||
gfx.Text("Level filters:", 10, 10)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(resX/2 - 30, resY/2 - 22, 60, 44)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
for i = 1, 20 do
|
||||
y = (resY/2) + (i - (levelcursor + 1)) * 40
|
||||
if selectedLevels[i] then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
|
||||
gfx.Text(tostring(i), resX/2, y)
|
||||
end
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_sorting_selection()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0, resX, resY)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(60)
|
||||
gfx.Text("Sorting method:", 10, 10)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(resX/2 - 75, resY/2 - 22, 150, 44)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
for i, opt in ipairs(sortingOptions) do
|
||||
y = (resY/2) + (i - (sortingcursor + 1)) * 40
|
||||
if selectedSorting == opt then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
|
||||
gfx.Text(opt, resX/2, y)
|
||||
end
|
||||
gfx.Restore()
|
||||
json = require "json"
|
||||
local header = {}
|
||||
header["user-agent"] = "unnamed_sdvx_clone"
|
||||
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local diffColors = {{50,50,127}, {50,127,50}, {127,50,50}, {127, 50, 127}}
|
||||
local entryW = 770
|
||||
local entryH = 320
|
||||
local resX,resY = game.GetResolution()
|
||||
local xCount = math.max(1, math.floor(resX / entryW))
|
||||
local yCount = math.max(1, math.floor(resY / entryH))
|
||||
local xOffset = (resX - xCount * entryW) / 2
|
||||
local cursorPos = 0
|
||||
local cursorPosX = 0
|
||||
local cursorPosY = 0
|
||||
local displayCursorPosX = 0
|
||||
local displayCursorPosY = 0
|
||||
local nextUrl = "https://ksm.dev/app/songs"
|
||||
local screenState = 0 --0 = normal, 1 = level, 2 = sorting
|
||||
local loading = true
|
||||
local downloaded = {}
|
||||
local songs = {}
|
||||
local selectedLevels = {}
|
||||
local selectedSorting = "Uploaded"
|
||||
local lastPlaying = nil
|
||||
for i = 1, 20 do
|
||||
selectedLevels[i] = false
|
||||
end
|
||||
|
||||
local cachepath = path.Absolute("skins/" .. game.GetSkin() .. "/nautica.json")
|
||||
local levelcursor = 0
|
||||
local sortingcursor = 0
|
||||
local sortingOptions = {"Uploaded", "Oldest"}
|
||||
local needsReload = false
|
||||
|
||||
function addsong(song)
|
||||
if song.jacket_url ~= nil then
|
||||
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
|
||||
else
|
||||
song.jacket = jacketFallback
|
||||
end
|
||||
if downloaded[song.id] then
|
||||
song.status = "Downloaded"
|
||||
end
|
||||
table.insert(songs, song)
|
||||
end
|
||||
local yOffset = 0
|
||||
local backgroundImage = gfx.CreateSkinImage("bg.png", 1);
|
||||
|
||||
dlcache = io.open(cachepath, "r")
|
||||
if dlcache then
|
||||
downloaded = json.decode(dlcache:read("*all"))
|
||||
dlcache:close()
|
||||
end
|
||||
function encodeURI(str)
|
||||
if (str) then
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])",
|
||||
function (c)
|
||||
local dontChange = "-/_:."
|
||||
for i = 1, #dontChange do
|
||||
if c == dontChange:sub(i,i) then return c end
|
||||
end
|
||||
return string.format ("%%%02X", string.byte(c))
|
||||
end)
|
||||
str = string.gsub(str, " ", "%%20")
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
|
||||
function gotSongsCallback(response)
|
||||
if response.status ~= 200 then
|
||||
error()
|
||||
return
|
||||
end
|
||||
local jsondata = json.decode(response.text)
|
||||
for i,song in ipairs(jsondata.data) do
|
||||
addsong(song)
|
||||
end
|
||||
nextUrl = jsondata.links.next
|
||||
loading = false
|
||||
end
|
||||
|
||||
Http.GetAsync(nextUrl, header, gotSongsCallback)
|
||||
|
||||
|
||||
function render_song(song, x,y)
|
||||
gfx.Save()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Translate(x,y)
|
||||
gfx.Scissor(0,0,750,300)
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,140)
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(30)
|
||||
gfx.Text(song.title, 2,2)
|
||||
gfx.FontSize(24)
|
||||
gfx.Text(song.artist, 2,26)
|
||||
if song.jacket_url ~= nil and song.jacket == jacketFallback then
|
||||
song.jacket = gfx.LoadWebImageJob(song.jacket_url, jacketFallback, 250, 250)
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 50, 250, 250, song.jacket, 1, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(250,50,500,250)
|
||||
gfx.FillColor(55,55,55,128)
|
||||
gfx.Fill()
|
||||
for i, diff in ipairs(song.charts) do
|
||||
local col = diffColors[diff.difficulty]
|
||||
local diffY = 50 + 250/4 * (diff.difficulty - 1)
|
||||
gfx.BeginPath()
|
||||
|
||||
gfx.Rect(250,diffY, 500, 250 / 4)
|
||||
gfx.FillColor(col[1], col[2], col[3])
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(string.format("%d Effected by %s", diff.level, diff.effector), 255, diffY + 250 / 8)
|
||||
end
|
||||
if downloaded[song.id] then
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.FillColor(0,0,0,127)
|
||||
gfx.Fill()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(60)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(downloaded[song.id], 375, 150)
|
||||
elseif song.status then
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0,750,300)
|
||||
gfx.FillColor(0,0,0,127)
|
||||
gfx.Fill()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(60)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.Text(song.status, 375, 150)
|
||||
end
|
||||
gfx.ResetScissor()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function load_more()
|
||||
if nextUrl ~= nil and not loading then
|
||||
Http.GetAsync(nextUrl, header, gotSongsCallback)
|
||||
loading = true
|
||||
end
|
||||
end
|
||||
|
||||
function render_cursor()
|
||||
local x = displayCursorPosX * entryW
|
||||
local y = displayCursorPosY * entryH
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x,y,750,300)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(5)
|
||||
gfx.Stroke()
|
||||
end
|
||||
|
||||
function render_loading()
|
||||
if not loading then return end
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(resX, resY)
|
||||
gfx.LineTo(resX - 350, resY)
|
||||
gfx.LineTo(resX - 300, resY - 50)
|
||||
gfx.LineTo(resX, resY - 50)
|
||||
gfx.ClosePath()
|
||||
gfx.FillColor(33,33,33)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.FontSize(70)
|
||||
gfx.Text("LOADING...", resX - 20, resY - 3)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_hotkeys()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,240)
|
||||
gfx.Rect(0,resY - 50, resX, 50)
|
||||
gfx.Fill()
|
||||
gfx.FontSize(30)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.Text("FXR: Sorting", resX/2 + 20, resY - 10)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT, gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.Text("FXL: Levels", resX/2 - 20, resY - 10)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_info()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, resY)
|
||||
gfx.LineTo(350, resY)
|
||||
gfx.LineTo(300, resY - 50)
|
||||
gfx.LineTo(0, resY - 50)
|
||||
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("Nautica", 3, resY - 3)
|
||||
local xmin,ymin,xmax,ymax = gfx.TextBounds(3, resY - 3, "Nautica")
|
||||
gfx.FontSize(20)
|
||||
gfx.Text("https://ksm.dev/", xmax + 13, resY - 3)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, resX, resY, backgroundImage, 1, 0);
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
displayCursorPosX = displayCursorPosX - (displayCursorPosX - cursorPosX) * deltaTime * 10
|
||||
displayCursorPosY = displayCursorPosY - (displayCursorPosY - cursorPosY) * deltaTime * 10
|
||||
if displayCursorPosY - yOffset > yCount - 1 then --scrolling down
|
||||
yOffset = yOffset - (yOffset - displayCursorPosY) - yCount + 1
|
||||
elseif displayCursorPosY - yOffset < 0 then
|
||||
yOffset = yOffset - (yOffset - displayCursorPosY)
|
||||
end
|
||||
gfx.Translate(xOffset, 50 - yOffset * entryH)
|
||||
for i, song in ipairs(songs) do
|
||||
if math.abs(cursorPos - i) <= xCount * yCount + xCount then
|
||||
i = i - 1
|
||||
local x = entryW * (i % xCount)
|
||||
local y = math.floor(i / xCount) * entryH
|
||||
render_song(song, x, y)
|
||||
if math.abs(#songs - i) < 4 then load_more() end
|
||||
end
|
||||
end
|
||||
render_cursor()
|
||||
if needsReload then reload_songs() end
|
||||
if screenState == 1 then render_level_filters()
|
||||
elseif screenState == 2 then render_sorting_selection()
|
||||
end
|
||||
render_hotkeys()
|
||||
render_loading()
|
||||
render_info()
|
||||
end
|
||||
|
||||
function archive_callback(entries, id)
|
||||
game.Log("Listing entries for " .. id, 0)
|
||||
local songsfolder = dlScreen.GetSongsPath()
|
||||
res = {}
|
||||
folders = { songsfolder .. "/nautica/" }
|
||||
local hasFolder = false
|
||||
for i, entry in ipairs(entries) do
|
||||
for j = 1, #entry do
|
||||
if entry:sub(j,j) == '/' then
|
||||
hasFolder = true
|
||||
table.insert(folders, songsfolder .. "/nautica/" .. entry:sub(1,j))
|
||||
end
|
||||
end
|
||||
game.Log(entry, 0)
|
||||
res[entry] = songsfolder .. "/nautica/" .. entry
|
||||
end
|
||||
|
||||
if not hasFolder then
|
||||
for i, entry in ipairs(entries) do
|
||||
res[entry] = songsfolder .. "/nautica/" .. id .. "/" .. entry
|
||||
end
|
||||
table.insert(folders, songsfolder .. "/nautica/" .. id .. "/")
|
||||
end
|
||||
downloaded[id] = "Downloaded"
|
||||
res[".folders"] = table.concat(folders, "|")
|
||||
return res
|
||||
end
|
||||
|
||||
function reload_songs()
|
||||
needsReload = true
|
||||
if loading then return end
|
||||
local useLevels = false
|
||||
local levelarr = {}
|
||||
|
||||
for i,value in ipairs(selectedLevels) do
|
||||
if value then
|
||||
useLevels = true
|
||||
table.insert(levelarr, i)
|
||||
end
|
||||
end
|
||||
nextUrl = string.format("https://ksm.dev/app/songs?sort=%s", selectedSorting:lower())
|
||||
if useLevels then
|
||||
nextUrl = nextUrl .. "&levels=" .. table.concat(levelarr, ",")
|
||||
end
|
||||
songs = {}
|
||||
cursorPos = 0
|
||||
cursorPosX = 0
|
||||
cursorPosY = 0
|
||||
displayCursorPosX = 0
|
||||
displayCursorPosY = 0
|
||||
load_more()
|
||||
game.Log(nextUrl, 0)
|
||||
needsReload = false
|
||||
|
||||
end
|
||||
|
||||
function button_pressed(button)
|
||||
if button == game.BUTTON_STA then
|
||||
if screenState == 0 then
|
||||
local song = songs[cursorPos + 1]
|
||||
if song == nil then return end
|
||||
dlScreen.DownloadArchive(encodeURI(song.cdn_download_url), header, song.id, archive_callback)
|
||||
downloaded[song.id] = "Downloading..."
|
||||
elseif screenState == 1 then
|
||||
if selectedLevels[levelcursor + 1] then
|
||||
selectedLevels[levelcursor + 1] = false
|
||||
else
|
||||
selectedLevels[levelcursor + 1] = true
|
||||
end
|
||||
reload_songs()
|
||||
elseif screenState == 2 then
|
||||
selectedSorting = sortingOptions[sortingcursor + 1]
|
||||
reload_songs()
|
||||
end
|
||||
|
||||
elseif button == game.BUTTON_BTA then
|
||||
if screenState == 0 then
|
||||
local song = songs[cursorPos + 1]
|
||||
if song == nil then return end
|
||||
dlScreen.PlayPreview(encodeURI(song.preview_url), header, song.id)
|
||||
song.status = "Playing"
|
||||
if lastPlaying ~=nil then
|
||||
lastPlaying.status = nil
|
||||
end
|
||||
lastPlaying = song
|
||||
end
|
||||
|
||||
elseif button == game.BUTTON_FXL then
|
||||
if screenState ~= 1 then
|
||||
screenState = 1
|
||||
else
|
||||
screenState = 0
|
||||
end
|
||||
elseif button == game.BUTTON_FXR then
|
||||
if screenState ~= 2 then
|
||||
screenState = 2
|
||||
else
|
||||
screenState = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function key_pressed(key)
|
||||
if key == 27 then --escape pressed
|
||||
dlcache = io.open(cachepath, "w")
|
||||
dlcache:write(json.encode(downloaded))
|
||||
dlcache:close()
|
||||
dlScreen.Exit()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function advance_selection(steps)
|
||||
if screenState == 0 and #songs > 0 then
|
||||
cursorPos = (cursorPos + steps) % #songs
|
||||
cursorPosX = cursorPos % xCount
|
||||
cursorPosY = math.floor(cursorPos / xCount)
|
||||
if cursorPos > #songs - 6 then
|
||||
load_more()
|
||||
end
|
||||
elseif screenState == 1 then
|
||||
levelcursor = (levelcursor + steps) % 20
|
||||
elseif screenState == 2 then
|
||||
sortingcursor = (sortingcursor + steps) % #sortingOptions
|
||||
end
|
||||
end
|
||||
|
||||
function render_level_filters()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0, resX, resY)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(60)
|
||||
gfx.Text("Level filters:", 10, 10)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(resX/2 - 30, resY/2 - 22, 60, 44)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
for i = 1, 20 do
|
||||
y = (resY/2) + (i - (levelcursor + 1)) * 40
|
||||
if selectedLevels[i] then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
|
||||
gfx.Text(tostring(i), resX/2, y)
|
||||
end
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function render_sorting_selection()
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0,0, resX, resY)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FontSize(60)
|
||||
gfx.Text("Sorting method:", 10, 10)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(resX/2 - 75, resY/2 - 22, 150, 44)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
for i, opt in ipairs(sortingOptions) do
|
||||
y = (resY/2) + (i - (sortingcursor + 1)) * 40
|
||||
if selectedSorting == opt then gfx.FillColor(255,255,255) else gfx.FillColor(127,127,127) end
|
||||
gfx.Text(opt, resX/2, y)
|
||||
end
|
||||
gfx.Restore()
|
||||
end
|
|
@ -1,11 +1,5 @@
|
|||
|
||||
local VolforceWindow = require('components.volforceWindow')
|
||||
local Dimensions = require 'common.dimensions';
|
||||
|
||||
do
|
||||
local resx, resy = game.GetResolution();
|
||||
Dimensions.updateResolution(resx / resy);
|
||||
end
|
||||
|
||||
local Banner = require('gameplay.banner')
|
||||
local CritLine = require('gameplay.crit_line')
|
||||
|
@ -17,14 +11,31 @@ local Gauge = require('gameplay.gauge')
|
|||
local Chain = require('gameplay.chain')
|
||||
local LaserAlert = require('gameplay.laser_alert')
|
||||
|
||||
local HitFX = require 'gameplay.hitfx'
|
||||
local EarlyLate = require 'gameplay.earlylate'
|
||||
|
||||
local TrackEnd = require('gameplay.track_end')
|
||||
|
||||
local json = require("common.json")
|
||||
local json = require "json"
|
||||
|
||||
local showHitAnims = true;
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
|
||||
local resolutionChange = function(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
|
||||
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR);
|
||||
end
|
||||
|
||||
local users = nil
|
||||
|
||||
|
@ -33,8 +44,11 @@ local chain = 0;
|
|||
local score = 0;
|
||||
|
||||
function render(deltaTime)
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution();
|
||||
Dimensions.updateResolution(resx / resy);
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
Banner.render(deltaTime, users, gameplay.user_id);
|
||||
|
||||
|
@ -60,26 +74,15 @@ function render(deltaTime)
|
|||
Chain.render(deltaTime, gameplay.comboState, chain, gameplay.critLine.x, gameplay.critLine.y);
|
||||
|
||||
LaserAlert.render(deltaTime);
|
||||
|
||||
EarlyLate.render(deltaTime)
|
||||
end
|
||||
|
||||
function render_crit_base(deltaTime)
|
||||
local cl = gameplay.critLine
|
||||
|
||||
CritLine.renderBase(deltaTime, cl.x, cl.y, -cl.rotation);
|
||||
Console.render(deltaTime, cl.x, cl.y, -cl.rotation);
|
||||
CritLine.renderBase(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation, gameplay.critLine.cursors);
|
||||
Console.render(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation);
|
||||
end
|
||||
|
||||
function render_crit_overlay(deltaTime)
|
||||
local cl = gameplay.critLine
|
||||
local centerX = cl.x
|
||||
local centerY = cl.y
|
||||
local rot = -cl.rotation
|
||||
|
||||
HitFX.renderButtons(deltaTime, centerX, centerY, rot);
|
||||
HitFX.renderLasers(deltaTime, centerX, centerY, rot, cl.cursors);
|
||||
CritLine.renderOverlay(deltaTime, centerX, centerY, rot, cl.cursors, gameplay.laserActive)
|
||||
end
|
||||
|
||||
function render_intro(deltaTime)
|
||||
|
@ -100,9 +103,6 @@ end
|
|||
|
||||
function update_score(newScore)
|
||||
score = newScore
|
||||
if (score == 0) then
|
||||
maxChain = 0;
|
||||
end
|
||||
end
|
||||
|
||||
function update_combo(newCombo)
|
||||
|
@ -118,22 +118,11 @@ function near_hit(wasLate)
|
|||
end
|
||||
|
||||
function button_hit(button, rating, delta)
|
||||
if (showHitAnims) then
|
||||
if (rating == 1) then
|
||||
HitFX.TriggerAnimation("Near", button + 1)
|
||||
elseif (rating == 2) then
|
||||
HitFX.TriggerAnimation("Crit", button + 1)
|
||||
end
|
||||
end
|
||||
|
||||
if 0 < rating and rating < 3 then
|
||||
EarlyLate.TriggerAnimation(rating, delta)
|
||||
end
|
||||
end
|
||||
|
||||
function laser_slam_hit(slamLength, startPos, endPost, index)
|
||||
if (showHitAnims) then
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function laser_alert(isRight)
|
||||
|
@ -164,9 +153,9 @@ end
|
|||
|
||||
-- Update the users in the scoreboard
|
||||
function score_callback(response)
|
||||
if response.status ~= 200 then
|
||||
error()
|
||||
return
|
||||
if response.status ~= 200 then
|
||||
error()
|
||||
return
|
||||
end
|
||||
local jsondata = json.decode(response.text)
|
||||
users = {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
local Numbers = require('components.numbers')
|
||||
local Numbers = require('common.numbers')
|
||||
|
||||
local chainLabel = gfx.CreateSkinImage("gameplay/chain/label.png", 0)
|
||||
|
||||
|
|
|
@ -1,87 +1,45 @@
|
|||
|
||||
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)
|
||||
-- Similar to crit line transforms, since the console needs to follow the lane rotation
|
||||
local setUpTransforms = function (x,y,rotation)
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
local scale = resx / desw
|
||||
|
||||
|
||||
gfx.Translate(x, y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.Scale(scale,scale)
|
||||
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
|
||||
return
|
||||
end
|
||||
|
||||
Dimensions.setUpTransforms(
|
||||
|
||||
setUpTransforms(
|
||||
critLineCenterX,
|
||||
critLineCenterY,
|
||||
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 {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
|
||||
local Dimensions = require 'common.dimensions'
|
||||
|
||||
local blackGradientImage = gfx.CreateSkinImage('gameplay/crit_line/black_gradient.png', 0)
|
||||
|
||||
local baseImage = gfx.CreateSkinImage("gameplay/crit_line/base.png", 0)
|
||||
local baseImageLandscape = gfx.CreateSkinImage("gameplay/crit_line/base_landscape.png", 0)
|
||||
local textImage = gfx.CreateSkinImage("gameplay/crit_line/text.png", 0)
|
||||
|
@ -18,54 +14,45 @@ local cursorGlowTopImages = {
|
|||
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 = 1496
|
||||
local CRITBAR_H = 348
|
||||
|
||||
local CRITBAR_W = 1080 * 1.4
|
||||
local CRITBAR_H = 251 * 1.4
|
||||
local scale;
|
||||
|
||||
local isLandscape = false;
|
||||
|
||||
local drawCursors = function (scale, cursors, laserActive)
|
||||
local cursorW = 598 * 0.165;
|
||||
local cursorH = 673 * 0.14;
|
||||
local setUpTransforms = function (x,y,rotation)
|
||||
local resx, resy = game.GetResolution();
|
||||
isLandscape = resx > resy;
|
||||
|
||||
local tailW = cursorW * 9
|
||||
local tailH = cursorH * 9
|
||||
local desw, desh;
|
||||
|
||||
if (isLandscape) then
|
||||
desw = 1920;
|
||||
desh = 1080;
|
||||
else
|
||||
desw = 1080;
|
||||
desh = 1920;
|
||||
end
|
||||
|
||||
scale = resx / desw
|
||||
|
||||
gfx.Translate(x, y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.Scale(scale,scale)
|
||||
end
|
||||
|
||||
local drawCursors = function (centerX, centerY,cursors)
|
||||
local cursorW = 598*0.2;
|
||||
local cursorH = 673*0.2;
|
||||
for i = 0, 1, 1 do
|
||||
local luaIndex = i + 1
|
||||
local cursor = cursors[i];
|
||||
local r, g, b = game.GetLaserColor(i);
|
||||
|
||||
gfx.Save();
|
||||
local cursor = cursors[i];
|
||||
gfx.BeginPath();
|
||||
gfx.SkewX(cursor.skew)
|
||||
|
||||
local skew = cursor.pos * 0.001;
|
||||
gfx.SkewX(skew);
|
||||
|
||||
local cursorPos = cursor.pos * (1 / scale)
|
||||
local cursorX = cursorPos - 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
|
||||
local cursorX = (cursor.pos *(1/scale) - cursorW/2);
|
||||
local cursorY = (-cursorH/2);
|
||||
|
||||
gfx.ImageRect(
|
||||
cursorX,
|
||||
|
@ -82,23 +69,11 @@ local drawCursors = function (scale, cursors, laserActive)
|
|||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowWhite,
|
||||
glowAlpha,
|
||||
cursorGlowBottomImages[i+1],
|
||||
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,55 +89,40 @@ local drawCursors = function (scale, cursors, laserActive)
|
|||
cursorY,
|
||||
cursorW,
|
||||
cursorH,
|
||||
cursorGlowWhite,
|
||||
glowAlpha,
|
||||
cursorGlowTopImages[i+1],
|
||||
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)
|
||||
local renderBase = function (deltaTime, centerX, centerY, rotation, cursors)
|
||||
setUpTransforms(centerX, centerY, rotation)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0, 0, 0, 192)
|
||||
gfx.Rect(-9999, 0, 9999 * 2, 1080)
|
||||
gfx.Rect(-1080/2, 0, 1080, 1080)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath();
|
||||
if (isLandscape) then
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(-9999, -CRITBAR_H/2, 9999 * 2, CRITBAR_H, baseImageLandscape, 1, 0);
|
||||
gfx.ImageRect(-CRITBAR_W/2, -CRITBAR_H/2, CRITBAR_W, CRITBAR_H, baseImageLandscape, 1, 0);
|
||||
else
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(-CRITBAR_W/2, -CRITBAR_H/2, CRITBAR_W, CRITBAR_H, baseImage, 1, 0);
|
||||
end
|
||||
|
||||
drawCursors(centerX, centerY, cursors)
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
||||
local renderOverlay = function (deltaTime, centerX, centerY, rotation, cursors, laserActive)
|
||||
scale, _ = Dimensions.setUpTransforms(centerX, centerY, rotation)
|
||||
|
||||
drawCursors(scale, cursors, laserActive)
|
||||
|
||||
gfx.ResetTransform()
|
||||
local renderOverlay = function (deltaTime)
|
||||
|
||||
end
|
||||
|
||||
return {
|
||||
renderBase=renderBase,
|
||||
renderOverlay=renderOverlay
|
||||
}
|
||||
}
|
|
@ -1,109 +1,30 @@
|
|||
local Dimensions = require "common.dimensions"
|
||||
|
||||
-- Used for comparing button_hit()'s delta parameter with the
|
||||
-- gameplay_earlyLateFor/gameplay_msDisplay skin settings values.
|
||||
-- If the number is <= delta then the EarlyLate/ms should be shown
|
||||
local compare = {
|
||||
["ALL"] = 2,
|
||||
["CRITICAL (or worse)"] = 2,
|
||||
["NEAR (or worse)"] = 1,
|
||||
["NONE"] = -1,
|
||||
["OFF"] = -1
|
||||
}
|
||||
local desw = 1080;
|
||||
local desh = 1920;
|
||||
|
||||
local portraitHeightFractions = {
|
||||
["UPPER+"] = 2.4,
|
||||
["UPPER"] = 3,
|
||||
["STANDARD"] = 4.2,
|
||||
["LOWER"] = 5.3,
|
||||
}
|
||||
local transitionExistScale = 0;
|
||||
|
||||
local landscapeHeightFractions = {
|
||||
["UPPER+"] = 1.5,
|
||||
["UPPER"] = 2.7,
|
||||
["STANDARD"] = 4.1,
|
||||
["LOWER"] = 6.7,
|
||||
}
|
||||
|
||||
local earlyLateFor = compare[game.GetSkinSetting("gameplay_earlyLateFor")]
|
||||
local msFor = compare[game.GetSkinSetting("gameplay_msFor")]
|
||||
local earlyLatePosition = game.GetSkinSetting("gameplay_earlyLatePosition")
|
||||
|
||||
local EarlyLate = {
|
||||
timer = 0,
|
||||
color = {},
|
||||
earlyLateText = "",
|
||||
millisecText = ""
|
||||
}
|
||||
|
||||
function EarlyLate.render(deltaTime)
|
||||
if EarlyLate.timer <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
EarlyLate.timer = EarlyLate.timer - deltaTime * 100
|
||||
|
||||
local screenW, screenH = Dimensions.screen.width, Dimensions.screen.height
|
||||
local screenCenterX = screenW / 2
|
||||
|
||||
local desh, fractionTable
|
||||
|
||||
if screenH > screenW then
|
||||
desh = 1600
|
||||
fractionTable = portraitHeightFractions
|
||||
else
|
||||
desh = 1080
|
||||
fractionTable = landscapeHeightFractions
|
||||
end
|
||||
|
||||
local scale = screenH / desh
|
||||
local y = screenH / 8 * fractionTable[earlyLatePosition]
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.LoadSkinFont("Digital-Serial-ExtraBold.ttf")
|
||||
gfx.FontSize(20 * scale)
|
||||
|
||||
local color = EarlyLate.color
|
||||
gfx.FillColor(color[1], color[2], color[3])
|
||||
local tickTransitions = function (deltaTime)
|
||||
|
||||
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 transitionExistScale < 1 then
|
||||
transitionExistScale = transitionExistScale + deltaTime / 2 -- transition should last for that time in seconds
|
||||
end
|
||||
end
|
||||
|
||||
function EarlyLate.TriggerAnimation(rating, millisec)
|
||||
local showEarlyLate = rating <= earlyLateFor
|
||||
local showMillisec = rating <= msFor
|
||||
local isEarly = millisec < 0
|
||||
local render = function (deltaTime, comboState, combo, critLineCenterX, critLineCenterY)
|
||||
tickTransitions(deltaTime)
|
||||
|
||||
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 (transitionExistScale >= 1) then
|
||||
return;
|
||||
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
|
||||
local trigger = function ()
|
||||
|
||||
end
|
||||
|
||||
return {
|
||||
render=render
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
|
||||
require 'common.globals'
|
||||
|
||||
local Dimensions = require 'common.dimensions'
|
||||
|
||||
local Animation = require 'api.animation'
|
||||
|
||||
local Animations = {
|
||||
Crit = Animation.new('gameplay/hit_animation_frames/critical_taps', {
|
||||
centered = true,
|
||||
}),
|
||||
|
||||
Near = Animation.new('gameplay/hit_animation_frames/near_taps', {
|
||||
centered = true,
|
||||
}),
|
||||
|
||||
HoldCrit = Animation.new('gameplay/hit_animation_frames/hold_critical', {
|
||||
centered = true,
|
||||
loop = true,
|
||||
}),
|
||||
|
||||
HoldDome = Animation.new('gameplay/hit_animation_frames/hold_dome', {
|
||||
centered = true,
|
||||
loop = true,
|
||||
loopPoint = 10
|
||||
}),
|
||||
|
||||
HoldEnd = Animation.new('gameplay/hit_animation_frames/hold_end', {
|
||||
centered = true,
|
||||
}),
|
||||
|
||||
HoldInner = Animation.new('gameplay/hit_animation_frames/hold_inner', {
|
||||
centered = true,
|
||||
loop = true,
|
||||
}),
|
||||
|
||||
LaserCrit = Animation.new('gameplay/hit_animation_frames/laser_critical', {
|
||||
loop = true,
|
||||
}),
|
||||
|
||||
LaserDome = Animation.new('gameplay/hit_animation_frames/laser_dome', {
|
||||
loop = true,
|
||||
}),
|
||||
|
||||
LaserEndOuter = Animation.new('gameplay/hit_animation_frames/laser_end_outer', {}),
|
||||
|
||||
LaserEndLeft = Animation.new('gameplay/hit_animation_frames/laser_end_l_inner', {}),
|
||||
|
||||
LaserEndRight = Animation.new('gameplay/hit_animation_frames/laser_end_r_inner', {}),
|
||||
};
|
||||
|
||||
---@class LaserStateTable
|
||||
---@field Crit AnimationState
|
||||
---@field Dome AnimationState
|
||||
---@field EndInner AnimationState
|
||||
---@field EndOuter AnimationState
|
||||
|
||||
---@type LaserStateTable[]
|
||||
local laserStateTables = {
|
||||
{
|
||||
Crit = Animations.LaserCrit:createState(),
|
||||
Dome = Animations.LaserDome:createState(),
|
||||
EndInner = Animations.LaserEndLeft:createState(),
|
||||
EndOuter = Animations.LaserEndOuter:createState()
|
||||
},
|
||||
{
|
||||
Crit = Animations.LaserCrit:createState(),
|
||||
Dome = Animations.LaserDome:createState(),
|
||||
EndInner = Animations.LaserEndRight:createState(),
|
||||
EndOuter = Animations.LaserEndOuter:createState()
|
||||
}
|
||||
}
|
||||
|
||||
---@class HoldStateTable
|
||||
---@field Crit AnimationState
|
||||
---@field Dome AnimationState
|
||||
---@field End AnimationState
|
||||
---@field Inner AnimationState
|
||||
|
||||
---@type HoldStateTable[]
|
||||
local holdStateTables = {}
|
||||
|
||||
for i = 1, 6 do
|
||||
holdStateTables[i] = {
|
||||
Crit = Animations.HoldCrit:createState(),
|
||||
Dome = Animations.HoldDome:createState(),
|
||||
End = Animations.HoldEnd:createState(),
|
||||
Inner = Animations.HoldInner:createState()
|
||||
}
|
||||
end
|
||||
|
||||
---@type AnimationState[]
|
||||
local tapStates = {}
|
||||
|
||||
local HitFX = { };
|
||||
|
||||
local function setUpTransform(critCenterX, critCenterY, critRotation, xScalar)
|
||||
local critLine = gameplay.critLine
|
||||
local x = critCenterX + (critLine.line.x2 - critLine.line.x1) * xScalar
|
||||
local y = critCenterY + (critLine.line.y2 - critLine.line.y1) * xScalar
|
||||
|
||||
Dimensions.setUpTransforms(x, y, critRotation)
|
||||
end
|
||||
|
||||
function HitFX.renderLasers(deltaTime, critCenterX, critCenterY, critRotation, cursors)
|
||||
local hitSize = 406
|
||||
|
||||
-- Lasers
|
||||
for laser = 1, 2 do
|
||||
-- Update
|
||||
local isActive = gameplay.laserActive[laser]
|
||||
local laserState = laserStateTables[laser]
|
||||
local isAnimationPlaying = laserState.Dome.running
|
||||
|
||||
if isActive and not isAnimationPlaying then
|
||||
laserState.Crit:restart()
|
||||
laserState.Dome:restart()
|
||||
end
|
||||
|
||||
if not isActive and isAnimationPlaying then
|
||||
laserState.Crit:stop()
|
||||
laserState.Dome:stop()
|
||||
|
||||
laserState.EndInner:restart()
|
||||
laserState.EndOuter:restart()
|
||||
end
|
||||
|
||||
-- Render
|
||||
scale, _ = Dimensions.setUpTransforms(critCenterX, critCenterY, critRotation)
|
||||
|
||||
local laserColor = {game.GetLaserColor(laser - 1)}
|
||||
local x = cursors[laser - 1].pos * (1 / scale)
|
||||
|
||||
laserState.Dome:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize,
|
||||
color = laserColor,
|
||||
x = x,
|
||||
})
|
||||
|
||||
laserState.Crit:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize,
|
||||
x = x,
|
||||
})
|
||||
laserState.EndInner:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize,
|
||||
x = x,
|
||||
})
|
||||
laserState.EndOuter:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize,
|
||||
color = laserColor,
|
||||
x = x,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function HitFX.renderButtons(deltaTime, critCenterX, critCenterY, critRotation)
|
||||
--local baseHitSize = 325;
|
||||
local hitSize = 406
|
||||
|
||||
-- BT + FX
|
||||
for i = 1, 6 do
|
||||
--[[
|
||||
local hitSize = baseHitSize;
|
||||
if (i > 4) then
|
||||
hitSize = hitSize * 1.5;
|
||||
end
|
||||
]]
|
||||
|
||||
local laneWidth = (track.GetCurrentLaneXPos(2) - track.GetCurrentLaneXPos(1)) * (i <= 4 and 1 or 2);
|
||||
local lanePosition = track.GetCurrentLaneXPos(i) + laneWidth / 2
|
||||
if (i == 5) then
|
||||
lanePosition = -track.GetCurrentLaneXPos(6) - laneWidth / 2
|
||||
end
|
||||
|
||||
-- Update Holds
|
||||
local isHeld = gameplay.noteHeld[i]
|
||||
local holdStates = holdStateTables[i]
|
||||
local isAnimationPlaying = holdStates.Dome.running
|
||||
|
||||
if isHeld and not isAnimationPlaying then
|
||||
holdStates.Crit:restart()
|
||||
holdStates.Dome:restart()
|
||||
holdStates.Inner:restart()
|
||||
end
|
||||
|
||||
if not isHeld and isAnimationPlaying then
|
||||
holdStates.Crit:stop()
|
||||
holdStates.Dome:stop()
|
||||
holdStates.Inner:stop()
|
||||
|
||||
holdStates.End:restart()
|
||||
end
|
||||
|
||||
-- Render holds
|
||||
setUpTransform(critCenterX, critCenterY, critRotation, lanePosition)
|
||||
|
||||
holdStates.Inner:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize
|
||||
})
|
||||
holdStates.Dome:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize
|
||||
})
|
||||
holdStates.Crit:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize
|
||||
})
|
||||
holdStates.End:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize
|
||||
})
|
||||
|
||||
-- Render Taps
|
||||
local tapState = tapStates[i]
|
||||
|
||||
if tapState then
|
||||
tapState:render(deltaTime, {
|
||||
centered = true,
|
||||
width = hitSize,
|
||||
height = hitSize,
|
||||
});
|
||||
end
|
||||
end
|
||||
|
||||
gfx.ResetTransform()
|
||||
end
|
||||
|
||||
function HitFX.TriggerAnimation(name, lane)
|
||||
tapStates[lane] = Animations[name]:start();
|
||||
end
|
||||
|
||||
return HitFX;
|
|
@ -1,4 +1,4 @@
|
|||
local Numbers = require('components.numbers')
|
||||
local Numbers = require('common.numbers')
|
||||
|
||||
local bgImage = gfx.CreateSkinImage("gameplay/score_panel/bg.png", 0)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
local Charting = require('common.charting');
|
||||
local DiffRectangle = require('components.diff_rectangle');
|
||||
|
||||
local desw = 1080;
|
||||
|
@ -99,8 +98,7 @@ local render = function (deltaTime, bpm, laneSpeed, jacketPath, diff, level, pro
|
|||
);
|
||||
|
||||
-- Draw diff rectangle
|
||||
local adjustedDiff = Charting.GetDisplayDifficulty(gameplay.jacketPath, diff)
|
||||
DiffRectangle.render(deltaTime, 31, y+140, 0.84, adjustedDiff, level);
|
||||
DiffRectangle.render(deltaTime, 31, y+140, 0.84, diff, level);
|
||||
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
local Common = require('common.util')
|
||||
local Common = require('common.common')
|
||||
local Easing = require('common.easing')
|
||||
|
||||
local bgImage = gfx.CreateSkinImage("gameplay/track_end/bg.png", 0)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,251 +1,247 @@
|
|||
function clamp(x, min, max)
|
||||
if x < min then
|
||||
x = min
|
||||
end
|
||||
if x > max then
|
||||
x = max
|
||||
end
|
||||
|
||||
return x
|
||||
end
|
||||
|
||||
function smootherstep(edge0, edge1, x)
|
||||
-- Scale, and clamp x to 0..1 range
|
||||
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
|
||||
-- Evaluate polynomial
|
||||
return x * x * x * (x * (x * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
function to_range(val, start, stop)
|
||||
return start + (stop - start) * val
|
||||
end
|
||||
|
||||
Animation = {
|
||||
start = 0,
|
||||
stop = 0,
|
||||
progress = 0,
|
||||
duration = 1,
|
||||
smoothStart = false
|
||||
}
|
||||
|
||||
function Animation:new(o)
|
||||
o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Animation:restart(start, stop, duration)
|
||||
self.progress = 0
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.duration = duration
|
||||
end
|
||||
|
||||
function Animation:tick(deltaTime)
|
||||
self.progress = math.min(1, self.progress + deltaTime / self.duration)
|
||||
if self.progress == 1 then return self.stop end
|
||||
if self.smoothStart then
|
||||
return to_range(smootherstep(0, 1, self.progress), self.start, self.stop)
|
||||
else
|
||||
return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop)
|
||||
end
|
||||
end
|
||||
|
||||
local yScale = Animation:new()
|
||||
local diagWidth = 600
|
||||
local diagHeight = 400
|
||||
local tabStroke = {start=0, stop=1}
|
||||
local tabStrokeAnimation = {start=Animation:new(), stop=Animation:new()}
|
||||
local settingsStrokeAnimation = {x=Animation:new(), y=Animation:new()}
|
||||
local prevTab = -1
|
||||
local prevSettingStroke = {x=0, y=0}
|
||||
local settingStroke = {x=0, y=0}
|
||||
local prevVis = false
|
||||
|
||||
function processSkinSettings()
|
||||
for ti, tab in ipairs(SettingsDiag.tabs) do
|
||||
for si, setting in ipairs(tab.settings) do
|
||||
|
||||
if (tab.name == 'Game') then
|
||||
if (setting.name == 'Gauge') then
|
||||
game.SetSkinSetting('_gaugeType', setting.value);
|
||||
end
|
||||
if (setting.name == 'Backup Gauge') then
|
||||
game.SetSkinSetting('_gaugeARS', setting.value and 1 or 0);
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function render(deltaTime, visible)
|
||||
if visible and not prevVis then
|
||||
yScale:restart(0, 1, 0.25)
|
||||
elseif not visible and prevVis then
|
||||
yScale:restart(1, 0, 0.25)
|
||||
end
|
||||
processSkinSettings()
|
||||
|
||||
if not visible and yScale:tick(0) < 0.05 then return end
|
||||
|
||||
local posX = SettingsDiag.posX or 0.5
|
||||
local posY = SettingsDiag.posY or 0.5
|
||||
local message_1 = "Press both FXs to open/close. Use the Start button to press buttons."
|
||||
local message_2 = "Use FX keys to navigate tabs. Use arrow keys to navigate and modify settings."
|
||||
|
||||
gfx.Save()
|
||||
|
||||
resX, resY = game.GetResolution()
|
||||
local scale = resY / 1080
|
||||
gfx.ResetScissor()
|
||||
gfx.ResetTransform()
|
||||
gfx.Translate(math.floor(diagWidth/2 + posX*(resX-diagWidth)), math.floor(diagHeight/2 + posY*(resY-diagHeight)))
|
||||
gfx.Scale(scale, scale)
|
||||
gfx.Scale(1.0, smootherstep(0, 1, yScale:tick(deltaTime)))
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-diagWidth/2, -diagHeight/2, diagWidth, diagHeight)
|
||||
gfx.FillColor(50,50,50)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
|
||||
gfx.FontSize(20)
|
||||
|
||||
local m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_1)
|
||||
gfx.Text(message_1, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax - 20)
|
||||
|
||||
m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_2)
|
||||
gfx.Text(message_2, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax)
|
||||
|
||||
tabStroke.start = tabStrokeAnimation.start:tick(deltaTime)
|
||||
tabStroke.stop = tabStrokeAnimation.stop:tick(deltaTime)
|
||||
|
||||
settingStroke.x = settingsStrokeAnimation.x:tick(deltaTime)
|
||||
settingStroke.y = settingsStrokeAnimation.y:tick(deltaTime)
|
||||
|
||||
local tabBarHeight = 0
|
||||
local nextTabX = 5
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.FontSize(35)
|
||||
gfx.Save() --draw tab bar
|
||||
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
|
||||
for ti, tab in ipairs(SettingsDiag.tabs) do
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(nextTabX, 5, tab.name)
|
||||
|
||||
if ti == SettingsDiag.currentTab and SettingsDiag.currentTab ~= prevTab then
|
||||
tabStrokeAnimation.start:restart(tabStroke.start, nextTabX, 0.1)
|
||||
tabStrokeAnimation.stop:restart(tabStroke.stop, xmax, 0.1)
|
||||
end
|
||||
tabBarHeight = math.max(tabBarHeight, ymax + 5)
|
||||
gfx.Text(tab.name, nextTabX, 5)
|
||||
nextTabX = xmax + 10
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, tabBarHeight)
|
||||
gfx.LineTo(diagWidth, tabBarHeight)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(tabStroke.start, tabBarHeight)
|
||||
gfx.LineTo(tabStroke.stop, tabBarHeight)
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
gfx.Stroke()
|
||||
gfx.Restore() --draw tab bar end
|
||||
|
||||
gfx.FontSize(30)
|
||||
gfx.Save() --draw current tab
|
||||
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
|
||||
gfx.Translate(5, tabBarHeight + 5)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, settingStroke.y)
|
||||
gfx.LineTo(settingStroke.x, settingStroke.y)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
gfx.Stroke()
|
||||
|
||||
local settingHeight = 30
|
||||
local tab = SettingsDiag.tabs[SettingsDiag.currentTab]
|
||||
for si, setting in ipairs(tab.settings) do
|
||||
processSkinSettings(setting.name, setting.value)
|
||||
|
||||
local disp = ""
|
||||
if setting.type == "enum" then
|
||||
disp = string.format("%s: %s", setting.name, setting.options[setting.value])
|
||||
elseif setting.type == "int" then
|
||||
disp = string.format("%s: %d", setting.name, setting.value)
|
||||
elseif setting.type == "float" then
|
||||
disp = string.format("%s: %.2f", setting.name, setting.value)
|
||||
if setting.max == 1 and setting.min == 0 then --draw slider
|
||||
disp = setting.name .. ": "
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
local width = diagWidth - 20 - xmax
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(xmax + 5, 20)
|
||||
gfx.LineTo(xmax + 5 + width, 20)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(xmax + 5, 20)
|
||||
gfx.LineTo(xmax + 5 + width * setting.value, 20)
|
||||
gfx.StrokeColor(255,127,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
end
|
||||
elseif setting.type == "button" then
|
||||
disp = string.format("%s", setting.name)
|
||||
local xmin, ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-2, 3, 4+xmax-xmin, 28)
|
||||
gfx.FillColor(0, 64, 128)
|
||||
if si == SettingsDiag.currentSetting then
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
else
|
||||
gfx.StrokeColor(0,127,255)
|
||||
end
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
else
|
||||
disp = string.format("%s:", setting.name)
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(xmax + 5, 5, 20,20)
|
||||
gfx.FillColor(255, 127, 0, setting.value and 255 or 0)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
end
|
||||
gfx.Text(disp, 0 ,0)
|
||||
if si == SettingsDiag.currentSetting then
|
||||
local setting_name = setting.name .. ":"
|
||||
if setting.type == "button" then
|
||||
setting_name = setting.name
|
||||
end
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, setting_name)
|
||||
ymax = ymax + settingHeight * (si - 1)
|
||||
if xmax ~= prevSettingStroke.x or ymax ~= prevSettingStroke.y then
|
||||
settingsStrokeAnimation.x:restart(settingStroke.x, xmax, 0.1)
|
||||
settingsStrokeAnimation.y:restart(settingStroke.y, ymax, 0.1)
|
||||
end
|
||||
|
||||
prevSettingStroke.x = xmax
|
||||
prevSettingStroke.y = ymax
|
||||
end
|
||||
gfx.Translate(0, settingHeight)
|
||||
end
|
||||
|
||||
|
||||
gfx.Restore() --draw current tab end
|
||||
prevTab = SettingsDiag.currentTab
|
||||
prevVis = visible
|
||||
|
||||
gfx.Restore()
|
||||
function clamp(x, min, max)
|
||||
if x < min then
|
||||
x = min
|
||||
end
|
||||
if x > max then
|
||||
x = max
|
||||
end
|
||||
|
||||
return x
|
||||
end
|
||||
|
||||
function smootherstep(edge0, edge1, x)
|
||||
-- Scale, and clamp x to 0..1 range
|
||||
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0)
|
||||
-- Evaluate polynomial
|
||||
return x * x * x * (x * (x * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
function to_range(val, start, stop)
|
||||
return start + (stop - start) * val
|
||||
end
|
||||
|
||||
Animation = {
|
||||
start = 0,
|
||||
stop = 0,
|
||||
progress = 0,
|
||||
duration = 1,
|
||||
smoothStart = false
|
||||
}
|
||||
|
||||
function Animation:new(o)
|
||||
o = o or {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function Animation:restart(start, stop, duration)
|
||||
self.progress = 0
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.duration = duration
|
||||
end
|
||||
|
||||
function Animation:tick(deltaTime)
|
||||
self.progress = math.min(1, self.progress + deltaTime / self.duration)
|
||||
if self.progress == 1 then return self.stop end
|
||||
if self.smoothStart then
|
||||
return to_range(smootherstep(0, 1, self.progress), self.start, self.stop)
|
||||
else
|
||||
return to_range(smootherstep(-1, 1, self.progress) * 2 - 1, self.start, self.stop)
|
||||
end
|
||||
end
|
||||
|
||||
local yScale = Animation:new()
|
||||
local diagWidth = 600
|
||||
local diagHeight = 400
|
||||
local tabStroke = {start=0, stop=1}
|
||||
local tabStrokeAnimation = {start=Animation:new(), stop=Animation:new()}
|
||||
local settingsStrokeAnimation = {x=Animation:new(), y=Animation:new()}
|
||||
local prevTab = -1
|
||||
local prevSettingStroke = {x=0, y=0}
|
||||
local settingStroke = {x=0, y=0}
|
||||
local prevVis = false
|
||||
|
||||
function processSkinSettings()
|
||||
for ti, tab in ipairs(SettingsDiag.tabs) do
|
||||
for si, setting in ipairs(tab.settings) do
|
||||
|
||||
if (tab.name == 'Game') then
|
||||
if (setting.name == 'Gauge') then
|
||||
game.SetSkinSetting('_gaugeType', setting.value);
|
||||
end
|
||||
if (setting.name == 'Backup Gauge') then
|
||||
game.SetSkinSetting('_gaugeARS', setting.value and 1 or 0);
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function render(deltaTime, visible)
|
||||
if visible and not prevVis then
|
||||
yScale:restart(0, 1, 0.25)
|
||||
elseif not visible and prevVis then
|
||||
yScale:restart(1, 0, 0.25)
|
||||
end
|
||||
processSkinSettings()
|
||||
|
||||
if not visible and yScale:tick(0) < 0.05 then return end
|
||||
|
||||
local posX = SettingsDiag.posX or 0.5
|
||||
local posY = SettingsDiag.posY or 0.5
|
||||
local message_1 = "Press both FXs to open/close. Use the Start button to press buttons."
|
||||
local message_2 = "Use FX keys to navigate tabs. Use arrow keys to navigate and modify settings."
|
||||
|
||||
resX, resY = game.GetResolution()
|
||||
local scale = resY / 1080
|
||||
gfx.ResetTransform()
|
||||
gfx.Translate(math.floor(diagWidth/2 + posX*(resX-diagWidth)), math.floor(diagHeight/2 + posY*(resY-diagHeight)))
|
||||
gfx.Scale(scale, scale)
|
||||
gfx.Scale(1.0, smootherstep(0, 1, yScale:tick(deltaTime)))
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-diagWidth/2, -diagHeight/2, diagWidth, diagHeight)
|
||||
gfx.FillColor(50,50,50)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255)
|
||||
|
||||
gfx.FontSize(20)
|
||||
|
||||
local m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_1)
|
||||
gfx.Text(message_1, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax - 20)
|
||||
|
||||
m_xmin, m_ymin, m_xmax, m_ymax = gfx.TextBounds(0, 0, message_2)
|
||||
gfx.Text(message_2, diagWidth/2 - m_xmax, diagHeight/2 - m_ymax)
|
||||
|
||||
tabStroke.start = tabStrokeAnimation.start:tick(deltaTime)
|
||||
tabStroke.stop = tabStrokeAnimation.stop:tick(deltaTime)
|
||||
|
||||
settingStroke.x = settingsStrokeAnimation.x:tick(deltaTime)
|
||||
settingStroke.y = settingsStrokeAnimation.y:tick(deltaTime)
|
||||
|
||||
local tabBarHeight = 0
|
||||
local nextTabX = 5
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.FontSize(35)
|
||||
gfx.Save() --draw tab bar
|
||||
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
|
||||
for ti, tab in ipairs(SettingsDiag.tabs) do
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(nextTabX, 5, tab.name)
|
||||
|
||||
if ti == SettingsDiag.currentTab and SettingsDiag.currentTab ~= prevTab then
|
||||
tabStrokeAnimation.start:restart(tabStroke.start, nextTabX, 0.1)
|
||||
tabStrokeAnimation.stop:restart(tabStroke.stop, xmax, 0.1)
|
||||
end
|
||||
tabBarHeight = math.max(tabBarHeight, ymax + 5)
|
||||
gfx.Text(tab.name, nextTabX, 5)
|
||||
nextTabX = xmax + 10
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, tabBarHeight)
|
||||
gfx.LineTo(diagWidth, tabBarHeight)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(tabStroke.start, tabBarHeight)
|
||||
gfx.LineTo(tabStroke.stop, tabBarHeight)
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
gfx.Stroke()
|
||||
gfx.Restore() --draw tab bar end
|
||||
|
||||
gfx.FontSize(30)
|
||||
gfx.Save() --draw current tab
|
||||
gfx.Translate(-diagWidth / 2, -diagHeight / 2)
|
||||
gfx.Translate(5, tabBarHeight + 5)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, settingStroke.y)
|
||||
gfx.LineTo(settingStroke.x, settingStroke.y)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
gfx.Stroke()
|
||||
|
||||
local settingHeight = 30
|
||||
local tab = SettingsDiag.tabs[SettingsDiag.currentTab]
|
||||
for si, setting in ipairs(tab.settings) do
|
||||
processSkinSettings(setting.name, setting.value)
|
||||
|
||||
local disp = ""
|
||||
if setting.type == "enum" then
|
||||
disp = string.format("%s: %s", setting.name, setting.options[setting.value])
|
||||
elseif setting.type == "int" then
|
||||
disp = string.format("%s: %d", setting.name, setting.value)
|
||||
elseif setting.type == "float" then
|
||||
disp = string.format("%s: %.2f", setting.name, setting.value)
|
||||
if setting.max == 1 and setting.min == 0 then --draw slider
|
||||
disp = setting.name .. ": "
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
local width = diagWidth - 20 - xmax
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(xmax + 5, 20)
|
||||
gfx.LineTo(xmax + 5 + width, 20)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(xmax + 5, 20)
|
||||
gfx.LineTo(xmax + 5 + width * setting.value, 20)
|
||||
gfx.StrokeColor(255,127,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Stroke()
|
||||
end
|
||||
elseif setting.type == "button" then
|
||||
disp = string.format("%s", setting.name)
|
||||
local xmin, ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(-2, 3, 4+xmax-xmin, 28)
|
||||
gfx.FillColor(0, 64, 128)
|
||||
if si == SettingsDiag.currentSetting then
|
||||
gfx.StrokeColor(255, 127, 0)
|
||||
else
|
||||
gfx.StrokeColor(0,127,255)
|
||||
end
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
else
|
||||
disp = string.format("%s:", setting.name)
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, disp)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(xmax + 5, 5, 20,20)
|
||||
gfx.FillColor(255, 127, 0, setting.value and 255 or 0)
|
||||
gfx.StrokeColor(0,127,255)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
end
|
||||
gfx.Text(disp, 0 ,0)
|
||||
if si == SettingsDiag.currentSetting then
|
||||
local setting_name = setting.name .. ":"
|
||||
if setting.type == "button" then
|
||||
setting_name = setting.name
|
||||
end
|
||||
local xmin,ymin, xmax,ymax = gfx.TextBounds(0, 0, setting_name)
|
||||
ymax = ymax + settingHeight * (si - 1)
|
||||
if xmax ~= prevSettingStroke.x or ymax ~= prevSettingStroke.y then
|
||||
settingsStrokeAnimation.x:restart(settingStroke.x, xmax, 0.1)
|
||||
settingsStrokeAnimation.y:restart(settingStroke.y, ymax, 0.1)
|
||||
end
|
||||
|
||||
prevSettingStroke.x = xmax
|
||||
prevSettingStroke.y = ymax
|
||||
end
|
||||
gfx.Translate(0, settingHeight)
|
||||
end
|
||||
|
||||
|
||||
gfx.Restore() --draw current tab end
|
||||
prevTab = SettingsDiag.currentTab
|
||||
prevVis = visible
|
||||
|
||||
end
|
|
@ -1,400 +1,400 @@
|
|||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2019 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.1" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2019 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.1" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\\\",
|
||||
[ "\"" ] = "\\\"",
|
||||
[ "\b" ] = "\\b",
|
||||
[ "\f" ] = "\\f",
|
||||
[ "\n" ] = "\\n",
|
||||
[ "\r" ] = "\\r",
|
||||
[ "\t" ] = "\\t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "\\/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return escape_char_map[c] or string.format("\\u%04x", c:byte())
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(3, 6), 16 )
|
||||
local n2 = tonumber( s:sub(9, 12), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local has_unicode_escape = false
|
||||
local has_surrogate_escape = false
|
||||
local has_escape = false
|
||||
local last
|
||||
for j = i + 1, #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
end
|
||||
|
||||
if last == 92 then -- "\\" (escape char)
|
||||
if x == 117 then -- "u" (unicode escape sequence)
|
||||
local hex = str:sub(j + 1, j + 5)
|
||||
if not hex:find("%x%x%x%x") then
|
||||
decode_error(str, j, "invalid unicode escape in string")
|
||||
end
|
||||
if hex:find("^[dD][89aAbB]") then
|
||||
has_surrogate_escape = true
|
||||
else
|
||||
has_unicode_escape = true
|
||||
end
|
||||
else
|
||||
local c = string.char(x)
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
has_escape = true
|
||||
end
|
||||
last = nil
|
||||
|
||||
elseif x == 34 then -- '"' (end of string)
|
||||
local s = str:sub(i + 1, j - 1)
|
||||
if has_surrogate_escape then
|
||||
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_unicode_escape then
|
||||
s = s:gsub("\\u....", parse_unicode_escape)
|
||||
end
|
||||
if has_escape then
|
||||
s = s:gsub("\\.", escape_char_map_inv)
|
||||
end
|
||||
return s, j + 1
|
||||
|
||||
else
|
||||
last = x
|
||||
end
|
||||
end
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
|
@ -2,37 +2,37 @@
|
|||
local lang = {
|
||||
Challanges = {
|
||||
--rightside
|
||||
ch = "Analyze your skill!",
|
||||
ch1 = "Find out how good you are by taking on challenges!\nIn this mode your ability will pushed to the limits.",
|
||||
ch = "Idk",
|
||||
ch1 = "Torture yourself :)",
|
||||
},
|
||||
Multiplayer = {
|
||||
--rightside
|
||||
mp = "Online matchmaking!",
|
||||
mp2 = "Play against your friends!\nSupport for up to 8 players.",
|
||||
mp = "Wait, there's multiplayer???",
|
||||
mp2 = "Yes, but nobody is ever online",
|
||||
},
|
||||
Start = {
|
||||
--rightside
|
||||
st = "Basic play!",
|
||||
st2 = "Play whatever songs you like!\nComplete for high scores on an IR around the world.",
|
||||
st = "Play shit idk",
|
||||
st2 = "Play something blah blah blah blah\nWith newlines",
|
||||
--leftside
|
||||
st3 = "Start",
|
||||
sc = "VOL to select",
|
||||
sc = "Scroll",
|
||||
desc = "Test description. Blah blah blah",
|
||||
},
|
||||
Nautica = {
|
||||
--rightside
|
||||
dls = "Download more songs!",
|
||||
dls2 = "Get new charts from ksm.dev, updated daily!",
|
||||
dls = "Download more songs",
|
||||
dls2 = "ksm.dev",
|
||||
},
|
||||
Settings = {
|
||||
--rightside
|
||||
se = "Open settings!",
|
||||
se1= "Tweak things to your liking.",
|
||||
se = "Adjust things",
|
||||
se1= "Open settings",
|
||||
},
|
||||
Exit = {
|
||||
--rightside
|
||||
ex = "Close the game!",
|
||||
ex2 = "Close the game and end this session.\nGoodbye!",
|
||||
ex = "Leave this cursed game",
|
||||
ex2 = "C'mon press that button!\nYou know you want to do it",
|
||||
},
|
||||
Result = {
|
||||
--leftside
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +1,11 @@
|
|||
local Numbers = require('common.numbers')
|
||||
local Easing = require('common.easing');
|
||||
local Charting = require('common.charting');
|
||||
local Background = require('components.background');
|
||||
local Footer = require('components.footer');
|
||||
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,18 +45,12 @@ 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);
|
||||
|
||||
local badgeLines = gfx.CreateSkinImage("result/badge_lines.png", 0);
|
||||
local badgeGrade = gfx.CreateSkinImage("result/badge_gradient.png", 0);
|
||||
|
||||
local gaugeTypeMirrorImage = gfx.CreateSkinImage("result/gauge_type_badges/mirror.png", 0);
|
||||
local gaugeTypeRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random.png", 0);
|
||||
local gaugeTypeMirrorRandomImage = gfx.CreateSkinImage("result/gauge_type_badges/random_mirror.png", 0);
|
||||
|
||||
local gradeImages = {
|
||||
S = gfx.CreateSkinImage("common/grades/S.png", 0),
|
||||
AAA_P = gfx.CreateSkinImage("common/grades/AAA+.png", 0),
|
||||
|
@ -103,8 +94,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 +129,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 +154,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 = {
|
||||
|
@ -192,13 +176,6 @@ local irText = ''
|
|||
game.LoadSkinSample("result")
|
||||
game.LoadSkinSample("shutter")
|
||||
|
||||
local function isHard(result)
|
||||
if result.flags == nil then
|
||||
return result.gauge_type == 1
|
||||
end
|
||||
return result.flags & 1 == 1
|
||||
end
|
||||
|
||||
local handleSfx = function()
|
||||
if not bgSfxPlayed then
|
||||
game.PlaySample("result", true)
|
||||
|
@ -207,55 +184,6 @@ local handleSfx = function()
|
|||
end
|
||||
end
|
||||
|
||||
local drawGraph = function(x,y,w,h)
|
||||
if isHard(result) then
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x,y,w,103)
|
||||
gfx.FillColor(26,26,26,255)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255,255)
|
||||
else
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x,y,w,h-68)
|
||||
gfx.FillColor(55,27,51,255)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x,y+30,w,72)
|
||||
gfx.FillColor(7,24,28,255)
|
||||
gfx.Fill()
|
||||
gfx.FillColor(255,255,255,255)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(x,y + h + 2 - h * result.gaugeSamples[1])
|
||||
for i = 2, #result.gaugeSamples do
|
||||
gfx.LineTo(x + i * w / #result.gaugeSamples,y + h + 2 - h * result.gaugeSamples[i])
|
||||
end
|
||||
|
||||
if isHard(result) then
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.StrokeColor(232,163,10)
|
||||
gfx.Stroke()
|
||||
gfx.Scissor(x, y + h *0.01, w, h*0.98)
|
||||
gfx.Stroke()
|
||||
gfx.ResetScissor()
|
||||
gfx.Scissor(x, y + h * 0.99, w, (h * 0.03) + 4)
|
||||
gfx.StrokeColor(255,0,0)
|
||||
gfx.Stroke()
|
||||
gfx.ResetScissor()
|
||||
else
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.StrokeColor(46,211,241)
|
||||
gfx.Scissor(x, y + h * 0.3, w, (h * 0.7) + 4)
|
||||
gfx.Stroke()
|
||||
gfx.ResetScissor()
|
||||
gfx.Scissor(x, y, w, h*0.3)
|
||||
gfx.StrokeColor(215,48,182)
|
||||
gfx.Stroke()
|
||||
gfx.ResetScissor()
|
||||
end
|
||||
end
|
||||
|
||||
function drawTimingBar(y, value, max, type)
|
||||
gfx.BeginPath();
|
||||
|
||||
|
@ -273,19 +201,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
|
||||
|
||||
|
@ -475,21 +401,6 @@ local drawRightPanelContent = function()
|
|||
gfx.Restore()
|
||||
end
|
||||
|
||||
-- Draw the gauge type flags if needed (mirror, random)
|
||||
if(result.mirror or result.random) then
|
||||
gfx.BeginPath();
|
||||
local gaugeTypeFlagPosX = gaugePosX + 10;
|
||||
local gaugeTypeFlagPosY = gaugePosY - 30;
|
||||
local flagw, flagh = gfx.ImageSize(gaugeTypeMirrorImage)
|
||||
if(result.mirror and result.random) then
|
||||
gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorRandomImage, 1, 0)
|
||||
elseif(result.mirror) then
|
||||
gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeMirrorImage, 1, 0)
|
||||
elseif(result.random) then
|
||||
gfx.ImageRect(gaugeTypeFlagPosX, gaugeTypeFlagPosY, flagw, flagh, gaugeTypeRandomImage, 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw err/early/critical/late/err texts
|
||||
|
||||
gfx.Text(earlyLateBarsStats.earlyErrors, rightPanelX + 683,
|
||||
|
@ -549,27 +460,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)
|
||||
|
@ -588,23 +497,8 @@ local drawBottomPanelContent = function(deltatime)
|
|||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.Text(result.medianHitDelta.." ms", rightX, baseY);
|
||||
gfx.Text(math.floor(result.meanHitDelta).." ms", rightX, baseY + detailTextMargin);
|
||||
|
||||
--Draw Graph
|
||||
drawGraph(leftX-22, baseY-18, 454, 98);
|
||||
|
||||
--draw Recommended Offset
|
||||
local delta = math.floor(result.medianHitDelta);
|
||||
local songOffset = 0;
|
||||
if (songOffset == nil) then songOffset = 0; end
|
||||
local offset = tonumber(songOffset) + delta;
|
||||
gfx.FillColor(255,255,255,255);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text('RECOMMENDED SONG OFFSET:', leftX + 367, baseY + 89);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(string.format("%dms", offset), leftX + 370, baseY + 89);
|
||||
end
|
||||
|
||||
|
||||
local drawJacketPanel = function()
|
||||
gfx.BeginPath();
|
||||
local tw, th = gfx.ImageSize(jacketPanelImage);
|
||||
|
@ -614,9 +508,10 @@ end
|
|||
|
||||
local drawJacketPanelContent = function(deltaTime)
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(jacketPanelX + 13, jacketPanelY + 28, 265, 265, jacketImage or defaultJacketImage, 1, 0);
|
||||
local adjustedDiff = Charting.GetDisplayDifficulty(result.jacketPath, result.difficulty)
|
||||
DiffRectangle.render(deltaTime, jacketPanelX+183, jacketPanelY+2.5, 0.67, adjustedDiff, result.level);
|
||||
gfx.ImageRect(jacketPanelX + 13, jacketPanelY + 28, 265, 265,
|
||||
jacketImage or defaultJacketImage, 1, 0);
|
||||
|
||||
DiffRectangle.render(deltaTime, jacketPanelX+183, jacketPanelY+2.5, 0.67, result.difficulty, result.level);
|
||||
|
||||
-- gfx.BeginPath();
|
||||
-- gfx.ImageRect(jacketPanelX + 183, jacketPanelY + 2.5, 140 / 1.5, 31 / 1.5,
|
||||
|
@ -646,7 +541,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 +558,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 +733,4 @@ end
|
|||
|
||||
screenshot_captured = function(path)
|
||||
game.PlaySample("shutter")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
local idols ={
|
||||
-- SDVX IV
|
||||
nearnoah_summer={"crew/anim/nearnoah_summer", 1 / 30, 0, true}
|
||||
-- SDVX V
|
||||
|
||||
-- SDVX III
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return idols
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,16 @@
|
|||
local Easing = require('common.easing')
|
||||
local Dim = require("common.dimensions")
|
||||
require('common')
|
||||
local Easing = require('common.easing');
|
||||
local SongSelectHeader = require('components.headers.songSelectHeader')
|
||||
local Footer = require('components.footer');
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
|
||||
local defaultFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/bg.png', 0)
|
||||
local collectionFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/col_bg.png', 0)
|
||||
local subFolderBgImage = gfx.CreateSkinImage('song_select/filter_wheel/sub_bg.png', 0)
|
||||
|
||||
local scrollBarBackgroundImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1)
|
||||
local scrollBarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1)
|
||||
local scrollbarBgImage = gfx.CreateSkinImage("song_select/scrollbar/bg.png", 1)
|
||||
local scrollbarFillImage = gfx.CreateSkinImage("song_select/scrollbar/fill.png", 1)
|
||||
|
||||
local cursorImages = {
|
||||
gfx.CreateSkinImage("song_select/cursor.png", 1), -- Effective rate or fallback
|
||||
|
@ -16,7 +19,7 @@ local cursorImages = {
|
|||
gfx.CreateSkinImage("song_select/cursor_blast.png", 1), -- Blastive rate
|
||||
}
|
||||
|
||||
local ITEM_HEIGHT = 172
|
||||
local ITEM_HEIGHT = 172;
|
||||
|
||||
local specialFolders = {
|
||||
{
|
||||
|
@ -89,27 +92,27 @@ local specialFolders = {
|
|||
}
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample('song_wheel/cursor_change.wav')
|
||||
game.LoadSkinSample('filter_wheel/open_close.wav')
|
||||
game.LoadSkinSample('song_wheel/cursor_change.wav');
|
||||
game.LoadSkinSample('filter_wheel/open_close.wav');
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw, desh = 1080, 1920
|
||||
local scale = 1
|
||||
local scale = 1;
|
||||
|
||||
local isFilterWheelActive = false
|
||||
local previousActiveState = false -- for open/close sounds
|
||||
local isFilterWheelActive = false;
|
||||
local previousActiveState = false; -- for open/close sounds
|
||||
|
||||
local selectionMode = 'folders'
|
||||
local selectedFolder = 1
|
||||
local selectedLevel = 1
|
||||
local selectionMode = 'folders';
|
||||
local selectedFolder = 1;
|
||||
local selectedLevel = 1;
|
||||
|
||||
local transitionScrollScale = 0
|
||||
local transitionScrollOffsetY = 0
|
||||
local scrollingUp = false
|
||||
local transitionScrollScale = 0;
|
||||
local transitionScrollOffsetY = 0;
|
||||
local scrollingUp = false;
|
||||
|
||||
local transitionLeaveScale = 1
|
||||
local transitionLeaveReappearTimer = 0
|
||||
local TRANSITION_LEAVE_DURATION = 0.1
|
||||
local transitionLeaveScale = 1;
|
||||
local transitionLeaveReappearTimer = 0;
|
||||
local TRANSITION_LEAVE_DURATION = 0.1;
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
@ -135,31 +138,31 @@ function resetLayoutInformation()
|
|||
end
|
||||
|
||||
function getCorrectedIndex(from, offset)
|
||||
local total = 1
|
||||
local total = 1;
|
||||
if selectionMode == 'folders' then
|
||||
total = #filters.folder
|
||||
total = #filters.folder;
|
||||
else
|
||||
total = #filters.level
|
||||
total = #filters.level;
|
||||
end
|
||||
|
||||
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 > total then
|
||||
indexesUntilEnd = total - from
|
||||
indexesUntilEnd = total - from;
|
||||
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
||||
end
|
||||
|
||||
return index
|
||||
return index;
|
||||
end
|
||||
|
||||
function getFolderData(folderLabel)
|
||||
local folderType = 'unknown'
|
||||
local isSpecial = false
|
||||
local folderBgImage = defaultFolderBgImage
|
||||
local folderType = 'unknown';
|
||||
local isSpecial = false;
|
||||
local folderBgImage = defaultFolderBgImage;
|
||||
|
||||
if not folderLabel then
|
||||
return {
|
||||
|
@ -177,16 +180,16 @@ function getFolderData(folderLabel)
|
|||
|
||||
|
||||
if (string.find(folderLabel, 'Folder: ')) then
|
||||
folderType = 'folder'
|
||||
folderType = 'folder';
|
||||
folderLabel = folderLabel:gsub('Folder: ', '') -- Delete default prefix
|
||||
elseif (string.find(folderLabel, 'Collection: ')) then
|
||||
folderType = 'collection'
|
||||
folderType = 'collection';
|
||||
folderLabel = folderLabel:gsub('Collection: ', '') -- Delete default prefix
|
||||
folderBgImage = collectionFolderBgImage
|
||||
folderBgImage = collectionFolderBgImage;
|
||||
elseif (string.find(folderLabel, 'Level: ')) then
|
||||
folderType = 'level'
|
||||
folderType = 'level';
|
||||
folderLabel = folderLabel:gsub('Level: ', '') -- Delete default prefix
|
||||
folderLabel = 'LEVEL ' .. folderLabel
|
||||
folderLabel = 'LEVEL ' .. folderLabel;
|
||||
end
|
||||
|
||||
local labelMatcherString = string.upper(folderLabel)
|
||||
|
@ -194,8 +197,8 @@ function getFolderData(folderLabel)
|
|||
for i, specialFolder in ipairs(specialFolders) do
|
||||
for i, specialFolderKey in ipairs(specialFolder.keys) do
|
||||
if (specialFolderKey == labelMatcherString) then
|
||||
folderBgImage = specialFolder.folderBg
|
||||
isSpecial = true
|
||||
folderBgImage = specialFolder.folderBg;
|
||||
isSpecial = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -223,51 +226,51 @@ function drawFolder(label, y)
|
|||
|
||||
-- Draw the folder label, but only if the folder is not special
|
||||
if (not folderData.isSpecial) then
|
||||
gfx.BeginPath()
|
||||
gfx.BeginPath();
|
||||
gfx.FontSize(38)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.Text(folderData.label, x + 18, y + 72)
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
gfx.Text(folderData.label, x + 18, y + 72);
|
||||
end
|
||||
end
|
||||
|
||||
function drawFolderList()
|
||||
gfx.GlobalAlpha(1-transitionLeaveScale)
|
||||
|
||||
local numOfItemsAround = 7
|
||||
local selectedIndex = 1
|
||||
local folderList = filters.folder
|
||||
local numOfItemsAround = 7;
|
||||
local selectedIndex = 1;
|
||||
local folderList = filters.folder;
|
||||
|
||||
if selectionMode == 'folders' then
|
||||
selectedIndex = selectedFolder
|
||||
folderList = filters.folder
|
||||
folderList = filters.folder;
|
||||
else
|
||||
selectedIndex = selectedLevel
|
||||
folderList = filters.level
|
||||
folderList = filters.level;
|
||||
end
|
||||
|
||||
local yOffset = transitionScrollOffsetY
|
||||
local yOffset = transitionScrollOffsetY;
|
||||
|
||||
local i = 1
|
||||
local i = 1;
|
||||
while (i <= numOfItemsAround) do
|
||||
local index = getCorrectedIndex(selectedIndex, -i)
|
||||
drawFolder(folderList[index],
|
||||
desh / 2 - ITEM_HEIGHT / 2 - ITEM_HEIGHT * i + yOffset)
|
||||
i = i + 1
|
||||
i = i + 1;
|
||||
end
|
||||
|
||||
-- Draw the selected song
|
||||
drawFolder(folderList[selectedIndex], desh / 2 - ITEM_HEIGHT / 2 + yOffset)
|
||||
|
||||
i = 1
|
||||
i = 1;
|
||||
while (i <= numOfItemsAround) do
|
||||
local index = getCorrectedIndex(selectedIndex, i)
|
||||
drawFolder(folderList[index],
|
||||
desh / 2 - ITEM_HEIGHT / 2 + ITEM_HEIGHT * i + yOffset)
|
||||
i = i + 1
|
||||
i = i + 1;
|
||||
end
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
gfx.GlobalAlpha(1);
|
||||
end
|
||||
|
||||
function drawCursor()
|
||||
|
@ -276,7 +279,7 @@ function drawCursor()
|
|||
gfx.BeginPath()
|
||||
|
||||
local cursorImageIndex = game.GetSkinSetting('_gaugeType')
|
||||
local cursorImage = cursorImages[cursorImageIndex or 1]
|
||||
local cursorImage = cursorImages[cursorImageIndex or 1];
|
||||
|
||||
gfx.ImageRect(desw / 2 - 14, desh / 2 - 213 / 2, 555, 213, cursorImage, 1, 0)
|
||||
end
|
||||
|
@ -285,39 +288,27 @@ function drawScrollbar()
|
|||
if not isFilterWheelActive or transitionLeaveScale ~= 0 then return end
|
||||
|
||||
gfx.BeginPath()
|
||||
local resize = 0.85
|
||||
local lw, lh = gfx.ImageSize(scrollBarBackgroundImage)
|
||||
local lw = lw * resize
|
||||
local lh = lh * resize
|
||||
local xPos = desw-20
|
||||
local backgroundYPos = desh/2 - lh/2
|
||||
gfx.ImageRect(xPos, backgroundYPos, lw, lh, scrollBarBackgroundImage, 1, 0)
|
||||
local bgW = 13*0.85;
|
||||
local bgH = 1282*0.85;
|
||||
local scrollPosX = desw-20
|
||||
local scrollPosY = desh/2-bgH/2
|
||||
|
||||
gfx.ImageRect(scrollPosX, scrollPosY, bgW, bgH, scrollbarBgImage, 1, 0)
|
||||
|
||||
local total = game.GetSkinSetting('_songWheelScrollbarTotal')
|
||||
local index = game.GetSkinSetting('_songWheelScrollbarIndex')
|
||||
|
||||
if (index == nil) then return end;
|
||||
|
||||
gfx.BeginPath()
|
||||
local sw, sh = gfx.ImageSize(scrollBarFillImage)
|
||||
local sw = sw * resize
|
||||
local sh = sh * resize
|
||||
local fillXPos = xPos - 6
|
||||
local fillW = 27*0.85
|
||||
local fillH = 65*0.85
|
||||
local fillPosOffsetY = (bgH-fillH)*(
|
||||
(index-1) /
|
||||
math.max(1,total-1)
|
||||
)
|
||||
|
||||
-- figure out index and total
|
||||
local index = 1
|
||||
local total = 1
|
||||
if selectionMode == 'folders' then
|
||||
index = selectedFolder
|
||||
total = #filters.folder
|
||||
else
|
||||
index = selectedLevel
|
||||
total = #filters.level
|
||||
end
|
||||
|
||||
|
||||
local minScrollYPos = backgroundYPos
|
||||
local maxScrollYPos = backgroundYPos + lh - sh
|
||||
local scrollStep = (maxScrollYPos - minScrollYPos) / (total - 1)
|
||||
local scrollbarYOffset = (index - 1) * scrollStep
|
||||
local scrollbarYPos = minScrollYPos + scrollbarYOffset
|
||||
|
||||
gfx.ImageRect(fillXPos, scrollbarYPos, sw, sh, scrollBarFillImage, 1, 0)
|
||||
gfx.ImageRect(scrollPosX-6, scrollPosY+fillPosOffsetY, fillW, fillH, scrollbarFillImage, 1, 0)
|
||||
end
|
||||
|
||||
function tickTransitions(deltaTime)
|
||||
|
@ -329,10 +320,10 @@ function tickTransitions(deltaTime)
|
|||
|
||||
if scrollingUp then
|
||||
transitionScrollOffsetY = Easing.inQuad(1 - transitionScrollScale) *
|
||||
ITEM_HEIGHT
|
||||
ITEM_HEIGHT;
|
||||
else
|
||||
transitionScrollOffsetY = Easing.inQuad(1 - transitionScrollScale) *
|
||||
-ITEM_HEIGHT
|
||||
-ITEM_HEIGHT;
|
||||
end
|
||||
|
||||
-- LEAVE TRANSITION
|
||||
|
@ -342,7 +333,7 @@ function tickTransitions(deltaTime)
|
|||
else
|
||||
transitionLeaveScale = 1
|
||||
end
|
||||
transitionLeaveReappearTimer = 1
|
||||
transitionLeaveReappearTimer = 1;
|
||||
else
|
||||
if (transitionLeaveReappearTimer == 1) then
|
||||
-- This stuff happens right after filterwheel is deactivated
|
||||
|
@ -351,85 +342,90 @@ function tickTransitions(deltaTime)
|
|||
transitionLeaveReappearTimer = transitionLeaveReappearTimer - deltaTime / (TRANSITION_LEAVE_DURATION + 0.05) -- same reasoning as in the songwheel
|
||||
|
||||
if (transitionLeaveReappearTimer <= 0) then
|
||||
transitionLeaveScale = 0
|
||||
transitionLeaveReappearTimer = 0
|
||||
transitionLeaveScale = 0;
|
||||
transitionLeaveReappearTimer = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function drawFilterWheelContent(deltatime)
|
||||
tickTransitions(deltatime)
|
||||
tickTransitions(deltatime);
|
||||
drawFolderList()
|
||||
end
|
||||
|
||||
local drawFilterWheel = function (deltaTime)
|
||||
local drawFilterWheel = function (x,y,w,h, deltaTime)
|
||||
gfx.Translate(x,y);
|
||||
gfx.Scale(w/1080, h/1920);
|
||||
|
||||
drawFilterWheelContent(deltaTime)
|
||||
drawCursor()
|
||||
drawScrollbar()
|
||||
|
||||
if (game.GetSkinSetting('_currentScreen') == 'songwheel') then
|
||||
SongSelectHeader.draw(deltaTime)
|
||||
SongSelectHeader.draw(deltaTime);
|
||||
Footer.draw(deltaTime);
|
||||
end
|
||||
|
||||
if (isFilterWheelActive ~= previousActiveState) then
|
||||
game.PlaySample('filter_wheel/open_close.wav')
|
||||
previousActiveState = isFilterWheelActive
|
||||
game.PlaySample('filter_wheel/open_close.wav');
|
||||
previousActiveState = isFilterWheelActive;
|
||||
end
|
||||
|
||||
-- Debug text
|
||||
gfx.BeginPath()
|
||||
gfx.BeginPath();
|
||||
gfx.FontSize(18)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
if game.GetSkinSetting('debug_showInformation') then
|
||||
gfx.Text('S_M: ' .. selectionMode .. ' // S_F: ' .. selectedFolder ..
|
||||
' // S_L: ' .. selectedLevel .. ' // L_TS: ' ..
|
||||
transitionLeaveScale .. ' // L_TRT: ' .. transitionLeaveReappearTimer, 8, 1870)
|
||||
transitionLeaveScale .. ' // L_TRT: ' .. transitionLeaveReappearTimer, 8, 1870);
|
||||
end
|
||||
end
|
||||
|
||||
render = function(deltaTime, shown)
|
||||
isFilterWheelActive = shown
|
||||
isFilterWheelActive = shown;
|
||||
|
||||
if not shown then
|
||||
game.SetSkinSetting('_songWheelOverlayActive', 0)
|
||||
game.SetSkinSetting('_songWheelOverlayActive', 0);
|
||||
else
|
||||
game.SetSkinSetting('_songWheelOverlayActive', 1)
|
||||
game.SetSkinSetting('_songWheelOverlayActive', 1);
|
||||
end
|
||||
|
||||
game.SetSkinSetting('_songWheelActiveFolderLabel', getFolderData(filters.folder[selectedFolder]).label)
|
||||
game.SetSkinSetting('_songWheelActiveSubFolderLabel', getFolderData(filters.level[selectedLevel]).label)
|
||||
game.SetSkinSetting('_songWheelActiveFolderLabel', getFolderData(filters.folder[selectedFolder]).label);
|
||||
game.SetSkinSetting('_songWheelActiveSubFolderLabel', getFolderData(filters.level[selectedLevel]).label);
|
||||
|
||||
Dim.updateResolution()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution();
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
drawFilterWheel(deltaTime)
|
||||
drawFilterWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime);
|
||||
end
|
||||
|
||||
set_selection = function(newIndex, isFolder)
|
||||
local oldIndex = 1
|
||||
local total = 1
|
||||
local oldIndex = 1;
|
||||
local total = 1;
|
||||
if isFolder then
|
||||
oldIndex = selectedFolder
|
||||
selectedFolder = newIndex
|
||||
total = #filters.folder
|
||||
total = #filters.folder;
|
||||
else
|
||||
oldIndex = selectedLevel
|
||||
selectedLevel = newIndex
|
||||
total = #filters.level
|
||||
total = #filters.level;
|
||||
end
|
||||
|
||||
transitionScrollScale = 0
|
||||
transitionScrollScale = 0;
|
||||
|
||||
scrollingUp = false
|
||||
scrollingUp = false;
|
||||
if ((newIndex > oldIndex and not (newIndex == total and oldIndex == 1)) or
|
||||
(newIndex == 1 and oldIndex == total)) then scrollingUp = true end
|
||||
(newIndex == 1 and oldIndex == total)) then scrollingUp = true; end
|
||||
|
||||
game.PlaySample('song_wheel/cursor_change.wav')
|
||||
game.PlaySample('song_wheel/cursor_change.wav');
|
||||
end
|
||||
|
||||
set_mode = function(isFolder)
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
resx,resy = game.GetResolution()
|
||||
local wheelY = -resy
|
||||
local bgFade = 0
|
||||
local yoff = 0
|
||||
local lastSelected = 0
|
||||
|
||||
render = function(deltaTime, shown)
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("segoeui.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.FontSize(40);
|
||||
if shown then
|
||||
bgFade = math.min(bgFade + deltaTime * 10, 1)
|
||||
wheelY = math.min(wheelY + deltaTime * resy * 10, 0)
|
||||
else
|
||||
wheelY = math.max(wheelY - deltaTime * resy * 10, -resy)
|
||||
bgFade = math.max(bgFade - deltaTime * 10, 0)
|
||||
end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgFade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
yoff = 0.8 * yoff + (settings.currentSelection - lastSelected)
|
||||
lastSelected = settings.currentSelection
|
||||
if bgFade > 0 then
|
||||
for i,setting in ipairs(settings) do
|
||||
if i == settings.currentSelection then
|
||||
gfx.FillColor(255,255,255)
|
||||
else
|
||||
gfx.FillColor(70,70,70)
|
||||
end
|
||||
gfx.FastText(string.format("%s: %s", setting.name, setting.value), resx/2, resy/2 + 40 * (i - settings.currentSelection + yoff) + wheelY, 40, gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,149 @@
|
|||
local window = {
|
||||
isPortrait = false,
|
||||
resX = 0,
|
||||
resY = 0,
|
||||
scale = 1,
|
||||
w = 0,
|
||||
h = 0,
|
||||
|
||||
set = function(this, doScale)
|
||||
local resX, resY = game.GetResolution();
|
||||
|
||||
if ((this.resX ~= resX) or (this.resY ~= this.resY)) then
|
||||
this.isPortrait = resY > resX;
|
||||
this.w = (this.isPortrait and 1080) or 1920;
|
||||
this.h = this.w * (resY / resX);
|
||||
this.scale = resX / this.w;
|
||||
|
||||
this.resX = resX;
|
||||
this.resY = resY;
|
||||
end
|
||||
|
||||
if (doScale) then gfx.Scale(this.scale, this.scale); end
|
||||
end,
|
||||
};
|
||||
|
||||
local wheel = {
|
||||
cache = { w = 0, h = 0 },
|
||||
visibleSongs = 11,
|
||||
margin = 13,
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = 0,
|
||||
h = {
|
||||
song = 0,
|
||||
total = 0,
|
||||
},
|
||||
|
||||
setSizes = function(this)
|
||||
if ((this.cache.w ~= window.w) or (this.cache.h ~= window.h)) then
|
||||
local marginTotal = this.margin * (this.visibleSongs - 1);
|
||||
|
||||
this.x = window.w / 2;
|
||||
this.y = 0;
|
||||
this.w = window.w / 2;
|
||||
this.h.total = window.h - marginTotal;
|
||||
this.h.song = this.h.total / this.visibleSongs;
|
||||
|
||||
this.cache.w = window.w;
|
||||
this.cache.h = window.h;
|
||||
end
|
||||
end,
|
||||
};
|
||||
|
||||
local displaying = {};
|
||||
local jacketCache = {};
|
||||
|
||||
local currDiff = 1;
|
||||
local currSong = 1;
|
||||
|
||||
local jacketFallback = gfx.CreateSkinImage('song_select/loading.png', 0);
|
||||
|
||||
local getJacket = function(diff)
|
||||
if ((not jacketCache[diff.jacketPath])
|
||||
or (jacketCache[diff.jacketPath] == jacketFallback)) then
|
||||
jacketCache[diff.jacketPath] = gfx.LoadImageJob(
|
||||
diff.jacketPath,
|
||||
jacketFallback,
|
||||
500,
|
||||
500
|
||||
);
|
||||
end
|
||||
|
||||
return jacketCache[diff.jacketPath];
|
||||
end
|
||||
|
||||
local setDisplaying = function()
|
||||
local songs = songwheel.songs;
|
||||
local enoughSongs = #songs >= wheel.visibleSongs;
|
||||
|
||||
displaying[5] = songs[currSong] or {};
|
||||
|
||||
for i = 1, 4 do
|
||||
if (enoughSongs) then
|
||||
displaying[5 - i] = songs[currSong - i] or songs[currSong + #songs - i];
|
||||
else
|
||||
displaying[5 - i] = songs[currSong - i] or {};
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, 3 do
|
||||
if (enoughSongs) then
|
||||
displaying[5 + i] = songs[currSong + i] or songs[currSong - #songs + i];
|
||||
else
|
||||
displaying[5 + i] = songs[currSong + i] or {};
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local renderWheel = function()
|
||||
local margin = wheel.margin;
|
||||
local x = wheel.x;
|
||||
local y = wheel.y;
|
||||
local w = wheel.w;
|
||||
local h = wheel.h.song;
|
||||
|
||||
for i, song in ipairs(displaying) do
|
||||
local isSelected = i == 5;
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0, 0, 0, (isSelected and 200) or 100);
|
||||
gfx.Rect(x, y, w, h);
|
||||
gfx.Fill();
|
||||
|
||||
if (song and song.difficulties) then
|
||||
local jacket = getJacket(song.difficulties[currDiff] or song.difficulties[1]);
|
||||
|
||||
if (jacket) then
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x, y, h, h, jacket, (isSelected and 1) or 0.5, 0);
|
||||
end
|
||||
end
|
||||
|
||||
y = y + h + margin;
|
||||
end
|
||||
end
|
||||
|
||||
render = function(dt)
|
||||
window:set(true);
|
||||
|
||||
wheel:setSizes();
|
||||
|
||||
setDisplaying();
|
||||
|
||||
renderWheel();
|
||||
|
||||
gfx.ForceRender();
|
||||
end
|
||||
|
||||
set_index = function(newSong)
|
||||
currSong = newSong;
|
||||
end
|
||||
|
||||
set_diff = function(newDiff)
|
||||
currDiff = newDiff;
|
||||
end
|
||||
|
||||
songs_changed = function(withAll)
|
||||
if (not withAll) then return; end
|
||||
end
|
|
@ -0,0 +1,675 @@
|
|||
easing = require("easing")
|
||||
|
||||
gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf")
|
||||
|
||||
game.LoadSkinSample("cursor_song")
|
||||
game.LoadSkinSample("cursor_difficulty")
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
|
||||
local levelFont = ImageFont.new("font-level", "0123456789")
|
||||
local diffFont = ImageFont.new("diff_num", "0123456789")
|
||||
local bpmFont = ImageFont.new("number", "0123456789.") -- FIXME: font-default
|
||||
local desw, desh;
|
||||
local resx, resy;
|
||||
local portrait;
|
||||
local scale;
|
||||
|
||||
function ResetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
portrait = resy > resx
|
||||
desw = portrait and 1080 or 1920
|
||||
desh = desw * (resy / resx)
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
ResetLayoutInformation()
|
||||
end
|
||||
|
||||
-- Grades
|
||||
---------
|
||||
local noGrade = Image.skin("song_select/grade/nograde.png")
|
||||
local grades = {
|
||||
{["min"] = 9900000, ["image"] = Image.skin("song_select/grade/s.png")},
|
||||
{["min"] = 9800000, ["image"] = Image.skin("song_select/grade/aaap.png")},
|
||||
{["min"] = 9700000, ["image"] = Image.skin("song_select/grade/aaa.png")},
|
||||
{["min"] = 9500000, ["image"] = Image.skin("song_select/grade/aap.png")},
|
||||
{["min"] = 9300000, ["image"] = Image.skin("song_select/grade/aa.png")},
|
||||
{["min"] = 9000000, ["image"] = Image.skin("song_select/grade/ap.png")},
|
||||
{["min"] = 8700000, ["image"] = Image.skin("song_select/grade/a.png")},
|
||||
{["min"] = 7500000, ["image"] = Image.skin("song_select/grade/b.png")},
|
||||
{["min"] = 6500000, ["image"] = Image.skin("song_select/grade/c.png")},
|
||||
{["min"] = 0, ["image"] = Image.skin("song_select/grade/d.png")},
|
||||
}
|
||||
|
||||
function lookup_grade_image(difficulty)
|
||||
local gradeImage = noGrade
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
for i, v in ipairs(grades) do
|
||||
if highScore.score >= v.min then
|
||||
gradeImage = v.image
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return { image = gradeImage, flicker = (gradeImage == grades[1].image) }
|
||||
end
|
||||
|
||||
-- Medals
|
||||
---------
|
||||
local noMedal = Image.skin("song_select/medal/nomedal.png")
|
||||
local medals = {
|
||||
Image.skin("song_select/medal/played.png"),
|
||||
Image.skin("song_select/medal/clear.png"),
|
||||
Image.skin("song_select/medal/hard.png"),
|
||||
Image.skin("song_select/medal/uc.png"),
|
||||
Image.skin("song_select/medal/puc.png")
|
||||
}
|
||||
|
||||
function lookup_medal_image(difficulty)
|
||||
local medalImage = noMedal
|
||||
local flicker = false
|
||||
if difficulty.scores[1] ~= nil then
|
||||
if difficulty.topBadge ~= 0 then
|
||||
medalImage = medals[difficulty.topBadge]
|
||||
if difficulty.topBadge >= 3 then -- hard
|
||||
flicker = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return { image = medalImage, flicker = flicker }
|
||||
end
|
||||
|
||||
-- Lookup difficulty
|
||||
function lookup_difficulty(diffs, diff)
|
||||
local diffIndex = nil
|
||||
for i, v in ipairs(diffs) do
|
||||
if v.difficulty + 1 == diff then
|
||||
diffIndex = i
|
||||
end
|
||||
end
|
||||
local difficulty = nil
|
||||
if diffIndex ~= nil then
|
||||
difficulty = diffs[diffIndex]
|
||||
end
|
||||
return difficulty
|
||||
end
|
||||
|
||||
-- JacketCache class
|
||||
--------------------
|
||||
JacketCache = {}
|
||||
JacketCache.new = function()
|
||||
local this = {
|
||||
cache = {},
|
||||
images = {
|
||||
loading = Image.skin("song_select/loading.png"),
|
||||
}
|
||||
}
|
||||
setmetatable(this, {__index = JacketCache})
|
||||
return this
|
||||
end
|
||||
|
||||
JacketCache.get = function(this, path)
|
||||
local jacket = this.cache[path]
|
||||
if not jacket or jacket == this.images.loading.image then
|
||||
jacket = gfx.LoadImageJob(path, this.images.loading.image)
|
||||
this.cache[path] = jacket
|
||||
end
|
||||
return Image.wrap(jacket)
|
||||
end
|
||||
|
||||
|
||||
-- SongData class
|
||||
-----------------
|
||||
SongData = {}
|
||||
SongData.new = function(jacketCache)
|
||||
local this = {
|
||||
selectedIndex = 1,
|
||||
selectedDifficulty = 0,
|
||||
memo = Memo.new(),
|
||||
jacketCache = jacketCache,
|
||||
images = {
|
||||
dataBg = Image.skin("song_select/data_bg.png"),
|
||||
fg = Image.skin("song_select/fg.png"),
|
||||
cursor = Image.skin("song_select/level_cursor.png"),
|
||||
none = Image.skin("song_select/level/none.png"),
|
||||
difficulties = {
|
||||
Image.skin("song_select/level/novice.png"),
|
||||
Image.skin("song_select/level/advanced.png"),
|
||||
Image.skin("song_select/level/exhaust.png"),
|
||||
Image.skin("song_select/level/maximum.png"),
|
||||
Image.skin("song_select/level/infinite.png"),
|
||||
Image.skin("song_select/level/gravity.png"),
|
||||
Image.skin("song_select/level/heavenly.png"),
|
||||
Image.skin("song_select/level/vivid.png")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
setmetatable(this, {__index = SongData})
|
||||
return this
|
||||
end
|
||||
|
||||
SongData.render = function(this, deltaTime)
|
||||
local song = songwheel.songs[this.selectedIndex]
|
||||
if not song then return end
|
||||
|
||||
-- Lookup difficulty
|
||||
local diff = song.difficulties[this.selectedDifficulty]
|
||||
if diff == nil then diff = song.difficulties[1] end
|
||||
|
||||
-- Draw the background
|
||||
this.images.dataBg:draw({ x = desw / 2, y = desh / 2, w = 1080 ,h = 1920})
|
||||
|
||||
-- Draw the jacket
|
||||
local jacket = this.jacketCache:get(diff.jacketPath)
|
||||
jacket:draw({ x = 97, y = 326, w = 346, h = 346, anchor_h = Image.ANCHOR_LEFT, anchor_v = Image.ANCHOR_TOP })
|
||||
|
||||
-- Draw the title
|
||||
local title = this.memo:memoize(string.format("title_%s", song.id), function ()
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
return gfx.CreateLabel(song.title, 24, 0)
|
||||
end)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.DrawLabel(title, 32, desh / 2 - 4, 390)
|
||||
|
||||
-- Draw the artist
|
||||
local artist = this.memo:memoize(string.format("artist_%s", song.id), function ()
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
return gfx.CreateLabel(song.artist, 24, 0)
|
||||
end)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.DrawLabel(artist, 32, desh / 2 + 42, 390)
|
||||
|
||||
-- Draw the effector
|
||||
local effector = this.memo:memoize(string.format("eff_%s_%s", song.id, diff.id), function ()
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
return gfx.CreateLabel(diff.effector, 16, 0)
|
||||
end)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.DrawLabel(effector, 260, desh / 2 + 208, 320)
|
||||
|
||||
-- Draw the illustrator
|
||||
if diff.illustrator then
|
||||
local illustrator = this.memo:memoize(string.format("ill_%s_%s", song.id, diff.id), function ()
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf")
|
||||
return gfx.CreateLabel(diff.illustrator, 16, 0)
|
||||
end)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.DrawLabel(illustrator, 260, desh / 2 + 238, 320)
|
||||
end
|
||||
|
||||
-- Draw the bpm
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf")
|
||||
gfx.FontSize(24)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
gfx.FillColor(255, 255, 255, 255)
|
||||
gfx.Text(song.bpm, 75, desh / 2 - 34)
|
||||
|
||||
this:draw_cursor(diff.difficulty)
|
||||
|
||||
-- Draw the hi-score
|
||||
local hiScore = diff.scores[1]
|
||||
if hiScore then
|
||||
-- FIXME: large / small font
|
||||
local scoreText = string.format("%08d", hiScore.score)
|
||||
levelFont:draw(scoreText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
|
||||
-- local scoreHiText = string.format("%04d", math.floor(hiScore.score / 1000))
|
||||
-- levelFont:draw(scoreHiText, 362, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
|
||||
-- local scoreLoText = string.format("%04d", hiScore.score % 1000)
|
||||
-- bpmFont:draw(scoreLoText, 470, 220, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
|
||||
end
|
||||
|
||||
-- Draw the grade and medal
|
||||
local grade = lookup_grade_image(diff)
|
||||
grade.image:draw({ x = desw / 2 - 157, y = desh / 2 - 162, scale = 0.85, alpha = grade.flicker and glowState and 0.9 or 1 })
|
||||
local medal = lookup_medal_image(diff)
|
||||
medal.image:draw({ x = desw / 2 - 72, y = desh / 2 - 199, scale = 0.86, alpha = medal.flicker and glowState and 0.9 or 1})
|
||||
|
||||
for i = 1, 4 do
|
||||
local d = lookup_difficulty(song.difficulties, i)
|
||||
this:draw_difficulty(i - 1, d, jacket)
|
||||
end
|
||||
end
|
||||
|
||||
SongData.draw_title_artist = function(this, label, x, y, maxWidth)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BASELINE)
|
||||
gfx.FillColor(55, 55, 55, 64)
|
||||
gfx.DrawLabel(label, x + 2, y + 2, maxWidth)
|
||||
gfx.FillColor(55, 55, 55, 255)
|
||||
gfx.DrawLabel(label, x, y, maxWidth)
|
||||
end
|
||||
|
||||
SongData.set_index = function(this, newIndex)
|
||||
this.selectedIndex = newIndex
|
||||
end
|
||||
|
||||
SongData.draw_cursor = function(this, index)
|
||||
local x = 98
|
||||
local y = desh / 2 + 133
|
||||
|
||||
-- Draw the cursor
|
||||
this.images.cursor:draw({ x = x + index * 115, y = y, scale = 0.85 })
|
||||
end
|
||||
|
||||
SongData.set_difficulty = function(this, newDiff)
|
||||
this.selectedDifficulty = newDiff
|
||||
end
|
||||
|
||||
SongData.draw_difficulty = function(this, index, diff, jacket)
|
||||
local x = 98
|
||||
local y = desh / 2 + 135
|
||||
|
||||
-- Draw the jacket icon
|
||||
local jacket = this.jacketCache.images.loading
|
||||
if diff ~= nil then jacket = this.jacketCache:get(diff.jacketPath) end
|
||||
|
||||
if diff == nil then
|
||||
this.images.none:draw({ x = x + index * 115, y = y - 600, scale = 0.78})
|
||||
else
|
||||
-- Draw the background
|
||||
this.images.difficulties[diff.difficulty + 1]:draw({ x = x + index * 115, y = y, scale = 0.78})
|
||||
-- Draw the level
|
||||
local levelText = string.format("%02d", diff.level)
|
||||
diffFont:draw(levelText, x + index * 115, y - 20, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
|
||||
end
|
||||
end
|
||||
|
||||
-- SongTable class
|
||||
------------------
|
||||
SongTable = {}
|
||||
SongTable.new = function(jacketCache)
|
||||
local this = {
|
||||
cols = 1,
|
||||
rows = 11,
|
||||
selectedIndex = 1,
|
||||
selectedDifficulty = 0,
|
||||
rowOffset = 0, -- song index offset of top-left song in page
|
||||
cursorPos = 0, -- cursor position in page [0..cols * rows)
|
||||
displayCursorPos = 0,
|
||||
cursorAnim = 0,
|
||||
cursorAnimTotal = 0.1,
|
||||
memo = Memo.new(),
|
||||
jacketCache = jacketCache,
|
||||
images = {
|
||||
matchingBg = Image.skin("song_select/matching_bg.png"),
|
||||
scoreBg = Image.skin("song_select/score_bg.png"),
|
||||
force = Image.skin("song_select/force.png"),
|
||||
cursor = Image.skin("song_select/cursor.png"),
|
||||
cursorText = Image.skin("song_select/cursor_text.png"),
|
||||
cursorDiamond = Image.skin("song_select/cursor_diamond.png"),
|
||||
cursorDiamondWire = Image.skin("song_select/cursor_diamond_wire.png"),
|
||||
plates = {
|
||||
Image.skin("song_select/plate/novice.png"),
|
||||
Image.skin("song_select/plate/advanced.png"),
|
||||
Image.skin("song_select/plate/exhaust.png"),
|
||||
Image.skin("song_select/plate/maximum.png"),
|
||||
Image.skin("song_select/plate/infinite.png"),
|
||||
Image.skin("song_select/plate/gravity.png"),
|
||||
Image.skin("song_select/plate/heavenly.png"),
|
||||
Image.skin("song_select/plate/vivid.png")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
setmetatable(this, {__index = SongTable})
|
||||
return this
|
||||
end
|
||||
|
||||
SongTable.calc_cursor_point = function(this, pos)
|
||||
local col = pos % this.cols
|
||||
local row = math.floor((pos) / this.cols)
|
||||
local x = desw * 0.75 + col * this.images.cursor.w
|
||||
local y = 0 + row * this.images.cursor.h
|
||||
return x, y
|
||||
end
|
||||
|
||||
SongTable.set_index = function(this, newIndex)
|
||||
if newIndex ~= this.selectedIndex then
|
||||
game.PlaySample("cursor_song")
|
||||
end
|
||||
|
||||
local delta = newIndex - this.selectedIndex
|
||||
if delta < -1 or delta > 1 then
|
||||
local newOffset = newIndex - 1
|
||||
this.rowOffset = math.floor((newIndex - 1) / this.cols) * this.cols
|
||||
this.cursorPos = (newIndex - 1) - this.rowOffset
|
||||
this.displayCursorPos = this.cursorPos
|
||||
else
|
||||
local newCursorPos = this.cursorPos + delta
|
||||
|
||||
if newCursorPos < 0 then
|
||||
-- scroll up
|
||||
this.rowOffset = this.rowOffset - this.cols
|
||||
if this.rowOffset < 0 then
|
||||
-- this.rowOffset = math.floor(#songwheel.songs / this.cols)
|
||||
end
|
||||
newCursorPos = newCursorPos + this.cols
|
||||
elseif newCursorPos >= this.cols * this.rows then
|
||||
-- scroll down
|
||||
this.rowOffset = this.rowOffset + this.cols
|
||||
newCursorPos = newCursorPos - this.cols
|
||||
else
|
||||
-- no scroll, move cursor in page
|
||||
end
|
||||
if this.cursorAnim > 0 then
|
||||
this.displayCursorPos = easing.outQuad(0.5 - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, 0.5)
|
||||
end
|
||||
this.cursorPos = newCursorPos
|
||||
this.cursorAnim = this.cursorAnimTotal
|
||||
end
|
||||
this.selectedIndex = newIndex
|
||||
end
|
||||
|
||||
SongTable.set_difficulty = function(this, newDiff)
|
||||
if newDiff ~= this.selectedDifficulty then
|
||||
game.PlaySample("cursor_difficulty")
|
||||
end
|
||||
this.selectedDifficulty = newDiff
|
||||
end
|
||||
|
||||
SongTable.render = function(this, deltaTime)
|
||||
this:draw_songs()
|
||||
this:draw_cursor(deltaTime)
|
||||
end
|
||||
|
||||
SongTable.draw_songs = function(this)
|
||||
for i = 1, this.cols * this.rows do
|
||||
if this.rowOffset + i <= #songwheel.songs then
|
||||
this:draw_song(i - 1, this.rowOffset + i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw the song plate
|
||||
SongTable.draw_song = function(this, pos, songIndex)
|
||||
local song = songwheel.songs[songIndex]
|
||||
if not song then return end
|
||||
|
||||
-- Lookup difficulty
|
||||
local diff = song.difficulties[this.selectedDifficulty]
|
||||
if diff == nil then diff = song.difficulties[1] end
|
||||
|
||||
local x, y = this:calc_cursor_point(pos)
|
||||
x = x + 4
|
||||
y = y + 16
|
||||
|
||||
-- Draw the jacket
|
||||
local jacket = this.jacketCache:get(diff.jacketPath)
|
||||
jacket:draw({ x = x - 24, y = y - 21, w = 122, h = 122 })
|
||||
|
||||
-- Draw the background
|
||||
gfx.FillColor(255, 255, 255)
|
||||
this.images.scoreBg:draw({ x = x + 72, y = y + 16 })
|
||||
if diff.force and diff.force > 0 then
|
||||
this.images.matchingBg:draw({ x = x + 72, y = y - 62 })
|
||||
end
|
||||
this.images.plates[diff.difficulty + 1]:draw({ x = x, y = y })
|
||||
|
||||
-- Draw the title
|
||||
local title = this.memo:memoize(string.format("title_%s", song.id), function ()
|
||||
gfx.LoadSkinFont("rounded-mplus-1c-bold.ttf")
|
||||
return gfx.CreateLabel(song.title, 14, 0)
|
||||
end)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BASELINE)
|
||||
gfx.DrawLabel(title, x - 22, y + 53, 125)
|
||||
|
||||
-- Draw the grade and medal
|
||||
local grade = lookup_grade_image(diff)
|
||||
grade.image:draw({ x = x + 78, y = y - 23, alpha = grade.flicker and glowState and 0.9 or 1 })
|
||||
|
||||
local medal = lookup_medal_image(diff)
|
||||
medal.image:draw({ x = x + 78, y = y + 10, alpha = medal.flicker and glowState and 0.9 or 1 })
|
||||
|
||||
-- Draw the level
|
||||
local levelText = string.format("%02d", diff.level)
|
||||
levelFont:draw(levelText, x + 72, y + 56, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
-- Draw the volforce
|
||||
--if diff.force and diff.force > 0 then
|
||||
--local forceText = string.format("%d", math.floor(diff.force * 100))
|
||||
--bpmFont:draw(forceText, x + , y - 60, 1, gfx.TEXT_ALIGN_CENTER, gfx.TEXT_ALIGN_MIDDLE)
|
||||
--end
|
||||
|
||||
--if diff.forceInTotal then
|
||||
--this.images.force:draw({x = x - 75, y = y - 60, w = 59, h = 59 })
|
||||
--end
|
||||
end
|
||||
|
||||
-- Draw the song cursor
|
||||
SongTable.draw_cursor = function(this, deltaTime)
|
||||
gfx.Save()
|
||||
|
||||
local pos = this.displayCursorPos
|
||||
if this.cursorAnim > 0 then
|
||||
this.cursorAnim = this.cursorAnim - deltaTime
|
||||
if this.cursorAnim <= 0 then
|
||||
this.displayCursorPos = this.cursorPos
|
||||
pos = this.cursorPos
|
||||
else
|
||||
pos = easing.outQuad(this.cursorAnimTotal - this.cursorAnim, this.displayCursorPos, this.cursorPos - this.displayCursorPos, this.cursorAnimTotal)
|
||||
end
|
||||
end
|
||||
|
||||
local x, y = this:calc_cursor_point(pos)
|
||||
gfx.FillColor(255, 255, 255)
|
||||
|
||||
local t = currentTime % 1
|
||||
|
||||
-- scroll text
|
||||
gfx.Scissor(
|
||||
x - this.images.cursor.w / 2, y - (this.images.cursor.h - 30) / 2,
|
||||
this.images.cursor.w, this.images.cursor.h - 30)
|
||||
local offset = (currentTime * 50) % 290
|
||||
local alpha = glowState and 0.8 or 1
|
||||
this.images.cursorText:draw({ x = x + 96, y = y + offset, alpha = alpha })
|
||||
this.images.cursorText:draw({ x = x + 96, y = y - 290 + offset, alpha = alpha })
|
||||
this.images.cursorText:draw({ x = x - 96, y = y + offset, alpha = alpha })
|
||||
this.images.cursorText:draw({ x = x - 96, y = y - 290 + offset, alpha = alpha })
|
||||
gfx.ResetScissor()
|
||||
|
||||
-- diamong wireframe
|
||||
local h = (this.images.cursorDiamondWire.h * 1.5) * easing.outQuad(t * 2, 0, 1, 1)
|
||||
this.images.cursorDiamondWire:draw({ x = x, y = y, w = this.images.cursorDiamondWire.w * 1.5, h = h, alpha = 0.5 })
|
||||
|
||||
-- ghost cursor
|
||||
alpha = easing.outSine(t, 1, -1, 1)
|
||||
h = this.images.cursor.h * easing.outSine(t, 0, 1, 1)
|
||||
this.images.cursor:draw({ x = x, y = y, h = h, alpha = alpha })
|
||||
|
||||
-- concrete cursor
|
||||
-- local w = this.images.cursor.w * easing.outSine(t, 1, 0.05, 0.5)
|
||||
this.images.cursor:draw({ x = x, y = y, alpha = glowState and 0.8 or 1 })
|
||||
|
||||
-- diamond knot
|
||||
gfx.GlobalCompositeOperation(gfx.BLEND_OP_LIGHTER)
|
||||
this.images.cursorDiamond:draw({ x = x + 100, y = y, alpha = 1 })
|
||||
this.images.cursorDiamond:draw({ x = x - 100, y = y, alpha = 1 })
|
||||
|
||||
local s = this.images.cursorDiamond.w / 1.5
|
||||
this.images.cursorDiamond:draw({ x = x + 90 + easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 })
|
||||
this.images.cursorDiamond:draw({ x = x - 90 - easing.outQuad(t, 0, -4, 0.5), y = y, w = s, h = s, alpha = 0.5 })
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
-- main
|
||||
-------
|
||||
|
||||
local jacketCache = JacketCache.new()
|
||||
local songData = SongData.new(jacketCache)
|
||||
local songTable = SongTable.new(jacketCache)
|
||||
|
||||
glowState = false
|
||||
currentTime = 0
|
||||
|
||||
-- Callback
|
||||
get_page_size = function()
|
||||
return 12
|
||||
end
|
||||
|
||||
searchIndex = 1
|
||||
soffset = 0
|
||||
searchText = gfx.CreateLabel("", 5, 0)
|
||||
|
||||
draw_search = function(x,y,w,h)
|
||||
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
|
||||
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
|
||||
game.PlaySample("woosh")
|
||||
end
|
||||
searchIndex = songwheel.searchInputActive and 0 or 1
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgfade = 1 - (searchIndex + soffset)
|
||||
--if not songwheel.searchInputActive then bgfade = soffset end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.ForceRender()
|
||||
local xpos = x + (searchIndex + soffset)*w
|
||||
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRect(xpos,y,w,h,h/2)
|
||||
gfx.FillColor(30,30,30)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("segoeui.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
|
||||
end
|
||||
|
||||
-- Callback
|
||||
function render(deltaTime)
|
||||
ResetLayoutInformation()
|
||||
|
||||
if ((math.floor(currentTime * 1000) % 100) < 50) then
|
||||
glowState = false
|
||||
else
|
||||
glowState = true
|
||||
end
|
||||
|
||||
local xshift = (resx - desw * scale) / 2
|
||||
local yshift = (resy - desh * scale) / 2
|
||||
|
||||
gfx.Translate(xshift, yshift)
|
||||
--gfx.Scale(scale, scale)
|
||||
|
||||
songData:render(deltaTime)
|
||||
songTable:render(deltaTime)
|
||||
|
||||
--if totalForce then
|
||||
--local forceText = string.format("%.2f", totalForce)
|
||||
-- gfx.SetImageTint(255, 254, 2)
|
||||
--bpmFont:draw(forceText, 140, 353, 1, gfx.TEXT_ALIGN_LEFT, gfx.TEXT_ALIGN_MIDDLE)
|
||||
--end
|
||||
|
||||
-- Draw the search status
|
||||
if songwheel.searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.LoadSkinFont("segoeui.ttf")
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(20)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
|
||||
gfx.Text(songwheel.searchStatus, 3, desh)
|
||||
end
|
||||
|
||||
soffset = soffset * 0.8
|
||||
draw_search(120, 5, 600, 40)
|
||||
end
|
||||
|
||||
-- Callback
|
||||
set_index = function(newIndex)
|
||||
songData:set_index(newIndex)
|
||||
songTable:set_index(newIndex)
|
||||
end
|
||||
|
||||
-- Callback
|
||||
set_diff = function(newDiff)
|
||||
songData:set_difficulty(newDiff)
|
||||
songTable:set_difficulty(newDiff)
|
||||
end
|
||||
|
||||
-- force calculation
|
||||
--------------------
|
||||
totalForce = nil
|
||||
|
||||
local badgeRates = {
|
||||
0.5, -- Played
|
||||
1.0, -- Cleared
|
||||
1.02, -- Hard clear
|
||||
1.04, -- UC
|
||||
1.1 -- PUC
|
||||
}
|
||||
|
||||
local gradeRates = {
|
||||
{["min"] = 9900000, ["rate"] = 1.05}, -- S
|
||||
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
|
||||
{["min"] = 9700000, ["rate"] = 1}, -- AAA
|
||||
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
|
||||
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
|
||||
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
|
||||
{["min"] = 8700000, ["rate"] = 0.88}, -- A
|
||||
{["min"] = 7500000, ["rate"] = 0.85}, -- B
|
||||
{["min"] = 6500000, ["rate"] = 0.82}, -- C
|
||||
{["min"] = 0, ["rate"] = 0.8} -- D
|
||||
}
|
||||
|
||||
calculate_force = function(diff)
|
||||
if #diff.scores < 1 then
|
||||
return 0
|
||||
end
|
||||
local score = diff.scores[1]
|
||||
local badgeRate = badgeRates[diff.topBadge]
|
||||
local gradeRate
|
||||
for i, v in ipairs(gradeRates) do
|
||||
if score.score >= v.min then
|
||||
gradeRate = v.rate
|
||||
break
|
||||
end
|
||||
end
|
||||
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
|
||||
end
|
||||
|
||||
-- callback
|
||||
songs_changed = function(withAll)
|
||||
if (not withAll) then return end
|
||||
local diffsById = {}
|
||||
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 = calculate_force(diff)
|
||||
table.insert(diffs, diff)
|
||||
diffsById[diff.id] = 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
|
||||
totalForce = totalForce + diffs[i].force
|
||||
diffs[i].forceInTotal = true
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #songwheel.songs do
|
||||
local song = songwheel.songs[i]
|
||||
for j = 1, #song.difficulties do
|
||||
local diff = song.difficulties[j]
|
||||
local newDiff = diffsById[diff.id]
|
||||
song.difficulties[j] = newDiff
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,894 @@
|
|||
--Horizontal alignment
|
||||
TEXT_ALIGN_LEFT = 1
|
||||
TEXT_ALIGN_CENTER = 2
|
||||
TEXT_ALIGN_RIGHT = 4
|
||||
--Vertical alignment
|
||||
TEXT_ALIGN_TOP = 8
|
||||
TEXT_ALIGN_MIDDLE = 16
|
||||
TEXT_ALIGN_BOTTOM = 32
|
||||
TEXT_ALIGN_BASELINE = 64
|
||||
|
||||
local jacket = nil;
|
||||
local selectedIndex = 1
|
||||
local selectedDiff = 1
|
||||
local songCache = {}
|
||||
local ioffset = 0
|
||||
local doffset = 0
|
||||
local soffset = 0
|
||||
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
|
||||
local timer = 0
|
||||
local effector = 0
|
||||
local searchText = gfx.CreateLabel("",5,0)
|
||||
local searchIndex = 1
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local showGuide = game.GetSkinSetting("show_guide")
|
||||
local legendTable = {
|
||||
{["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
|
||||
local badges = {
|
||||
gfx.CreateSkinImage("badges/played.png", 0),
|
||||
gfx.CreateSkinImage("badges/clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/hard-clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/full-combo.png", 0),
|
||||
gfx.CreateSkinImage("badges/perfect.png", 0)
|
||||
}
|
||||
|
||||
local foreground = gfx.CreateSkinImage("song_select/fg.png", 0);
|
||||
|
||||
local recordCache = {}
|
||||
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
|
||||
game.LoadSkinSample("menu_click")
|
||||
game.LoadSkinSample("click-02")
|
||||
game.LoadSkinSample("woosh")
|
||||
|
||||
local wheelSize = 12
|
||||
|
||||
get_page_size = function()
|
||||
return math.floor(wheelSize/2)
|
||||
end
|
||||
|
||||
-- Responsive UI variables
|
||||
-- Aspect Ratios
|
||||
local aspectFloat = 1.850
|
||||
local aspectRatio = "widescreen"
|
||||
local landscapeWidescreenRatio = 1.850
|
||||
local landscapeStandardRatio = 1.500
|
||||
local portraitWidescreenRatio = 0.5
|
||||
|
||||
-- Responsive sizes
|
||||
local fifthX = 0
|
||||
local fourthX= 0
|
||||
local thirdX = 0
|
||||
local halfX = 0
|
||||
local fullX = 0
|
||||
|
||||
local fifthY = 0
|
||||
local fourthY= 0
|
||||
local thirdY = 0
|
||||
local halfY = 0
|
||||
local fullY = 0
|
||||
|
||||
|
||||
adjustScreen = function(x,y)
|
||||
local a = x/y;
|
||||
if x >= y and a <= landscapeStandardRatio then
|
||||
aspectRatio = "landscapeStandard"
|
||||
aspectFloat = 1.1
|
||||
elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.2
|
||||
elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then
|
||||
aspectRatio = "PortraitWidescreen"
|
||||
aspectFloat = 0.5
|
||||
else
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.0
|
||||
end
|
||||
fifthX = x/5
|
||||
fourthX= x/4
|
||||
thirdX = x/3
|
||||
halfX = x/2
|
||||
fullX = x
|
||||
|
||||
fifthY = y/5
|
||||
fourthY= y/4
|
||||
thirdY = y/3
|
||||
halfY = y/2
|
||||
fullY = y
|
||||
end
|
||||
|
||||
|
||||
check_or_create_cache = function(song, loadJacket)
|
||||
if not songCache[song.id] then songCache[song.id] = {} end
|
||||
|
||||
if not songCache[song.id]["title"] then
|
||||
songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["artist"] then
|
||||
songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["bpm"] then
|
||||
songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["effector"] then
|
||||
songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["jacket"] then
|
||||
songCache[song.id]["jacket"] = { }
|
||||
end
|
||||
|
||||
for i = 1, #song.difficulties do
|
||||
songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 200, 200)
|
||||
end
|
||||
end
|
||||
|
||||
function record_handler_factory(hash)
|
||||
return (function(res)
|
||||
if res.statusCode == 42 then
|
||||
recordCache[hash] = {good=false, reason="Untracked"}
|
||||
elseif res.statusCode == 20 and res.body ~= nil then
|
||||
recordCache[hash] = {good=true, record=res.body.record}
|
||||
elseif res.statusCode == 44 then
|
||||
recordCache[hash] = {good=true, record=nil}
|
||||
else
|
||||
recordCache[hash] = {good=false, reason="Failed"}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function get_record(hash)
|
||||
if recordCache[hash] then return recordCache[hash] end
|
||||
|
||||
recordCache[hash] = {good=false, reason="Loading..."}
|
||||
|
||||
IR.Record(hash, record_handler_factory(hash))
|
||||
|
||||
return recordCache[hash]
|
||||
end
|
||||
|
||||
function log_table(table)
|
||||
str = "{"
|
||||
for k, v in pairs(table) do
|
||||
str = str .. k .. ": "
|
||||
|
||||
t = type(v)
|
||||
|
||||
if t == "table" then
|
||||
str = str .. log_table(v)
|
||||
elseif t == "string" then
|
||||
str = str .. "\"" .. v .. "\""
|
||||
elseif t == "boolean" then
|
||||
if v then
|
||||
str = str .. "true"
|
||||
else
|
||||
str = str .. "false"
|
||||
end
|
||||
else
|
||||
str = str .. v
|
||||
end
|
||||
|
||||
str = str .. ", "
|
||||
end
|
||||
|
||||
return str .. "}"
|
||||
end
|
||||
|
||||
draw_scores_ir = function(difficulty, x, y, w, h)
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
|
||||
gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2))
|
||||
gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2)
|
||||
end
|
||||
|
||||
irRecord = get_record(difficulty.hash)
|
||||
|
||||
if not irRecord.good then
|
||||
recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0)
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
elseif irRecord.record == nil then --record not set, but can be tracked
|
||||
recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0)
|
||||
gfx.FillColor(170, 170, 170)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
else
|
||||
|
||||
recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0)
|
||||
recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0)
|
||||
|
||||
if irRecord.record.lamp ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0)
|
||||
end
|
||||
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > irRecord.record.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2)
|
||||
gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_scores = function(difficulty, x, y, w, h)
|
||||
if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end
|
||||
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
--gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
--gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_song = function(song, x, y, w, h, selected)
|
||||
local diffIndex = math.min(selectedDiff, #song.difficulties)
|
||||
local difficulty = song.difficulties[diffIndex]
|
||||
local clearLampR = 255
|
||||
local clearLampG = 255
|
||||
local clearLampB = 255
|
||||
local clearLampA = 100
|
||||
|
||||
if difficulty ~= nil then
|
||||
if difficulty.scores[1] ~= nil then
|
||||
if difficulty.topBadge == 1 then -- fail/played
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 2 then -- clear
|
||||
clearLampR = 25
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 3 then -- hard clear
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 255
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 4 then -- full combo
|
||||
clearLampR = 255
|
||||
clearLampG = 100
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 5 then -- perfect
|
||||
clearLampR = 255
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
check_or_create_cache(song)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1,y+1, w-2, h-2)
|
||||
gfx.FillColor(220,220,220)
|
||||
gfx.StrokeColor(0,8,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
if songCache[song.id]["jacket"][diffIndex] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 1, 0)
|
||||
end
|
||||
|
||||
-- Song title
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1)
|
||||
--gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10)
|
||||
|
||||
-- Song difficulty
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10)
|
||||
else
|
||||
gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10)
|
||||
end
|
||||
|
||||
|
||||
-- CLEAN THIS SHIT UP
|
||||
local diff_long = ""
|
||||
local diff_short = ""
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
if (song.difficulties[selectedDiff].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
else
|
||||
if (song.difficulties[selectedDiff - 1].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FontSize(8)
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FastText(diff_long, x + h/4, y + h - 7)
|
||||
|
||||
local seldiff = nil
|
||||
if song.difficulties[selectedDiff] ~= nil then
|
||||
seldiff = selectedDiff
|
||||
else
|
||||
seldiff = selectedDiff - 1
|
||||
end
|
||||
|
||||
if song.difficulties[seldiff].topBadge ~= 0 then
|
||||
if song.difficulties[seldiff].scores[1] ~= nil then
|
||||
local highScore = song.difficulties[seldiff].scores[1]
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
draw_diff_icon = function(diff, x, y, w, h, selected)
|
||||
local shrinkX = w/4
|
||||
local shrinkY = h/4
|
||||
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.FontSize(28)
|
||||
gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2))
|
||||
end
|
||||
|
||||
draw_cursor = function(x,y,rotation,width)
|
||||
gfx.Save()
|
||||
gfx.BeginPath();
|
||||
gfx.Translate(x,y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(4)
|
||||
gfx.Rect(-width/2, -width/2, width, width)
|
||||
gfx.Stroke()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
draw_diffs = function(diffs, x, y, w, h)
|
||||
local diffWidth = w/2.5
|
||||
local diffHeight = w/2.5
|
||||
local diffCount = #diffs
|
||||
local diffSpacingOffset = (diffWidth*0.82)*(selectedDiff-1)
|
||||
for i = math.max(selectedDiff - 3, 1), math.max(selectedDiff - 1,1) do
|
||||
local diff = diffs[i]
|
||||
local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) - ((diffWidth*0.82)*(i-selectedDiff+1))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedDiff + 3, diffCount), selectedDiff + 1,-1 do
|
||||
local diff = diffs[i]
|
||||
local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) + ((diffWidth*0.82)*(i-1))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
local diff = diffs[selectedDiff]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))
|
||||
draw_diff_icon(diff, (xpos*0.9)+diffSpacingOffset, y, diffWidth, diffHeight, true)
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,128,255)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.Fill()
|
||||
gfx.ResetScissor()
|
||||
draw_cursor((x + (w/5.7))*(selectedDiff^1.085), y +diffHeight/2, timer * math.pi, diffHeight / 1.5)
|
||||
end
|
||||
|
||||
draw_selected = function(song, x, y, w, h)
|
||||
check_or_create_cache(song)
|
||||
-- set up padding and margins
|
||||
local xPadding = math.floor(w/16)
|
||||
local yPadding = math.floor(h/32)
|
||||
local xMargin = math.floor(w/16)
|
||||
local yMargin = math.floor(h/32)
|
||||
local width = (w-(xMargin*2))
|
||||
local height = (h-(yMargin*2))
|
||||
local xpos = x+xMargin
|
||||
local ypos = y+yMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
xPadding = math.floor(w/32)
|
||||
yPadding = math.floor(h/32)
|
||||
xMargin = math.floor(w/34)
|
||||
yMargin = math.floor(h/32)
|
||||
width = ((w/2)-(xMargin))
|
||||
height = (h-(yMargin*2))
|
||||
xpos = x+xMargin/2
|
||||
ypos = y+yMargin
|
||||
end
|
||||
--Border
|
||||
local diff = song.difficulties[selectedDiff]
|
||||
gfx.BeginPath()
|
||||
--gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding)
|
||||
gfx.FillColor(30,30,30,100)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
-- jacket should take up 1/3 of height, always be square, and be centered
|
||||
local imageSize = math.floor(height/3)
|
||||
local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--Unless its portrait widesreen..
|
||||
imageSize = math.floor((height/8)*1.58)
|
||||
imageXPos = (x+w)/16+(xMargin*0.8)
|
||||
end
|
||||
if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then
|
||||
songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200)
|
||||
end
|
||||
|
||||
if songCache[song.id][selectedDiff] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(imageXPos, y+yMargin*4.45+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0)
|
||||
end
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--difficulty wheel should be right below the jacketImage, and the same width as
|
||||
--the jacketImage
|
||||
draw_diffs(song.difficulties,xpos+xPadding/1.5,(ypos*10.3+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding)
|
||||
else
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6))
|
||||
end
|
||||
-- effector / bpm should take up 1/3 of height, full width
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding/2, y+yMargin*15+yPadding, width)
|
||||
gfx.FontSize(40)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width)
|
||||
gfx.FontSize(10)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize)
|
||||
gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding)
|
||||
else
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20)
|
||||
gfx.FontSize(30)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85)
|
||||
gfx.FastText(string.format("%s", diff.effector),xpos+10, (height/10)*6 + 115)
|
||||
end
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding)
|
||||
else
|
||||
draw_scores(diff, xpos, (height/6)*5, width, (height/6))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_songwheel = function(x,y,w,h)
|
||||
local offsetX = fifthX/2
|
||||
local width = math.floor((w/5)*4)
|
||||
if aspectRatio == "landscapeWidescreen" then
|
||||
wheelSize = 12
|
||||
offsetX = 80
|
||||
elseif aspectRatio == "landscapeStandard" then
|
||||
wheelSize = 10
|
||||
offsetX = 40
|
||||
elseif aspectRatio == "PortraitWidescreen" then
|
||||
wheelSize = 20
|
||||
offsetX = 20
|
||||
width = w/2
|
||||
end
|
||||
local height = math.floor((h/wheelSize)*1.75)
|
||||
|
||||
for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - offsetY)
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY)
|
||||
local alpha = 255 - (selectedIndex - i + ioffset) * 31
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
-- draw selected
|
||||
local xpos = x + width
|
||||
local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat)))
|
||||
local ypos = y+((h/2 - height/2) - (ioffset) - offsetY)
|
||||
draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true)
|
||||
-- cursor
|
||||
gfx.BeginPath()
|
||||
local ypos = y+((h/2 - height/2))
|
||||
gfx.Rect(xpos, ypos, width, height)
|
||||
gfx.FillColor(0,0,0,0)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
return songwheel.songs[selectedIndex]
|
||||
end
|
||||
draw_legend_pane = function(x,y,w,h,obj)
|
||||
local xpos = x+5
|
||||
local ypos = y
|
||||
local imageSize = h
|
||||
gfx.BeginPath()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0)
|
||||
xpos = xpos + imageSize + 5
|
||||
gfx.FontSize(16);
|
||||
if h < (w-(10+imageSize))/2 then
|
||||
gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
else
|
||||
gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_legend = function(x,y,w,h)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,170)
|
||||
gfx.Rect(x,y,w,h)
|
||||
gfx.Fill()
|
||||
local xpos = 10;
|
||||
local legendWidth = math.floor((w-20)/#legendTable)
|
||||
for i,v in ipairs(legendTable) do
|
||||
local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i])
|
||||
end
|
||||
end
|
||||
|
||||
draw_search = function(x,y,w,h)
|
||||
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
|
||||
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
|
||||
game.PlaySample("woosh")
|
||||
end
|
||||
searchIndex = songwheel.searchInputActive and 0 or 1
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgfade = 1 - (searchIndex + soffset)
|
||||
--if not songwheel.searchInputActive then bgfade = soffset end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.ForceRender()
|
||||
local xpos = x + (searchIndex + soffset)*w
|
||||
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRect(xpos,y,w,h,h/2)
|
||||
gfx.FillColor(30,30,30)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
|
||||
|
||||
end
|
||||
|
||||
render = function(deltaTime)
|
||||
timer = (timer + deltaTime)
|
||||
timer = timer % 2
|
||||
resx,resy = game.GetResolution();
|
||||
adjustScreen(resx,resy);
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
gfx.FontSize(40);
|
||||
gfx.FillColor(255,255,255);
|
||||
if songwheel.songs[1] ~= nil then
|
||||
--draw songwheel and get selected song
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
local song = draw_songwheel(0,0,fullX,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fullX,resy)
|
||||
else
|
||||
local song = draw_songwheel(fifthX*2,0,fifthX*3,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fifthX*2,(fifthY/2)*9)
|
||||
end
|
||||
end
|
||||
--Draw Legend Information
|
||||
-- if showGuide then
|
||||
-- if aspectRatio == "PortraitWidescreen" then
|
||||
-- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1)
|
||||
-- else
|
||||
-- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2))
|
||||
-- end
|
||||
-- end
|
||||
gfx.BeginPath();
|
||||
gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE);
|
||||
gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0);
|
||||
|
||||
--draw text search
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/5)
|
||||
else
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/3)
|
||||
end
|
||||
|
||||
ioffset = ioffset * 0.9
|
||||
doffset = doffset * 0.9
|
||||
soffset = soffset * 0.8
|
||||
if songwheel.searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(songwheel.searchStatus, 3, 3)
|
||||
end
|
||||
if totalForce then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
|
||||
local forceText = string.format("Force: %.2f", totalForce)
|
||||
gfx.Text(forceText, 0, fullY)
|
||||
end
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.ResetTransform()
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
set_index = function(newIndex)
|
||||
if newIndex ~= selectedIndex then
|
||||
game.PlaySample("menu_click")
|
||||
end
|
||||
ioffset = ioffset + selectedIndex - newIndex
|
||||
selectedIndex = newIndex
|
||||
end;
|
||||
|
||||
set_diff = function(newDiff)
|
||||
if newDiff ~= selectedDiff then
|
||||
game.PlaySample("click-02")
|
||||
end
|
||||
doffset = doffset + selectedDiff - newDiff
|
||||
selectedDiff = newDiff
|
||||
end;
|
||||
|
||||
-- force calculation
|
||||
--------------------
|
||||
totalForce = nil
|
||||
|
||||
local badgeRates = {
|
||||
0.5, -- Played
|
||||
1.0, -- Cleared
|
||||
1.02, -- Hard clear
|
||||
1.04, -- UC
|
||||
1.1 -- PUC
|
||||
}
|
||||
|
||||
local gradeRates = {
|
||||
{["min"] = 9900000, ["rate"] = 1.05}, -- S
|
||||
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
|
||||
{["min"] = 9700000, ["rate"] = 1}, -- AAA
|
||||
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
|
||||
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
|
||||
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
|
||||
{["min"] = 8700000, ["rate"] = 0.88}, -- A
|
||||
{["min"] = 7500000, ["rate"] = 0.85}, -- B
|
||||
{["min"] = 6500000, ["rate"] = 0.82}, -- C
|
||||
{["min"] = 0, ["rate"] = 0.8} -- D
|
||||
}
|
||||
|
||||
calculate_force = function(diff)
|
||||
if #diff.scores < 1 then
|
||||
return 0
|
||||
end
|
||||
local score = diff.scores[1]
|
||||
local badgeRate = badgeRates[diff.topBadge]
|
||||
local gradeRate
|
||||
for i, v in ipairs(gradeRates) do
|
||||
if score.score >= v.min then
|
||||
gradeRate = v.rate
|
||||
break
|
||||
end
|
||||
end
|
||||
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
|
||||
end
|
||||
|
||||
songs_changed = function(withAll)
|
||||
if not withAll then return end
|
||||
|
||||
recordCache = {}
|
||||
|
||||
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 = calculate_force(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
|
||||
totalForce = totalForce + diffs[i].force
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,894 @@
|
|||
--Horizontal alignment
|
||||
TEXT_ALIGN_LEFT = 1
|
||||
TEXT_ALIGN_CENTER = 2
|
||||
TEXT_ALIGN_RIGHT = 4
|
||||
--Vertical alignment
|
||||
TEXT_ALIGN_TOP = 8
|
||||
TEXT_ALIGN_MIDDLE = 16
|
||||
TEXT_ALIGN_BOTTOM = 32
|
||||
TEXT_ALIGN_BASELINE = 64
|
||||
|
||||
local jacket = nil;
|
||||
local selectedIndex = 1
|
||||
local selectedDiff = 1
|
||||
local songCache = {}
|
||||
local ioffset = 0
|
||||
local doffset = 0
|
||||
local soffset = 0
|
||||
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
|
||||
local timer = 0
|
||||
local effector = 0
|
||||
local searchText = gfx.CreateLabel("",5,0)
|
||||
local searchIndex = 1
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local showGuide = game.GetSkinSetting("show_guide")
|
||||
local legendTable = {
|
||||
{["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
|
||||
local badges = {
|
||||
gfx.CreateSkinImage("badges/played.png", 0),
|
||||
gfx.CreateSkinImage("badges/clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/hard-clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/full-combo.png", 0),
|
||||
gfx.CreateSkinImage("badges/perfect.png", 0)
|
||||
}
|
||||
|
||||
local foreground = gfx.CreateSkinImage("song_select/fg.png", 0);
|
||||
|
||||
local recordCache = {}
|
||||
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
|
||||
game.LoadSkinSample("menu_click")
|
||||
game.LoadSkinSample("click-02")
|
||||
game.LoadSkinSample("woosh")
|
||||
|
||||
local wheelSize = 12
|
||||
|
||||
get_page_size = function()
|
||||
return math.floor(wheelSize/2)
|
||||
end
|
||||
|
||||
-- Responsive UI variables
|
||||
-- Aspect Ratios
|
||||
local aspectFloat = 1.850
|
||||
local aspectRatio = "widescreen"
|
||||
local landscapeWidescreenRatio = 1.850
|
||||
local landscapeStandardRatio = 1.500
|
||||
local portraitWidescreenRatio = 0.5
|
||||
|
||||
-- Responsive sizes
|
||||
local fifthX = 0
|
||||
local fourthX= 0
|
||||
local thirdX = 0
|
||||
local halfX = 0
|
||||
local fullX = 0
|
||||
|
||||
local fifthY = 0
|
||||
local fourthY= 0
|
||||
local thirdY = 0
|
||||
local halfY = 0
|
||||
local fullY = 0
|
||||
|
||||
|
||||
adjustScreen = function(x,y)
|
||||
local a = x/y;
|
||||
if x >= y and a <= landscapeStandardRatio then
|
||||
aspectRatio = "landscapeStandard"
|
||||
aspectFloat = 1.1
|
||||
elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.2
|
||||
elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then
|
||||
aspectRatio = "PortraitWidescreen"
|
||||
aspectFloat = 0.5
|
||||
else
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.0
|
||||
end
|
||||
fifthX = x/5
|
||||
fourthX= x/4
|
||||
thirdX = x/3
|
||||
halfX = x/2
|
||||
fullX = x
|
||||
|
||||
fifthY = y/5
|
||||
fourthY= y/4
|
||||
thirdY = y/3
|
||||
halfY = y/2
|
||||
fullY = y
|
||||
end
|
||||
|
||||
|
||||
check_or_create_cache = function(song, loadJacket)
|
||||
if not songCache[song.id] then songCache[song.id] = {} end
|
||||
|
||||
if not songCache[song.id]["title"] then
|
||||
songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["artist"] then
|
||||
songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["bpm"] then
|
||||
songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["effector"] then
|
||||
songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["jacket"] then
|
||||
songCache[song.id]["jacket"] = { }
|
||||
end
|
||||
|
||||
for i = 1, #song.difficulties do
|
||||
songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 200, 200)
|
||||
end
|
||||
end
|
||||
|
||||
function record_handler_factory(hash)
|
||||
return (function(res)
|
||||
if res.statusCode == 42 then
|
||||
recordCache[hash] = {good=false, reason="Untracked"}
|
||||
elseif res.statusCode == 20 and res.body ~= nil then
|
||||
recordCache[hash] = {good=true, record=res.body.record}
|
||||
elseif res.statusCode == 44 then
|
||||
recordCache[hash] = {good=true, record=nil}
|
||||
else
|
||||
recordCache[hash] = {good=false, reason="Failed"}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function get_record(hash)
|
||||
if recordCache[hash] then return recordCache[hash] end
|
||||
|
||||
recordCache[hash] = {good=false, reason="Loading..."}
|
||||
|
||||
IR.Record(hash, record_handler_factory(hash))
|
||||
|
||||
return recordCache[hash]
|
||||
end
|
||||
|
||||
function log_table(table)
|
||||
str = "{"
|
||||
for k, v in pairs(table) do
|
||||
str = str .. k .. ": "
|
||||
|
||||
t = type(v)
|
||||
|
||||
if t == "table" then
|
||||
str = str .. log_table(v)
|
||||
elseif t == "string" then
|
||||
str = str .. "\"" .. v .. "\""
|
||||
elseif t == "boolean" then
|
||||
if v then
|
||||
str = str .. "true"
|
||||
else
|
||||
str = str .. "false"
|
||||
end
|
||||
else
|
||||
str = str .. v
|
||||
end
|
||||
|
||||
str = str .. ", "
|
||||
end
|
||||
|
||||
return str .. "}"
|
||||
end
|
||||
|
||||
draw_scores_ir = function(difficulty, x, y, w, h)
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
|
||||
gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2))
|
||||
gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2)
|
||||
end
|
||||
|
||||
irRecord = get_record(difficulty.hash)
|
||||
|
||||
if not irRecord.good then
|
||||
recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0)
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
elseif irRecord.record == nil then --record not set, but can be tracked
|
||||
recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0)
|
||||
gfx.FillColor(170, 170, 170)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
else
|
||||
|
||||
recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0)
|
||||
recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0)
|
||||
|
||||
if irRecord.record.lamp ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0)
|
||||
end
|
||||
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > irRecord.record.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2)
|
||||
gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_scores = function(difficulty, x, y, w, h)
|
||||
if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end
|
||||
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
--gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
--gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_song = function(song, x, y, w, h, selected)
|
||||
local diffIndex = math.min(selectedDiff, #song.difficulties)
|
||||
local difficulty = song.difficulties[diffIndex]
|
||||
local clearLampR = 255
|
||||
local clearLampG = 255
|
||||
local clearLampB = 255
|
||||
local clearLampA = 100
|
||||
|
||||
if difficulty ~= nil then
|
||||
if difficulty.scores[1] ~= nil then
|
||||
if difficulty.topBadge == 1 then -- fail/played
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 2 then -- clear
|
||||
clearLampR = 25
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 3 then -- hard clear
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 255
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 4 then -- full combo
|
||||
clearLampR = 255
|
||||
clearLampG = 100
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 5 then -- perfect
|
||||
clearLampR = 255
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
check_or_create_cache(song)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1,y+1, w-2, h-2)
|
||||
gfx.FillColor(220,220,220)
|
||||
gfx.StrokeColor(0,8,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
if songCache[song.id]["jacket"][diffIndex] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 1, 0)
|
||||
end
|
||||
|
||||
-- Song title
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1)
|
||||
--gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10)
|
||||
|
||||
-- Song difficulty
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10)
|
||||
else
|
||||
gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10)
|
||||
end
|
||||
|
||||
|
||||
-- CLEAN THIS SHIT UP
|
||||
local diff_long = ""
|
||||
local diff_short = ""
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
if (song.difficulties[selectedDiff].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
else
|
||||
if (song.difficulties[selectedDiff - 1].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FontSize(8)
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FastText(diff_long, x + h/4, y + h - 7)
|
||||
|
||||
local seldiff = nil
|
||||
if song.difficulties[selectedDiff] ~= nil then
|
||||
seldiff = selectedDiff
|
||||
else
|
||||
seldiff = selectedDiff - 1
|
||||
end
|
||||
|
||||
if song.difficulties[seldiff].topBadge ~= 0 then
|
||||
if song.difficulties[seldiff].scores[1] ~= nil then
|
||||
local highScore = song.difficulties[seldiff].scores[1]
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
draw_diff_icon = function(diff, x, y, w, h, selected)
|
||||
local shrinkX = w/4
|
||||
local shrinkY = h/4
|
||||
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.FontSize(28)
|
||||
gfx.FastText(tostring(diff.level), x+(w/2),y+(h/2))
|
||||
end
|
||||
|
||||
draw_cursor = function(x,y,rotation,width)
|
||||
gfx.Save()
|
||||
gfx.BeginPath();
|
||||
gfx.Translate(x,y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(4)
|
||||
gfx.Rect(-width/2, -width/2, width, width)
|
||||
gfx.Stroke()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
draw_diffs = function(diffs, x, y, w, h)
|
||||
local diffWidth = w/2.5
|
||||
local diffHeight = w/2.5
|
||||
local diffCount = #diffs
|
||||
local diffSpacingOffset = (diffWidth*0.82)*(selectedDiff-1)
|
||||
for i = math.max(selectedDiff - 3, 1), math.max(selectedDiff - 1,1) do
|
||||
local diff = diffs[i]
|
||||
local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) - ((diffWidth*0.82)*(i-selectedDiff+1))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedDiff + 3, diffCount), selectedDiff + 1,-1 do
|
||||
local diff = diffs[i]
|
||||
local xpos = (x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))) + ((diffWidth*0.82)*(i-1))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
local diff = diffs[selectedDiff]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (-0.8*diffWidth))
|
||||
draw_diff_icon(diff, (xpos*0.9)+diffSpacingOffset, y, diffWidth, diffHeight, true)
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,128,255)
|
||||
gfx.Fill()
|
||||
gfx.BeginPath()
|
||||
gfx.Fill()
|
||||
gfx.ResetScissor()
|
||||
draw_cursor((x + (w/5.7))*(selectedDiff^1.085), y +diffHeight/2, timer * math.pi, diffHeight / 1.5)
|
||||
end
|
||||
|
||||
draw_selected = function(song, x, y, w, h)
|
||||
check_or_create_cache(song)
|
||||
-- set up padding and margins
|
||||
local xPadding = math.floor(w/16)
|
||||
local yPadding = math.floor(h/32)
|
||||
local xMargin = math.floor(w/16)
|
||||
local yMargin = math.floor(h/32)
|
||||
local width = (w-(xMargin*2))
|
||||
local height = (h-(yMargin*2))
|
||||
local xpos = x+xMargin
|
||||
local ypos = y+yMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
xPadding = math.floor(w/32)
|
||||
yPadding = math.floor(h/32)
|
||||
xMargin = math.floor(w/34)
|
||||
yMargin = math.floor(h/32)
|
||||
width = ((w/2)-(xMargin))
|
||||
height = (h-(yMargin*2))
|
||||
xpos = x+xMargin/2
|
||||
ypos = y+yMargin
|
||||
end
|
||||
--Border
|
||||
local diff = song.difficulties[selectedDiff]
|
||||
gfx.BeginPath()
|
||||
--gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding)
|
||||
gfx.FillColor(30,30,30,100)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
-- jacket should take up 1/3 of height, always be square, and be centered
|
||||
local imageSize = math.floor(height/3)
|
||||
local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--Unless its portrait widesreen..
|
||||
imageSize = math.floor((height/8)*1.58)
|
||||
imageXPos = (x+w)/16+(xMargin*0.8)
|
||||
end
|
||||
if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then
|
||||
songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200)
|
||||
end
|
||||
|
||||
if songCache[song.id][selectedDiff] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(imageXPos, y+yMargin*4.45+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0)
|
||||
end
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--difficulty wheel should be right below the jacketImage, and the same width as
|
||||
--the jacketImage
|
||||
draw_diffs(song.difficulties,xpos+xPadding/1.5,(ypos*10.3+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding)
|
||||
else
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6))
|
||||
end
|
||||
-- effector / bpm should take up 1/3 of height, full width
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding/2, y+yMargin*15+yPadding, width)
|
||||
gfx.FontSize(40)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width)
|
||||
gfx.FontSize(10)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize)
|
||||
gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding)
|
||||
else
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20)
|
||||
gfx.FontSize(30)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85)
|
||||
gfx.FastText(string.format("%s", diff.effector),xpos+10, (height/10)*6 + 115)
|
||||
end
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding)
|
||||
else
|
||||
draw_scores(diff, xpos, (height/6)*5, width, (height/6))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_songwheel = function(x,y,w,h)
|
||||
local offsetX = fifthX/2
|
||||
local width = math.floor((w/5)*4)
|
||||
if aspectRatio == "landscapeWidescreen" then
|
||||
wheelSize = 12
|
||||
offsetX = 80
|
||||
elseif aspectRatio == "landscapeStandard" then
|
||||
wheelSize = 10
|
||||
offsetX = 40
|
||||
elseif aspectRatio == "PortraitWidescreen" then
|
||||
wheelSize = 20
|
||||
offsetX = 20
|
||||
width = w/2
|
||||
end
|
||||
local height = math.floor((h/wheelSize)*1.75)
|
||||
|
||||
for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - offsetY)
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY)
|
||||
local alpha = 255 - (selectedIndex - i + ioffset) * 31
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
-- draw selected
|
||||
local xpos = x + width
|
||||
local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat)))
|
||||
local ypos = y+((h/2 - height/2) - (ioffset) - offsetY)
|
||||
draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true)
|
||||
-- cursor
|
||||
gfx.BeginPath()
|
||||
local ypos = y+((h/2 - height/2))
|
||||
gfx.Rect(xpos, ypos, width, height)
|
||||
gfx.FillColor(0,0,0,0)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
return songwheel.songs[selectedIndex]
|
||||
end
|
||||
draw_legend_pane = function(x,y,w,h,obj)
|
||||
local xpos = x+5
|
||||
local ypos = y
|
||||
local imageSize = h
|
||||
gfx.BeginPath()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0)
|
||||
xpos = xpos + imageSize + 5
|
||||
gfx.FontSize(16);
|
||||
if h < (w-(10+imageSize))/2 then
|
||||
gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
else
|
||||
gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_legend = function(x,y,w,h)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,170)
|
||||
gfx.Rect(x,y,w,h)
|
||||
gfx.Fill()
|
||||
local xpos = 10;
|
||||
local legendWidth = math.floor((w-20)/#legendTable)
|
||||
for i,v in ipairs(legendTable) do
|
||||
local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i])
|
||||
end
|
||||
end
|
||||
|
||||
draw_search = function(x,y,w,h)
|
||||
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
|
||||
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
|
||||
game.PlaySample("woosh")
|
||||
end
|
||||
searchIndex = songwheel.searchInputActive and 0 or 1
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgfade = 1 - (searchIndex + soffset)
|
||||
--if not songwheel.searchInputActive then bgfade = soffset end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.ForceRender()
|
||||
local xpos = x + (searchIndex + soffset)*w
|
||||
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRect(xpos,y,w,h,h/2)
|
||||
gfx.FillColor(30,30,30)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
|
||||
|
||||
end
|
||||
|
||||
render = function(deltaTime)
|
||||
timer = (timer + deltaTime)
|
||||
timer = timer % 2
|
||||
resx,resy = game.GetResolution();
|
||||
adjustScreen(resx,resy);
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
gfx.FontSize(40);
|
||||
gfx.FillColor(255,255,255);
|
||||
if songwheel.songs[1] ~= nil then
|
||||
--draw songwheel and get selected song
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
local song = draw_songwheel(0,0,fullX,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fullX,resy)
|
||||
else
|
||||
local song = draw_songwheel(fifthX*2,0,fifthX*3,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fifthX*2,(fifthY/2)*9)
|
||||
end
|
||||
end
|
||||
--Draw Legend Information
|
||||
-- if showGuide then
|
||||
-- if aspectRatio == "PortraitWidescreen" then
|
||||
-- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1)
|
||||
-- else
|
||||
-- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2))
|
||||
-- end
|
||||
-- end
|
||||
gfx.BeginPath();
|
||||
gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE);
|
||||
gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0);
|
||||
|
||||
--draw text search
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/5)
|
||||
else
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/3)
|
||||
end
|
||||
|
||||
ioffset = ioffset * 0.9
|
||||
doffset = doffset * 0.9
|
||||
soffset = soffset * 0.8
|
||||
if songwheel.searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(songwheel.searchStatus, 3, 3)
|
||||
end
|
||||
if totalForce then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
|
||||
local forceText = string.format("Force: %.2f", totalForce)
|
||||
gfx.Text(forceText, 0, fullY)
|
||||
end
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.ResetTransform()
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
set_index = function(newIndex)
|
||||
if newIndex ~= selectedIndex then
|
||||
game.PlaySample("menu_click")
|
||||
end
|
||||
ioffset = ioffset + selectedIndex - newIndex
|
||||
selectedIndex = newIndex
|
||||
end;
|
||||
|
||||
set_diff = function(newDiff)
|
||||
if newDiff ~= selectedDiff then
|
||||
game.PlaySample("click-02")
|
||||
end
|
||||
doffset = doffset + selectedDiff - newDiff
|
||||
selectedDiff = newDiff
|
||||
end;
|
||||
|
||||
-- force calculation
|
||||
--------------------
|
||||
totalForce = nil
|
||||
|
||||
local badgeRates = {
|
||||
0.5, -- Played
|
||||
1.0, -- Cleared
|
||||
1.02, -- Hard clear
|
||||
1.04, -- UC
|
||||
1.1 -- PUC
|
||||
}
|
||||
|
||||
local gradeRates = {
|
||||
{["min"] = 9900000, ["rate"] = 1.05}, -- S
|
||||
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
|
||||
{["min"] = 9700000, ["rate"] = 1}, -- AAA
|
||||
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
|
||||
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
|
||||
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
|
||||
{["min"] = 8700000, ["rate"] = 0.88}, -- A
|
||||
{["min"] = 7500000, ["rate"] = 0.85}, -- B
|
||||
{["min"] = 6500000, ["rate"] = 0.82}, -- C
|
||||
{["min"] = 0, ["rate"] = 0.8} -- D
|
||||
}
|
||||
|
||||
calculate_force = function(diff)
|
||||
if #diff.scores < 1 then
|
||||
return 0
|
||||
end
|
||||
local score = diff.scores[1]
|
||||
local badgeRate = badgeRates[diff.topBadge]
|
||||
local gradeRate
|
||||
for i, v in ipairs(gradeRates) do
|
||||
if score.score >= v.min then
|
||||
gradeRate = v.rate
|
||||
break
|
||||
end
|
||||
end
|
||||
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
|
||||
end
|
||||
|
||||
songs_changed = function(withAll)
|
||||
if not withAll then return end
|
||||
|
||||
recordCache = {}
|
||||
|
||||
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 = calculate_force(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
|
||||
totalForce = totalForce + diffs[i].force
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,897 @@
|
|||
--Horizontal alignment
|
||||
TEXT_ALIGN_LEFT = 1
|
||||
TEXT_ALIGN_CENTER = 2
|
||||
TEXT_ALIGN_RIGHT = 4
|
||||
--Vertical alignment
|
||||
TEXT_ALIGN_TOP = 8
|
||||
TEXT_ALIGN_MIDDLE = 16
|
||||
TEXT_ALIGN_BOTTOM = 32
|
||||
TEXT_ALIGN_BASELINE = 64
|
||||
|
||||
local jacket = nil;
|
||||
local selectedIndex = 1
|
||||
local selectedDiff = 1
|
||||
local songCache = {}
|
||||
local ioffset = 0
|
||||
local doffset = 0
|
||||
local soffset = 0
|
||||
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
|
||||
local timer = 0
|
||||
local effector = 0
|
||||
local searchText = gfx.CreateLabel("",5,0)
|
||||
local searchIndex = 1
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local showGuide = game.GetSkinSetting("show_guide")
|
||||
local legendTable = {
|
||||
{["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
|
||||
local badges = {
|
||||
gfx.CreateSkinImage("badges/played.png", 0),
|
||||
gfx.CreateSkinImage("badges/clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/hard-clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/full-combo.png", 0),
|
||||
gfx.CreateSkinImage("badges/perfect.png", 0)
|
||||
}
|
||||
|
||||
local recordCache = {}
|
||||
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
|
||||
game.LoadSkinSample("menu_click")
|
||||
game.LoadSkinSample("click-02")
|
||||
game.LoadSkinSample("woosh")
|
||||
|
||||
local wheelSize = 12
|
||||
|
||||
get_page_size = function()
|
||||
return math.floor(wheelSize/2)
|
||||
end
|
||||
|
||||
-- Responsive UI variables
|
||||
-- Aspect Ratios
|
||||
local aspectFloat = 1.850
|
||||
local aspectRatio = "widescreen"
|
||||
local landscapeWidescreenRatio = 1.850
|
||||
local landscapeStandardRatio = 1.500
|
||||
local portraitWidescreenRatio = 0.5
|
||||
|
||||
-- Responsive sizes
|
||||
local fifthX = 0
|
||||
local fourthX= 0
|
||||
local thirdX = 0
|
||||
local halfX = 0
|
||||
local fullX = 0
|
||||
|
||||
local fifthY = 0
|
||||
local fourthY= 0
|
||||
local thirdY = 0
|
||||
local halfY = 0
|
||||
local fullY = 0
|
||||
|
||||
|
||||
adjustScreen = function(x,y)
|
||||
local a = x/y;
|
||||
if x >= y and a <= landscapeStandardRatio then
|
||||
aspectRatio = "landscapeStandard"
|
||||
aspectFloat = 1.1
|
||||
elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.2
|
||||
elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then
|
||||
aspectRatio = "PortraitWidescreen"
|
||||
aspectFloat = 0.5
|
||||
else
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.0
|
||||
end
|
||||
fifthX = x/5
|
||||
fourthX= x/4
|
||||
thirdX = x/3
|
||||
halfX = x/2
|
||||
fullX = x
|
||||
|
||||
fifthY = y/5
|
||||
fourthY= y/4
|
||||
thirdY = y/3
|
||||
halfY = y/2
|
||||
fullY = y
|
||||
end
|
||||
|
||||
|
||||
check_or_create_cache = function(song, loadJacket)
|
||||
if not songCache[song.id] then songCache[song.id] = {} end
|
||||
|
||||
if not songCache[song.id]["title"] then
|
||||
songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["artist"] then
|
||||
songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 25, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["bpm"] then
|
||||
songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["effector"] then
|
||||
songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["jacket"] and loadJacket then
|
||||
songCache[song.id]["jacket"] = gfx.CreateImage(song.difficulties[1].jacketPath, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function record_handler_factory(hash)
|
||||
return (function(res)
|
||||
if res.statusCode == 42 then
|
||||
recordCache[hash] = {good=false, reason="Untracked"}
|
||||
elseif res.statusCode == 20 and res.body ~= nil then
|
||||
recordCache[hash] = {good=true, record=res.body.record}
|
||||
elseif res.statusCode == 44 then
|
||||
recordCache[hash] = {good=true, record=nil}
|
||||
else
|
||||
recordCache[hash] = {good=false, reason="Failed"}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function get_record(hash)
|
||||
if recordCache[hash] then return recordCache[hash] end
|
||||
|
||||
recordCache[hash] = {good=false, reason="Loading..."}
|
||||
|
||||
IR.Record(hash, record_handler_factory(hash))
|
||||
|
||||
return recordCache[hash]
|
||||
end
|
||||
|
||||
function log_table(table)
|
||||
str = "{"
|
||||
for k, v in pairs(table) do
|
||||
str = str .. k .. ": "
|
||||
|
||||
t = type(v)
|
||||
|
||||
if t == "table" then
|
||||
str = str .. log_table(v)
|
||||
elseif t == "string" then
|
||||
str = str .. "\"" .. v .. "\""
|
||||
elseif t == "boolean" then
|
||||
if v then
|
||||
str = str .. "true"
|
||||
else
|
||||
str = str .. "false"
|
||||
end
|
||||
else
|
||||
str = str .. v
|
||||
end
|
||||
|
||||
str = str .. ", "
|
||||
end
|
||||
|
||||
return str .. "}"
|
||||
end
|
||||
|
||||
draw_scores_ir = function(difficulty, x, y, w, h)
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
|
||||
gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2))
|
||||
gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2)
|
||||
end
|
||||
|
||||
irRecord = get_record(difficulty.hash)
|
||||
|
||||
if not irRecord.good then
|
||||
recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0)
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
elseif irRecord.record == nil then --record not set, but can be tracked
|
||||
recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0)
|
||||
gfx.FillColor(170, 170, 170)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
else
|
||||
|
||||
recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0)
|
||||
recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0)
|
||||
|
||||
if irRecord.record.lamp ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0)
|
||||
end
|
||||
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > irRecord.record.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2)
|
||||
gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_scores = function(difficulty, x, y, w, h)
|
||||
if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end
|
||||
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.FastText("HIGH SCORE", x +(w/2), y+(h/2))
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+xOffset,y+h/2,w-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(scoreLabel, x+(w/2),y+(h/4)*3,w)
|
||||
end
|
||||
end
|
||||
|
||||
draw_song = function(song, x, y, w, h, selected)
|
||||
local difficulty = song.difficulties[selectedDiff]
|
||||
local clearLampR = 255
|
||||
local clearLampG = 255
|
||||
local clearLampB = 255
|
||||
local clearLampA = 100
|
||||
|
||||
if difficulty ~= nil then
|
||||
if difficulty.scores[1] ~= nil then
|
||||
if difficulty.topBadge == 1 then -- fail/played
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 2 then -- clear
|
||||
clearLampR = 25
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 3 then -- hard clear
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 255
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 4 then -- full combo
|
||||
clearLampR = 255
|
||||
clearLampG = 100
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 5 then -- perfect
|
||||
clearLampR = 255
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
check_or_create_cache(song)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1,y+1, w-2, h-2)
|
||||
gfx.FillColor(220,220,220)
|
||||
gfx.StrokeColor(0,8,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
if not songCache[song.id][1] or songCache[song.id][1] == jacketFallback then
|
||||
songCache[song.id][1] = gfx.LoadImageJob(song.difficulties[1].jacketPath, jacketFallback, 200,200)
|
||||
end
|
||||
if songCache[song.id][1] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id][1], 1, 0)
|
||||
end
|
||||
|
||||
-- Song title
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1)
|
||||
--gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10)
|
||||
|
||||
-- Song difficulty
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10)
|
||||
else
|
||||
gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10)
|
||||
end
|
||||
|
||||
|
||||
-- CLEAN THIS SHIT UP
|
||||
local diff_long = ""
|
||||
local diff_short = ""
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
if (song.difficulties[selectedDiff].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
else
|
||||
if (song.difficulties[selectedDiff - 1].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff - 1].difficulty == 3) then
|
||||
diff_long = "INFINITE"
|
||||
diff_short = "INF"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FontSize(8)
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FastText(diff_long, x + h/4, y + h - 7)
|
||||
|
||||
local seldiff = nil
|
||||
if song.difficulties[selectedDiff] ~= nil then
|
||||
seldiff = selectedDiff
|
||||
else
|
||||
seldiff = selectedDiff - 1
|
||||
end
|
||||
|
||||
if song.difficulties[seldiff].topBadge ~= 0 then
|
||||
if song.difficulties[seldiff].scores[1] ~= nil then
|
||||
local highScore = song.difficulties[seldiff].scores[1]
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0)
|
||||
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))
|
||||
end
|
||||
|
||||
draw_cursor = function(x,y,rotation,width)
|
||||
gfx.Save()
|
||||
gfx.BeginPath();
|
||||
gfx.Translate(x,y)
|
||||
gfx.Rotate(rotation)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(4)
|
||||
gfx.Rect(-width/2, -width/2, width, width)
|
||||
gfx.Stroke()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
draw_diffs = function(diffs, x, y, w, h)
|
||||
local diffWidth = w/2.5
|
||||
local diffHeight = w/2.5
|
||||
local diffCount = #diffs
|
||||
gfx.Scissor(x,y,w,h)
|
||||
for i = math.max(selectedDiff - 2, 1), math.max(selectedDiff - 1,1) do
|
||||
local diff = diffs[i]
|
||||
local xpos = x + ((w/2 - diffWidth/2) + (selectedDiff - i + doffset)*(-0.8*diffWidth))
|
||||
if i ~= selectedDiff then
|
||||
draw_diff_icon(diff, xpos, y, diffWidth, diffHeight, false)
|
||||
end
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedDiff + 2, diffCount), 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)
|
||||
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()
|
||||
gfx.ResetScissor()
|
||||
draw_cursor(x + w/2, y +diffHeight/2, timer * math.pi, diffHeight / 1.5)
|
||||
end
|
||||
|
||||
draw_selected = function(song, x, y, w, h)
|
||||
check_or_create_cache(song)
|
||||
-- set up padding and margins
|
||||
local xPadding = math.floor(w/16)
|
||||
local yPadding = math.floor(h/32)
|
||||
local xMargin = math.floor(w/16)
|
||||
local yMargin = math.floor(h/32)
|
||||
local width = (w-(xMargin*2))
|
||||
local height = (h-(yMargin*2))
|
||||
local xpos = x+xMargin
|
||||
local ypos = y+yMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
xPadding = math.floor(w/32)
|
||||
yPadding = math.floor(h/32)
|
||||
xMargin = math.floor(w/34)
|
||||
yMargin = math.floor(h/32)
|
||||
width = ((w/2)-(xMargin))
|
||||
height = (h-(yMargin*2))
|
||||
xpos = x+xMargin/2
|
||||
ypos = y+yMargin
|
||||
end
|
||||
--Border
|
||||
local diff = song.difficulties[selectedDiff]
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding)
|
||||
gfx.FillColor(30,30,30,100)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
-- jacket should take up 1/3 of height, always be square, and be centered
|
||||
local imageSize = math.floor(height/3)
|
||||
local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--Unless its portrait widesreen..
|
||||
imageSize = math.floor((height/8)*2)
|
||||
imageXPos = x+xMargin
|
||||
end
|
||||
if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then
|
||||
songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200)
|
||||
end
|
||||
|
||||
if songCache[song.id][selectedDiff] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(imageXPos, y+yMargin+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0)
|
||||
end
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--difficulty wheel should be right below the jacketImage, and the same width as
|
||||
--the jacketImage
|
||||
draw_diffs(song.difficulties,xpos+xPadding,(ypos+yPadding+imageSize),imageSize,math.floor((height/3)*1)-yPadding)
|
||||
else
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6))
|
||||
end
|
||||
-- effector / bpm should take up 1/3 of height, full width
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding+imageSize, y+yMargin+yPadding, width-imageSize-20)
|
||||
gfx.FontSize(30)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 45, width-imageSize-20)
|
||||
gfx.FontSize(20)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding+imageSize+3, y+yMargin+yPadding + 85, width-imageSize-20)
|
||||
gfx.FastText(string.format("Effector: %s", diff.effector), xpos+xPadding+imageSize+3, y+yMargin+yPadding + 115)
|
||||
else
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20)
|
||||
gfx.FontSize(30)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85)
|
||||
gfx.FastText(string.format("Effector: %s", diff.effector),xpos+10, (height/10)*6 + 115)
|
||||
end
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding)
|
||||
else
|
||||
draw_scores(diff, xpos, (height/6)*5, width, (height/6))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_songwheel = function(x,y,w,h)
|
||||
local offsetX = fifthX/2
|
||||
local width = math.floor((w/5)*4)
|
||||
if aspectRatio == "landscapeWidescreen" then
|
||||
wheelSize = 12
|
||||
offsetX = 80
|
||||
elseif aspectRatio == "landscapeStandard" then
|
||||
wheelSize = 10
|
||||
offsetX = 40
|
||||
elseif aspectRatio == "PortraitWidescreen" then
|
||||
wheelSize = 20
|
||||
offsetX = 20
|
||||
width = w/2
|
||||
end
|
||||
local height = math.floor((h/wheelSize)*1.75)
|
||||
|
||||
for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - offsetY)
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.05)
|
||||
local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY)
|
||||
local alpha = 255 - (selectedIndex - i + ioffset) * 31
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
-- draw selected
|
||||
local xpos = x + width
|
||||
local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat)))
|
||||
local ypos = y+((h/2 - height/2) - (ioffset) - offsetY)
|
||||
draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true)
|
||||
-- cursor
|
||||
gfx.BeginPath()
|
||||
local ypos = y+((h/2 - height/2))
|
||||
gfx.Rect(xpos, ypos, width, height)
|
||||
gfx.FillColor(0,0,0,0)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
return songwheel.songs[selectedIndex]
|
||||
end
|
||||
draw_legend_pane = function(x,y,w,h,obj)
|
||||
local xpos = x+5
|
||||
local ypos = y
|
||||
local imageSize = h
|
||||
gfx.BeginPath()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0)
|
||||
xpos = xpos + imageSize + 5
|
||||
gfx.FontSize(16);
|
||||
if h < (w-(10+imageSize))/2 then
|
||||
gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
else
|
||||
gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_legend = function(x,y,w,h)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,170)
|
||||
gfx.Rect(x,y,w,h)
|
||||
gfx.Fill()
|
||||
local xpos = 10;
|
||||
local legendWidth = math.floor((w-20)/#legendTable)
|
||||
for i,v in ipairs(legendTable) do
|
||||
local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i])
|
||||
end
|
||||
end
|
||||
|
||||
draw_search = function(x,y,w,h)
|
||||
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
|
||||
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
|
||||
game.PlaySample("woosh")
|
||||
end
|
||||
searchIndex = songwheel.searchInputActive and 0 or 1
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgfade = 1 - (searchIndex + soffset)
|
||||
--if not songwheel.searchInputActive then bgfade = soffset end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.ForceRender()
|
||||
local xpos = x + (searchIndex + soffset)*w
|
||||
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRect(xpos,y,w,h,h/2)
|
||||
gfx.FillColor(30,30,30)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
|
||||
|
||||
end
|
||||
|
||||
render = function(deltaTime)
|
||||
timer = (timer + deltaTime)
|
||||
timer = timer % 2
|
||||
resx,resy = game.GetResolution();
|
||||
adjustScreen(resx,resy);
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
gfx.FontSize(40);
|
||||
gfx.FillColor(255,255,255);
|
||||
if songwheel.songs[1] ~= nil then
|
||||
--draw songwheel and get selected song
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
local song = draw_songwheel(0,0,fullX,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fullX,resy)
|
||||
else
|
||||
local song = draw_songwheel(fifthX*2,0,fifthX*3,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fifthX*2,(fifthY/2)*9)
|
||||
end
|
||||
end
|
||||
--Draw Legend Information
|
||||
-- if showGuide then
|
||||
-- if aspectRatio == "PortraitWidescreen" then
|
||||
-- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1)
|
||||
-- else
|
||||
-- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2))
|
||||
-- end
|
||||
-- end
|
||||
|
||||
--draw text search
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/5)
|
||||
else
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/3)
|
||||
end
|
||||
|
||||
ioffset = ioffset * 0.9
|
||||
doffset = doffset * 0.9
|
||||
soffset = soffset * 0.8
|
||||
if songwheel.searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(songwheel.searchStatus, 3, 3)
|
||||
end
|
||||
if totalForce then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
|
||||
local forceText = string.format("Force: %.2f", totalForce)
|
||||
gfx.Text(forceText, 0, fullY)
|
||||
end
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.ResetTransform()
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
set_index = function(newIndex)
|
||||
if newIndex ~= selectedIndex then
|
||||
game.PlaySample("menu_click")
|
||||
end
|
||||
ioffset = ioffset + selectedIndex - newIndex
|
||||
selectedIndex = newIndex
|
||||
end;
|
||||
|
||||
set_diff = function(newDiff)
|
||||
if newDiff ~= selectedDiff then
|
||||
game.PlaySample("click-02")
|
||||
end
|
||||
doffset = doffset + selectedDiff - newDiff
|
||||
selectedDiff = newDiff
|
||||
end;
|
||||
|
||||
-- force calculation
|
||||
--------------------
|
||||
totalForce = nil
|
||||
|
||||
local badgeRates = {
|
||||
0.5, -- Played
|
||||
1.0, -- Cleared
|
||||
1.02, -- Hard clear
|
||||
1.04, -- UC
|
||||
1.1 -- PUC
|
||||
}
|
||||
|
||||
local gradeRates = {
|
||||
{["min"] = 9900000, ["rate"] = 1.05}, -- S
|
||||
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
|
||||
{["min"] = 9700000, ["rate"] = 1}, -- AAA
|
||||
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
|
||||
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
|
||||
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
|
||||
{["min"] = 8700000, ["rate"] = 0.88}, -- A
|
||||
{["min"] = 7500000, ["rate"] = 0.85}, -- B
|
||||
{["min"] = 6500000, ["rate"] = 0.82}, -- C
|
||||
{["min"] = 0, ["rate"] = 0.8} -- D
|
||||
}
|
||||
|
||||
calculate_force = function(diff)
|
||||
if #diff.scores < 1 then
|
||||
return 0
|
||||
end
|
||||
local score = diff.scores[1]
|
||||
local badgeRate = badgeRates[diff.topBadge]
|
||||
local gradeRate
|
||||
for i, v in ipairs(gradeRates) do
|
||||
if score.score >= v.min then
|
||||
gradeRate = v.rate
|
||||
break
|
||||
end
|
||||
end
|
||||
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
|
||||
end
|
||||
|
||||
songs_changed = function(withAll)
|
||||
if not withAll then return end
|
||||
|
||||
recordCache = {}
|
||||
|
||||
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 = calculate_force(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
|
||||
totalForce = totalForce + diffs[i].force
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,945 @@
|
|||
-- game.Log("Something went wrong!", game.LOGGER_ERROR)
|
||||
|
||||
--Horizontal alignment
|
||||
TEXT_ALIGN_LEFT = 1
|
||||
TEXT_ALIGN_CENTER = 2
|
||||
TEXT_ALIGN_RIGHT = 4
|
||||
--Vertical alignment
|
||||
TEXT_ALIGN_TOP = 8
|
||||
TEXT_ALIGN_MIDDLE = 16
|
||||
TEXT_ALIGN_BOTTOM = 32
|
||||
TEXT_ALIGN_BASELINE = 64
|
||||
|
||||
local jacket = nil;
|
||||
local selectedIndex = 1
|
||||
local selectedDiff = 1
|
||||
local songCache = {}
|
||||
local ioffset = 0
|
||||
local doffset = 0
|
||||
local soffset = 0
|
||||
local diffColors = {{0,0,255}, {0,255,0}, {255,0,0}, {255, 0, 255}}
|
||||
local timer = 0
|
||||
local effector = 0
|
||||
local searchText = gfx.CreateLabel("",5,0)
|
||||
local searchIndex = 1
|
||||
local jacketFallback = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
local showGuide = game.GetSkinSetting("show_guide")
|
||||
local legendTable = {
|
||||
{["labelSingleLine"] = gfx.CreateLabel("DIFFICULTY SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("DIFFICULTY\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-left.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC SELECT",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nSELECT",16, 0), ["image"] = gfx.CreateSkinImage("legend/knob-right.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("FILTER MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("FILTER\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-L.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("SORT MUSIC",16, 0), ["labelMultiLine"] = gfx.CreateLabel("SORT\nMUSIC",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-R.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("MUSIC MODS",16, 0), ["labelMultiLine"] = gfx.CreateLabel("MUSIC\nMODS",16, 0), ["image"] = gfx.CreateSkinImage("legend/FX-LR.png", 0)},
|
||||
{["labelSingleLine"] = gfx.CreateLabel("PLAY",16, 0), ["labelMultiLine"] = gfx.CreateLabel("PLAY",16, 0), ["image"] = gfx.CreateSkinImage("legend/start.png", 0)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
|
||||
local badges = {
|
||||
gfx.CreateSkinImage("badges/played.png", 0),
|
||||
gfx.CreateSkinImage("badges/clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/hard-clear.png", 0),
|
||||
gfx.CreateSkinImage("badges/full-combo.png", 0),
|
||||
gfx.CreateSkinImage("badges/perfect.png", 0)
|
||||
}
|
||||
|
||||
local difficultyNumbers = {
|
||||
[0] = gfx.CreateSkinImage("diff_num/0.png", 0),
|
||||
[1] = gfx.CreateSkinImage("diff_num/1.png", 0),
|
||||
[2] = gfx.CreateSkinImage("diff_num/2.png", 0),
|
||||
[3] = gfx.CreateSkinImage("diff_num/3.png", 0),
|
||||
[4] = gfx.CreateSkinImage("diff_num/4.png", 0),
|
||||
[5] = gfx.CreateSkinImage("diff_num/5.png", 0),
|
||||
[6] = gfx.CreateSkinImage("diff_num/6.png", 0),
|
||||
[7] = gfx.CreateSkinImage("diff_num/7.png", 0),
|
||||
[8] = gfx.CreateSkinImage("diff_num/8.png", 0),
|
||||
[9] = gfx.CreateSkinImage("diff_num/9.png", 0),
|
||||
};
|
||||
|
||||
local difficultyNameOverlays = {
|
||||
[0] = gfx.CreateSkinImage("song_select/level/novice.png", 0),
|
||||
[1] = gfx.CreateSkinImage("song_select/level/advanced.png", 0),
|
||||
[2] = gfx.CreateSkinImage("song_select/level/exhaust.png", 0),
|
||||
[3] = gfx.CreateSkinImage("song_select/level/maximum.png", 0),
|
||||
[4] = gfx.CreateSkinImage("song_select/level/maximum.png", 0),
|
||||
[5] = gfx.CreateSkinImage("song_select/level/maximum.png", 0),
|
||||
[6] = gfx.CreateSkinImage("song_select/level/maximum.png", 0),
|
||||
[7] = gfx.CreateSkinImage("song_select/level/maximum.png", 0),
|
||||
}
|
||||
|
||||
local difficultyLevelCursor = gfx.CreateSkinImage("song_select/level_cursor.png", 0);
|
||||
|
||||
local foreground = gfx.CreateSkinImage("song_select/fg.png", 0);
|
||||
|
||||
local datapanel = gfx.CreateSkinImage("song_select/data_bg.png", 0);
|
||||
|
||||
local recordCache = {}
|
||||
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
|
||||
game.LoadSkinSample("menu_click")
|
||||
game.LoadSkinSample("click-02")
|
||||
game.LoadSkinSample("woosh")
|
||||
|
||||
local wheelSize = 12
|
||||
|
||||
get_page_size = function()
|
||||
return math.floor(wheelSize/2)
|
||||
end
|
||||
|
||||
-- Responsive UI variables
|
||||
-- Aspect Ratios
|
||||
local aspectFloat = 1.850
|
||||
local aspectRatio = "widescreen"
|
||||
local landscapeWidescreenRatio = 1.850
|
||||
local landscapeStandardRatio = 1.500
|
||||
local portraitWidescreenRatio = 0.5
|
||||
|
||||
-- Responsive sizes
|
||||
local fifthX = 0
|
||||
local fourthX= 0
|
||||
local thirdX = 0
|
||||
local halfX = 0
|
||||
local fullX = 0
|
||||
|
||||
local fifthY = 0
|
||||
local fourthY= 0
|
||||
local thirdY = 0
|
||||
local halfY = 0
|
||||
local fullY = 0
|
||||
|
||||
|
||||
adjustScreen = function(x,y)
|
||||
local a = x/y;
|
||||
if x >= y and a <= landscapeStandardRatio then
|
||||
aspectRatio = "landscapeStandard"
|
||||
aspectFloat = 1.1
|
||||
elseif x >= y and landscapeStandardRatio <= a and a <= landscapeWidescreenRatio then
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.2
|
||||
elseif x <= y and portraitWidescreenRatio <= a and a < landscapeStandardRatio then
|
||||
aspectRatio = "PortraitWidescreen"
|
||||
aspectFloat = 0.5
|
||||
else
|
||||
aspectRatio = "landscapeWidescreen"
|
||||
aspectFloat = 1.0
|
||||
end
|
||||
fifthX = x/5
|
||||
fourthX= x/4
|
||||
thirdX = x/3
|
||||
halfX = x/2
|
||||
fullX = x
|
||||
|
||||
fifthY = y/5
|
||||
fourthY= y/4
|
||||
thirdY = y/3
|
||||
halfY = y/2
|
||||
fullY = y
|
||||
end
|
||||
|
||||
|
||||
check_or_create_cache = function(song, loadJacket)
|
||||
if not songCache[song.id] then songCache[song.id] = {} end
|
||||
|
||||
if not songCache[song.id]["title"] then
|
||||
songCache[song.id]["title"] = gfx.CreateLabel(song.title, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["artist"] then
|
||||
songCache[song.id]["artist"] = gfx.CreateLabel(song.artist, 14, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["bpm"] then
|
||||
songCache[song.id]["bpm"] = gfx.CreateLabel(string.format("%s",song.bpm), 12, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["effector"] then
|
||||
songCache[song.id]["effector"] = gfx.CreateLabel(string.format("BPM: %s",song.bpm), 20, 0)
|
||||
end
|
||||
|
||||
if not songCache[song.id]["jacket"] then
|
||||
songCache[song.id]["jacket"] = { }
|
||||
end
|
||||
|
||||
for i = 1, #song.difficulties do
|
||||
songCache[song.id]["jacket"][i] = gfx.LoadImageJob(song.difficulties[i].jacketPath, jacketFallback, 400, 400)
|
||||
end
|
||||
end
|
||||
|
||||
function record_handler_factory(hash)
|
||||
return (function(res)
|
||||
if res.statusCode == 42 then
|
||||
recordCache[hash] = {good=false, reason="Untracked"}
|
||||
elseif res.statusCode == 20 and res.body ~= nil then
|
||||
recordCache[hash] = {good=true, record=res.body.record}
|
||||
elseif res.statusCode == 44 then
|
||||
recordCache[hash] = {good=true, record=nil}
|
||||
else
|
||||
recordCache[hash] = {good=false, reason="Failed"}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function get_record(hash)
|
||||
if recordCache[hash] then return recordCache[hash] end
|
||||
|
||||
recordCache[hash] = {good=false, reason="Loading..."}
|
||||
|
||||
IR.Record(hash, record_handler_factory(hash))
|
||||
|
||||
return recordCache[hash]
|
||||
end
|
||||
|
||||
function log_table(table)
|
||||
str = "{"
|
||||
for k, v in pairs(table) do
|
||||
str = str .. k .. ": "
|
||||
|
||||
t = type(v)
|
||||
|
||||
if t == "table" then
|
||||
str = str .. log_table(v)
|
||||
elseif t == "string" then
|
||||
str = str .. "\"" .. v .. "\""
|
||||
elseif t == "boolean" then
|
||||
if v then
|
||||
str = str .. "true"
|
||||
else
|
||||
str = str .. "false"
|
||||
end
|
||||
else
|
||||
str = str .. v
|
||||
end
|
||||
|
||||
str = str .. ", "
|
||||
end
|
||||
|
||||
return str .. "}"
|
||||
end
|
||||
|
||||
draw_scores_ir = function(difficulty, x, y, w, h)
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
|
||||
gfx.FastText("HIGH SCORE", x +(w/4), y+(h/2))
|
||||
gfx.FastText("IR RECORD", x + (3/4 * w), y + (h/2))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+xOffset,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x + xOffset + w/2,y+h/2,w/2-(xOffset*2),h/2)
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w/2-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(scoreLabel, x+(w/4),y+(h/4)*3,w/2)
|
||||
end
|
||||
|
||||
irRecord = get_record(difficulty.hash)
|
||||
|
||||
if not irRecord.good then
|
||||
recordLabel = gfx.CreateLabel(irRecord.reason, 40, 0)
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
elseif irRecord.record == nil then --record not set, but can be tracked
|
||||
recordLabel = gfx.CreateLabel(string.format("%08d", 0), 40, 0)
|
||||
gfx.FillColor(170, 170, 170)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordLabel, x+(w * 3/4),y+(h/4)*3,w/2)
|
||||
else
|
||||
|
||||
recordScoreLabel = gfx.CreateLabel(string.format("%08d", irRecord.record.score), 26, 0)
|
||||
recordPlayerLabel = gfx.CreateLabel(irRecord.record.username, 26, 0)
|
||||
|
||||
if irRecord.record.lamp ~= 0 then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[irRecord.record.lamp], 1, 0)
|
||||
end
|
||||
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > irRecord.record.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iarr = ih / iw
|
||||
oldheight = h/2 - 10
|
||||
newheight = iarr * (h/2-10)
|
||||
centreoffset = (oldheight - newheight)/2 + 3 -- +3 is stupid but ehhh
|
||||
gfx.ImageRect(x+xOffset+w/2, y+h/2 + centreoffset, oldheight, newheight, v.image, 1, 0) --this is nasty but it works for me
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FillColor(255, 255, 255)
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.DrawLabel(recordPlayerLabel, x+(w * 3/4),y+(h/4)*2.55,w/2)
|
||||
gfx.DrawLabel(recordScoreLabel, x+(w * 3/4),y+(h/4)*3.45,w/2)
|
||||
end
|
||||
end
|
||||
|
||||
draw_scores = function(difficulty, x, y, w, h)
|
||||
if IRData.Active then return draw_scores_ir(difficulty, x, y, w, h) end
|
||||
|
||||
-- draw the top score for this difficulty
|
||||
local xOffset = 5
|
||||
local height = h/3 - 10
|
||||
local ySpacing = h/3
|
||||
local yOffset = h/3
|
||||
gfx.FontSize(30);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_CENTER);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(30,30,30,10)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
if difficulty.scores[1] ~= nil then
|
||||
local highScore = difficulty.scores[1]
|
||||
scoreLabel = gfx.CreateLabel(string.format("%08d",highScore.score), 40, 0)
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
--gfx.ImageRect(x+xOffset,y+h/2 +5, iar * (h/2-10),h/2-10, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
if difficulty.topBadge ~= 0 then
|
||||
gfx.BeginPath()
|
||||
--gfx.ImageRect(x+xOffset+w-h/2, y+h/2 +5, (h/2-10), h/2-10, badges[difficulty.topBadge], 1, 0)
|
||||
end
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(40);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.DrawLabel(scoreLabel, x/11,y/1.48,w*2)
|
||||
end
|
||||
end
|
||||
|
||||
function deep_to_string(t)
|
||||
local tType = type(t);
|
||||
if (tType ~= "table") then
|
||||
return tostring(t);
|
||||
end
|
||||
|
||||
local result = "{";
|
||||
|
||||
for k, v in next, t do
|
||||
local kType = type(k);
|
||||
local vType = type(v);
|
||||
|
||||
local keyString = deep_to_string(k);
|
||||
local valueString = deep_to_string(v);
|
||||
|
||||
if (#result > 1) then
|
||||
result = result .. ";";
|
||||
end
|
||||
|
||||
result = result .. keyString .. "=" .. valueString;
|
||||
end
|
||||
|
||||
return result .. "}";
|
||||
end
|
||||
|
||||
draw_song = function(song, x, y, w, h, selected)
|
||||
-- game.Log("draw_song", game.LOGGER_ERROR);
|
||||
|
||||
local diffIndex = math.min(selectedDiff, #song.difficulties)
|
||||
local difficulty = song.difficulties[diffIndex]
|
||||
local clearLampR = 255
|
||||
local clearLampG = 255
|
||||
local clearLampB = 255
|
||||
local clearLampA = 100
|
||||
|
||||
if difficulty ~= nil then
|
||||
-- game.Log(deep_to_string(difficulty), game.LOGGER_ERROR);
|
||||
if difficulty.scores[1] ~= nil then
|
||||
if difficulty.topBadge == 1 then -- fail/played
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 2 then -- clear
|
||||
clearLampR = 25
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 3 then -- hard clear
|
||||
clearLampR = 255
|
||||
clearLampG = 25
|
||||
clearLampB = 255
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 4 then -- full combo
|
||||
clearLampR = 255
|
||||
clearLampG = 100
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
if difficulty.topBadge == 5 then -- perfect
|
||||
clearLampR = 255
|
||||
clearLampG = 255
|
||||
clearLampB = 25
|
||||
clearLampA = 200
|
||||
end
|
||||
end
|
||||
end
|
||||
-- game.Log(" past difficulty check", game.LOGGER_ERROR);
|
||||
|
||||
check_or_create_cache(song)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1,y+1, w-2, h-2)
|
||||
gfx.FillColor(220,220,220)
|
||||
gfx.StrokeColor(0,8,0)
|
||||
gfx.StrokeWidth(2)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.FillColor(255,255,255)
|
||||
if songCache[song.id]["jacket"][diffIndex] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x+2, y+2, h-4, h-4, songCache[song.id]["jacket"][diffIndex], 1, 0)
|
||||
end
|
||||
|
||||
-- Song title
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x+1, y + h - h/4 - 1, w-2, h/4)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], (x)+h/2 + 4, y + h - 7, -1)
|
||||
--gfx.DrawLabel(songCache[song.id]["artist"], x+10, y + 50, w-10)
|
||||
|
||||
-- Song difficulty
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(x - 1, y + h-h/2 - 4, h/2, h/2)
|
||||
gfx.FillColor(0,0,0,200)
|
||||
gfx.Fill()
|
||||
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
gfx.FontSize(28)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
gfx.FastText(song.difficulties[selectedDiff].level, x + h/4, y + h - 10)
|
||||
else
|
||||
--gfx.FastText(song.difficulties[selectedDiff - 1].level, x + h/4, y + h - 10)
|
||||
end
|
||||
|
||||
-- CLEAN THIS SHIT UP
|
||||
local diff_long = ""
|
||||
local diff_short = ""
|
||||
if (song.difficulties[selectedDiff] ~= nil) then
|
||||
if (song.difficulties[selectedDiff].difficulty == 0) then
|
||||
diff_long = "NOVICE"
|
||||
diff_short = "NOV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 1) then
|
||||
diff_long = "ADVANCED"
|
||||
diff_short = "ADV"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 2) then
|
||||
diff_long = "EXHAUST"
|
||||
diff_short = "EXH"
|
||||
elseif (song.difficulties[selectedDiff].difficulty == 3) then
|
||||
diff_long = "MAXIMUM"
|
||||
diff_short = "MXM"
|
||||
else
|
||||
diff_long = "UNKNOWN"
|
||||
diff_short = "???"
|
||||
end
|
||||
end
|
||||
|
||||
gfx.FontSize(8)
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
gfx.FastText(diff_long, x + h/4, y + h - 7)
|
||||
|
||||
if (false) then
|
||||
|
||||
local seldiff = nil
|
||||
if song.difficulties[selectedDiff] ~= nil then
|
||||
seldiff = selectedDiff
|
||||
else
|
||||
seldiff = selectedDiff
|
||||
end
|
||||
|
||||
if song.difficulties[seldiff].topBadge ~= 0 then
|
||||
if song.difficulties[seldiff].scores[1] ~= nil then
|
||||
local highScore = song.difficulties[seldiff].scores[1]
|
||||
for i,v in ipairs(grades) do
|
||||
if v.max > highScore.score then
|
||||
gfx.BeginPath()
|
||||
iw,ih = gfx.ImageSize(v.image)
|
||||
iar = iw / ih;
|
||||
gfx.ImageRect(x + w/1.45, y + h/8 + 2, (h/1.5-14), h/1.5-14, v.image, 1, 0)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x + w/2, y + h/8, (h/1.5-10), h/1.5-10, badges[song.difficulties[seldiff].topBadge], 1, 0)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
draw_diff_icon = function(diff, x, y, w, h, selected)
|
||||
local difficultyIndex = diff.difficulty;
|
||||
|
||||
local image = difficultyNameOverlays[difficultyIndex];
|
||||
|
||||
local imgx, imgy = gfx.ImageSize(image);
|
||||
local aspect = imgx / imgy;
|
||||
|
||||
h = h * 98 / 112;
|
||||
|
||||
local wa = h * aspect;
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x - wa / 2, y - h / 2, wa, h, image, 1, 0);
|
||||
|
||||
local level = diff.level;
|
||||
|
||||
local firstDigit = difficultyNumbers[math.max(0, math.floor(level / 10))];
|
||||
local secondDigit = difficultyNumbers[level % 10];
|
||||
|
||||
h = h * 0.475;
|
||||
|
||||
imgx, imgy = gfx.ImageSize(firstDigit);
|
||||
aspect = imgx / imgy;
|
||||
|
||||
wa = h * aspect;
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x - wa, y - h / 2, wa, h, firstDigit, 1, 0);
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x, y - h / 2, wa, h, secondDigit, 1, 0);
|
||||
end
|
||||
|
||||
draw_cursor = function(x, y, h)
|
||||
local imgx, imgy = gfx.ImageSize(difficultyLevelCursor);
|
||||
local aspect = imgx / imgy;
|
||||
|
||||
local w = h * aspect;
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x - w / 2, y - h / 2, w, h, difficultyLevelCursor, 1, 0);
|
||||
end
|
||||
|
||||
draw_diffs = function(diffs, x, y, w, h)
|
||||
local diffWidth = w / 5
|
||||
local diffHeight = diffWidth
|
||||
|
||||
for i = 1, #diffs do
|
||||
local diff = diffs[i]
|
||||
|
||||
local xPos = x + w * (i - 0.5) / 4;
|
||||
local yPos = y + h / 2;
|
||||
|
||||
if (i == selectedDiff) then
|
||||
draw_cursor(xPos, yPos, diffHeight);
|
||||
end
|
||||
|
||||
draw_diff_icon(diff, xPos, yPos, diffWidth, diffHeight, i == selectedDiff);
|
||||
end
|
||||
end
|
||||
|
||||
draw_selected = function(song, x, y, w, h)
|
||||
check_or_create_cache(song)
|
||||
-- set up padding and margins
|
||||
local xPadding = math.floor(w/16)
|
||||
local yPadding = math.floor(h/32)
|
||||
local xMargin = math.floor(w/16)
|
||||
local yMargin = math.floor(h/32)
|
||||
local width = (w-(xMargin*2))
|
||||
local height = (h-(yMargin*2))
|
||||
local xpos = x+xMargin
|
||||
local ypos = y+yMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
xPadding = math.floor(w/32)
|
||||
yPadding = math.floor(h/32)
|
||||
xMargin = math.floor(w/34)
|
||||
yMargin = math.floor(h/32)
|
||||
width = ((w/2)-(xMargin))
|
||||
height = (h-(yMargin*2))
|
||||
xpos = x+xMargin/2
|
||||
ypos = y+yMargin
|
||||
end
|
||||
--Border
|
||||
local diff = song.difficulties[selectedDiff]
|
||||
gfx.BeginPath()
|
||||
--gfx.RoundedRectVarying(xpos,ypos,width,height,yPadding,yPadding,yPadding,yPadding)
|
||||
gfx.FillColor(30,30,30,100)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
-- jacket should take up 1/3 of height, always be square, and be centered
|
||||
local imageSize = math.floor(height/3)
|
||||
local imageXPos = ((width/2) - (imageSize/2)) + x+xMargin
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--Unless its portrait widesreen..
|
||||
imageSize = math.floor((height/8)*1.58)
|
||||
imageXPos = (x+w)/16+(xMargin*0.8)
|
||||
end
|
||||
if not songCache[song.id][selectedDiff] or songCache[song.id][selectedDiff] == jacketFallback then
|
||||
songCache[song.id][selectedDiff] = gfx.LoadImageJob(diff.jacketPath, jacketFallback, 200,200)
|
||||
end
|
||||
|
||||
if songCache[song.id][selectedDiff] then
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(imageXPos, y+yMargin*4.45+yPadding, imageSize, imageSize, songCache[song.id][selectedDiff], 1, 0)
|
||||
end
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
gfx.LoadSkinFont("commext.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
--difficulty wheel should be right below the jacketImage, and the same width as
|
||||
--the jacketImage
|
||||
local diffPanelWidth = w * 0.4275;
|
||||
local diffPanelHeight = diffPanelWidth / 4;
|
||||
draw_diffs(song.difficulties, (w / 2 - diffPanelWidth) / 2, y + 0.5687583444592 * h - diffPanelHeight / 2, diffPanelWidth, diffPanelHeight)
|
||||
else
|
||||
-- difficulty should take up 1/6 of height, full width, and be centered
|
||||
draw_diffs(song.difficulties,(w/2)-(imageSize/2),(ypos+yPadding+imageSize),imageSize,math.floor(height/6))
|
||||
end
|
||||
-- effector / bpm should take up 1/3 of height, full width
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf")
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+xPadding/2, y+yMargin*15+yPadding, width)
|
||||
gfx.FontSize(40)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+xPadding/2, y+yMargin*15.8+yPadding, width)
|
||||
gfx.FontSize(10)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+xPadding*2, y+yMargin*14.42+yPadding, width-imageSize)
|
||||
gfx.FastText(string.format("%s", diff.effector), xpos+xPadding*7.5, y+yMargin*18.87+yPadding)
|
||||
else
|
||||
gfx.FontSize(40)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.DrawLabel(songCache[song.id]["title"], xpos+10, (height/10)*6, width-20)
|
||||
gfx.FontSize(30)
|
||||
gfx.DrawLabel(songCache[song.id]["artist"], xpos+10, (height/10)*6 + 45, width-20)
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20)
|
||||
gfx.DrawLabel(songCache[song.id]["bpm"], xpos+10, (height/10)*6 + 85)
|
||||
gfx.FastText(string.format("%s", diff.effector),xpos+10, (height/10)*6 + 115)
|
||||
end
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_scores(diff, xpos+xPadding+imageSize+3, (height/3)*2, width-imageSize-20, (height/3)-yPadding)
|
||||
else
|
||||
draw_scores(diff, xpos, (height/6)*5, width, (height/6))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_songwheel = function(x,y,w,h)
|
||||
local offsetX = fifthX/2
|
||||
local width = math.floor((w/5)*4)
|
||||
if aspectRatio == "landscapeWidescreen" then
|
||||
wheelSize = 12
|
||||
offsetX = 80
|
||||
elseif aspectRatio == "landscapeStandard" then
|
||||
wheelSize = 10
|
||||
offsetX = 40
|
||||
elseif aspectRatio == "PortraitWidescreen" then
|
||||
wheelSize = 20
|
||||
offsetX = 20
|
||||
width = w/2
|
||||
end
|
||||
local height = math.floor((h/wheelSize)*1.75)
|
||||
|
||||
for i = math.max(selectedIndex - wheelSize/2, 1), math.max(selectedIndex - 1,0) do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.0)
|
||||
local ypos = y+((h/2 - height/2) - offsetY)
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
|
||||
--after selected
|
||||
for i = math.min(selectedIndex + wheelSize/2, #songwheel.songs), selectedIndex + 1,-1 do
|
||||
local song = songwheel.songs[i]
|
||||
local xpos = x + width
|
||||
local offsetY = (selectedIndex - i + ioffset/2) * ( height * 1.0)
|
||||
local ypos = y+((h/2 - height/2) - (selectedIndex - i) - offsetY)
|
||||
local alpha = 255 - (selectedIndex - i + ioffset) * 31
|
||||
draw_song(song, xpos, ypos, width, height)
|
||||
end
|
||||
-- draw selected
|
||||
local xpos = x + width
|
||||
local offsetY = (ioffset/2) * ( height - (wheelSize/2*((1)*aspectFloat)))
|
||||
local ypos = y+((h/2 - height/2) - (ioffset) - offsetY)
|
||||
draw_song(songwheel.songs[selectedIndex], xpos, ypos, width, height, true)
|
||||
-- cursor
|
||||
gfx.BeginPath()
|
||||
local ypos = y+((h/2 - height/2))
|
||||
gfx.Rect(xpos, ypos, width, height)
|
||||
gfx.FillColor(0,0,0,0)
|
||||
gfx.StrokeColor(255,128,0)
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
return songwheel.songs[selectedIndex]
|
||||
end
|
||||
draw_legend_pane = function(x,y,w,h,obj)
|
||||
local xpos = x+5
|
||||
local ypos = y
|
||||
local imageSize = h
|
||||
gfx.BeginPath()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.ImageRect(x, y, imageSize, imageSize, obj.image, 1, 0)
|
||||
xpos = xpos + imageSize + 5
|
||||
gfx.FontSize(16);
|
||||
if h < (w-(10+imageSize))/2 then
|
||||
gfx.DrawLabel(obj.labelSingleLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
else
|
||||
gfx.DrawLabel(obj.labelMultiLine, xpos, y+(h/2), w-(10+imageSize))
|
||||
end
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
draw_legend = function(x,y,w,h)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_LEFT);
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(0,0,0,170)
|
||||
gfx.Rect(x,y,w,h)
|
||||
gfx.Fill()
|
||||
local xpos = 10;
|
||||
local legendWidth = math.floor((w-20)/#legendTable)
|
||||
for i,v in ipairs(legendTable) do
|
||||
local xOffset = draw_legend_pane(xpos+(legendWidth*(i-1)), y+5,legendWidth,h-10,legendTable[i])
|
||||
end
|
||||
end
|
||||
|
||||
draw_search = function(x,y,w,h)
|
||||
soffset = soffset + (searchIndex) - (songwheel.searchInputActive and 0 or 1)
|
||||
if searchIndex ~= (songwheel.searchInputActive and 0 or 1) then
|
||||
game.PlaySample("woosh")
|
||||
end
|
||||
searchIndex = songwheel.searchInputActive and 0 or 1
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgfade = 1 - (searchIndex + soffset)
|
||||
--if not songwheel.searchInputActive then bgfade = soffset end
|
||||
gfx.FillColor(0,0,0,math.floor(200 * bgfade))
|
||||
gfx.Rect(0,0,resx,resy)
|
||||
gfx.Fill()
|
||||
gfx.ForceRender()
|
||||
local xpos = x + (searchIndex + soffset)*w
|
||||
gfx.UpdateLabel(searchText ,string.format("Search: %s",songwheel.searchText), 30, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.RoundedRect(xpos,y,w,h,h/2)
|
||||
gfx.FillColor(30,30,30)
|
||||
gfx.StrokeColor(0,128,255)
|
||||
gfx.StrokeWidth(1)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(searchText, xpos+10,y+(h/2), w-20)
|
||||
|
||||
end
|
||||
|
||||
render = function(deltaTime)
|
||||
gfx.ResetTransform()
|
||||
timer = (timer + deltaTime)
|
||||
timer = timer % 2
|
||||
resx,resy = game.GetResolution();
|
||||
-- game.Log("res :: " .. resx .. "," .. resy, game.LOGGER_ERROR);
|
||||
adjustScreen(resx,resy);
|
||||
gfx.BeginPath();
|
||||
gfx.LoadSkinFont("dfmarugoth.ttf");
|
||||
gfx.FontSize(40);
|
||||
gfx.FillColor(255,255,255);
|
||||
gfx.ImageRect(0, 0, resx, resy, datapanel, 1, 0);
|
||||
if songwheel.songs[1] ~= nil then
|
||||
--draw songwheel and get selected song
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
local song = draw_songwheel(0,0,fullX,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fullX,resy)
|
||||
else
|
||||
local song = draw_songwheel(0,0,fullX,fullY)
|
||||
--render selected song information
|
||||
draw_selected(song, 0,0,fullX,resy)
|
||||
end
|
||||
end
|
||||
--Draw Legend Information
|
||||
-- if showGuide then
|
||||
-- if aspectRatio == "PortraitWidescreen" then
|
||||
-- draw_legend(0,(fifthY/3)*14, fullX, (fifthY/3)*1)
|
||||
-- else
|
||||
-- draw_legend(0,(fifthY/2)*9, fullX, (fifthY/2))
|
||||
-- end
|
||||
-- end
|
||||
gfx.BeginPath();
|
||||
gfx.TextAlign(TEXT_ALIGN_CENTER + TEXT_ALIGN_MIDDLE);
|
||||
gfx.ImageRect(0, 0, resx, resy, foreground, 1, 0);
|
||||
|
||||
--draw text search
|
||||
if aspectRatio == "PortraitWidescreen" then
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/5)
|
||||
else
|
||||
draw_search(fifthX*2,5,fifthX*3,fifthY/3)
|
||||
end
|
||||
|
||||
ioffset = ioffset * 0.9
|
||||
doffset = doffset * 0.9
|
||||
soffset = soffset * 0.8
|
||||
if songwheel.searchStatus then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_TOP)
|
||||
gfx.Text(songwheel.searchStatus, 3, 3)
|
||||
end
|
||||
if totalForce then
|
||||
gfx.BeginPath()
|
||||
gfx.FillColor(255,255,255)
|
||||
gfx.FontSize(20);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_BOTTOM)
|
||||
local forceText = string.format("Force: %.2f", totalForce)
|
||||
gfx.Text(forceText, 0, fullY)
|
||||
end
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
gfx.ResetTransform()
|
||||
gfx.ForceRender()
|
||||
end
|
||||
|
||||
set_index = function(newIndex)
|
||||
if newIndex ~= selectedIndex then
|
||||
game.PlaySample("menu_click")
|
||||
end
|
||||
ioffset = ioffset + selectedIndex - newIndex
|
||||
selectedIndex = newIndex
|
||||
end;
|
||||
|
||||
set_diff = function(newDiff)
|
||||
if newDiff ~= selectedDiff then
|
||||
game.PlaySample("click-02")
|
||||
end
|
||||
doffset = doffset + selectedDiff - newDiff
|
||||
selectedDiff = newDiff
|
||||
end;
|
||||
|
||||
-- force calculation
|
||||
--------------------
|
||||
totalForce = nil
|
||||
|
||||
local badgeRates = {
|
||||
0.5, -- Played
|
||||
1.0, -- Cleared
|
||||
1.02, -- Hard clear
|
||||
1.04, -- UC
|
||||
1.1 -- PUC
|
||||
}
|
||||
|
||||
local gradeRates = {
|
||||
{["min"] = 9900000, ["rate"] = 1.05}, -- S
|
||||
{["min"] = 9800000, ["rate"] = 1.02}, -- AAA+
|
||||
{["min"] = 9700000, ["rate"] = 1}, -- AAA
|
||||
{["min"] = 9500000, ["rate"] = 0.97}, -- AA+
|
||||
{["min"] = 9300000, ["rate"] = 0.94}, -- AA
|
||||
{["min"] = 9000000, ["rate"] = 0.91}, -- A+
|
||||
{["min"] = 8700000, ["rate"] = 0.88}, -- A
|
||||
{["min"] = 7500000, ["rate"] = 0.85}, -- B
|
||||
{["min"] = 6500000, ["rate"] = 0.82}, -- C
|
||||
{["min"] = 0, ["rate"] = 0.8} -- D
|
||||
}
|
||||
|
||||
calculate_force = function(diff)
|
||||
if #diff.scores < 1 then
|
||||
return 0
|
||||
end
|
||||
local score = diff.scores[1]
|
||||
local badgeRate = badgeRates[diff.topBadge]
|
||||
local gradeRate
|
||||
for i, v in ipairs(gradeRates) do
|
||||
if score.score >= v.min then
|
||||
gradeRate = v.rate
|
||||
break
|
||||
end
|
||||
end
|
||||
return math.floor((diff.level * 2) * (score.score / 10000000) * gradeRate * badgeRate) / 100
|
||||
end
|
||||
|
||||
songs_changed = function(withAll)
|
||||
if not withAll then return end
|
||||
|
||||
recordCache = {}
|
||||
|
||||
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 = calculate_force(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
|
||||
totalForce = totalForce + diffs[i].force
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,242 +1,243 @@
|
|||
local Easing = require('common.easing');
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw, desh = 1080, 1920
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample('sort_wheel/enter.wav');
|
||||
game.LoadSkinSample('sort_wheel/leave.wav');
|
||||
|
||||
-- IMAGES
|
||||
local panelBgImage = gfx.CreateSkinImage('song_select/sort_wheel/bg.png', 0)
|
||||
local activeItemBgImage = gfx.CreateSkinImage(
|
||||
'song_select/sort_wheel/active_bg.png', 0)
|
||||
local titleTextImage =
|
||||
gfx.CreateSkinImage('song_select/sort_wheel/title.png', 0)
|
||||
|
||||
local selection = 1;
|
||||
local renderedButtonLabels = {}
|
||||
|
||||
local FONT_SIZE = 32;
|
||||
local MARGIN = 16;
|
||||
local SUB_FONT_SIZE = 26;
|
||||
local SUB_MARGIN = 8;
|
||||
|
||||
local SORT_ORDER_LABEL_TEXTS = {
|
||||
{
|
||||
label = 'Title',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}, {label = 'Score', asc = 'Worst to best', dsc = 'Best to worst'},
|
||||
{label = 'Date', asc = 'Oldest to newest', dsc = 'Newest to oldest'},
|
||||
{label = 'Badge', asc = 'None to D to S', dsc = 'S to D to None'}, {
|
||||
label = 'Artist',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}, {
|
||||
label = 'Effector',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}
|
||||
}
|
||||
|
||||
local transitionEnterReverse = false;
|
||||
local transitionEnterScale = 0;
|
||||
local transitionEnterOffsetX = 0;
|
||||
|
||||
local previousActiveState = false;
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16 --+ 0.0035
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
|
||||
local resolutionChange = function(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
|
||||
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR);
|
||||
end
|
||||
|
||||
function tableContains(table, value)
|
||||
for i, v in ipairs(table) do if v == value then return true end end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function drawButton(i, f, x, y)
|
||||
local spaceAfter = (FONT_SIZE + MARGIN)
|
||||
|
||||
local sortOrder = 'asc';
|
||||
if (string.find(f, 'v')) then sortOrder = 'dsc' end
|
||||
|
||||
local label = f:gsub(' ^', '')
|
||||
label = label:gsub(' v', '')
|
||||
|
||||
if (string.find(sorts[selection], label) and sorts[selection] ~= f) then
|
||||
-- If there is a button active with the same label, but different sort order, don't render this one
|
||||
return 0;
|
||||
else
|
||||
-- If there is no active button with this label, if one with a label was already rendered, don't render this one
|
||||
if (tableContains(renderedButtonLabels, label)) then return 0; end
|
||||
table.insert(renderedButtonLabels, label);
|
||||
end
|
||||
|
||||
if (i == selection) then
|
||||
local ascLabelText = 'Ascending'
|
||||
local dscLabelText = 'Descending'
|
||||
|
||||
for i, obj in ipairs(SORT_ORDER_LABEL_TEXTS) do
|
||||
if (obj.label == label) then
|
||||
ascLabelText = obj.asc;
|
||||
dscLabelText = obj.dsc;
|
||||
end
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x - 182, y - 38, 365, 82, activeItemBgImage, 1, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
if sortOrder == 'asc' then
|
||||
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 - 31, 300, 67,
|
||||
activeItemBgImage, 1, 0)
|
||||
elseif sortOrder == 'dsc' then
|
||||
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 +
|
||||
SUB_FONT_SIZE + SUB_MARGIN - 31, 300, 67,
|
||||
activeItemBgImage, 1, 0)
|
||||
end
|
||||
|
||||
gfx.Save()
|
||||
gfx.FontSize(SUB_FONT_SIZE)
|
||||
gfx.Text(ascLabelText, x, y + FONT_SIZE + SUB_MARGIN * 2);
|
||||
gfx.Text(dscLabelText, x,
|
||||
y + FONT_SIZE + SUB_MARGIN * 2 + SUB_FONT_SIZE + SUB_MARGIN);
|
||||
gfx.Restore()
|
||||
|
||||
spaceAfter = spaceAfter + SUB_FONT_SIZE * 2 + SUB_MARGIN * 4;
|
||||
end
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.Text(label, x, y);
|
||||
|
||||
return spaceAfter;
|
||||
end
|
||||
|
||||
function tickTransitions(deltaTime)
|
||||
-- ENTRY TRANSITION
|
||||
if transitionEnterReverse then
|
||||
if transitionEnterScale > 0 then
|
||||
transitionEnterScale = transitionEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds
|
||||
else
|
||||
transitionEnterScale = 0
|
||||
end
|
||||
else
|
||||
if transitionEnterScale < 1 then
|
||||
transitionEnterScale = transitionEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds
|
||||
else
|
||||
transitionEnterScale = 1
|
||||
end
|
||||
end
|
||||
|
||||
transitionEnterOffsetX = Easing.inOutQuad(1 - transitionEnterScale) * 416
|
||||
end
|
||||
|
||||
local drawSortWheel = function (x,y,w,h, deltaTime)
|
||||
gfx.Scissor(x,y,w,h);
|
||||
gfx.Translate(x,y);
|
||||
gfx.Scale(w/1080, h/1920);
|
||||
gfx.Scissor(0,0,1080,1920);
|
||||
|
||||
-- Draw the dark overlay above song wheel
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0, 0, 0, math.floor(transitionEnterScale * 192));
|
||||
gfx.Rect(0, 0, 1080, 1920);
|
||||
gfx.Fill();
|
||||
|
||||
-- Draw the panel background
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(desw - 416 + transitionEnterOffsetX, 0, 416, desh,
|
||||
panelBgImage, 1, 0)
|
||||
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf");
|
||||
gfx.FontSize(FONT_SIZE);
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
|
||||
gfx.GlobalAlpha(transitionEnterScale)
|
||||
|
||||
-- Starting position of the first sort option
|
||||
local x = 889 + transitionEnterOffsetX;
|
||||
local y = desh / 2 - -- Center point
|
||||
(#sorts / 2 / 2) * (FONT_SIZE + MARGIN) - -- Space taken up by half the sort options (we remove the duplicate one)
|
||||
((SUB_FONT_SIZE * 2 + SUB_MARGIN * 4) / 2); -- Space for taken by order options
|
||||
|
||||
-- Draw the title image
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x - 72, y - 27, 144, 54, titleTextImage, 1, 0)
|
||||
y = y + (54 + MARGIN)
|
||||
|
||||
-- Draw all the sorting options
|
||||
for i, f in ipairs(sorts) do
|
||||
local spaceAfter = drawButton(i, f, x, y);
|
||||
y = y + spaceAfter;
|
||||
end
|
||||
end
|
||||
|
||||
function setSkinSetting()
|
||||
for i, f in ipairs(sorts) do
|
||||
if i == selection then
|
||||
local label = f:gsub(' ^', '')
|
||||
label = label:gsub(' v', '')
|
||||
|
||||
game.SetSkinSetting('_songWheelActiveSortOptionLabel', label);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function render(deltaTime, shown)
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
renderedButtonLabels = {};
|
||||
|
||||
|
||||
if (shown ~= previousActiveState) then
|
||||
if (shown) then
|
||||
game.PlaySample('sort_wheel/enter.wav');
|
||||
else
|
||||
game.PlaySample('sort_wheel/leave.wav');
|
||||
end
|
||||
|
||||
previousActiveState = shown;
|
||||
end
|
||||
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution();
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
if not shown then
|
||||
transitionEnterReverse = true
|
||||
if (transitionEnterScale > 0) then drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime) end
|
||||
else
|
||||
transitionEnterReverse = false
|
||||
drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
|
||||
end
|
||||
tickTransitions(deltaTime)
|
||||
setSkinSetting();
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function set_selection(index) selection = index end
|
||||
require('common')
|
||||
local Easing = require('common.easing');
|
||||
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw, desh = 1080, 1920
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample('sort_wheel/enter.wav');
|
||||
game.LoadSkinSample('sort_wheel/leave.wav');
|
||||
|
||||
-- IMAGES
|
||||
local panelBgImage = gfx.CreateSkinImage('song_select/sort_wheel/bg.png', 0)
|
||||
local activeItemBgImage = gfx.CreateSkinImage(
|
||||
'song_select/sort_wheel/active_bg.png', 0)
|
||||
local titleTextImage =
|
||||
gfx.CreateSkinImage('song_select/sort_wheel/title.png', 0)
|
||||
|
||||
local selection = 1;
|
||||
local renderedButtonLabels = {}
|
||||
|
||||
local FONT_SIZE = 32;
|
||||
local MARGIN = 16;
|
||||
local SUB_FONT_SIZE = 26;
|
||||
local SUB_MARGIN = 8;
|
||||
|
||||
local SORT_ORDER_LABEL_TEXTS = {
|
||||
{
|
||||
label = 'Title',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}, {label = 'Score', asc = 'Worst to best', dsc = 'Best to worst'},
|
||||
{label = 'Date', asc = 'Oldest to newest', dsc = 'Newest to oldest'},
|
||||
{label = 'Badge', asc = 'None to D to S', dsc = 'S to D to None'}, {
|
||||
label = 'Artist',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}, {
|
||||
label = 'Effector',
|
||||
asc = '# to A to Z to かな to 漢字',
|
||||
dsc = '漢字 to かな to Z to A to #'
|
||||
}
|
||||
}
|
||||
|
||||
local transitionEnterReverse = false;
|
||||
local transitionEnterScale = 0;
|
||||
local transitionEnterOffsetX = 0;
|
||||
|
||||
local previousActiveState = false;
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16 --+ 0.0035
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
|
||||
local resolutionChange = function(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
|
||||
game.Log('resX:' .. resX .. ' // resY:' .. resY .. ' // fullX:' .. fullX .. ' // fullY:' .. fullY, game.LOGGER_ERROR);
|
||||
end
|
||||
|
||||
function tableContains(table, value)
|
||||
for i, v in ipairs(table) do if v == value then return true end end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function drawButton(i, f, x, y)
|
||||
local spaceAfter = (FONT_SIZE + MARGIN)
|
||||
|
||||
local sortOrder = 'asc';
|
||||
if (string.find(f, 'v')) then sortOrder = 'dsc' end
|
||||
|
||||
local label = f:gsub(' ^', '')
|
||||
label = label:gsub(' v', '')
|
||||
|
||||
if (string.find(sorts[selection], label) and sorts[selection] ~= f) then
|
||||
-- If there is a button active with the same label, but different sort order, don't render this one
|
||||
return 0;
|
||||
else
|
||||
-- If there is no active button with this label, if one with a label was already rendered, don't render this one
|
||||
if (tableContains(renderedButtonLabels, label)) then return 0; end
|
||||
table.insert(renderedButtonLabels, label);
|
||||
end
|
||||
|
||||
if (i == selection) then
|
||||
local ascLabelText = 'Ascending'
|
||||
local dscLabelText = 'Descending'
|
||||
|
||||
for i, obj in ipairs(SORT_ORDER_LABEL_TEXTS) do
|
||||
if (obj.label == label) then
|
||||
ascLabelText = obj.asc;
|
||||
dscLabelText = obj.dsc;
|
||||
end
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x - 182, y - 38, 365, 82, activeItemBgImage, 1, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
if sortOrder == 'asc' then
|
||||
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 - 31, 300, 67,
|
||||
activeItemBgImage, 1, 0)
|
||||
elseif sortOrder == 'dsc' then
|
||||
gfx.ImageRect(x - 150, y + FONT_SIZE + SUB_MARGIN * 2 +
|
||||
SUB_FONT_SIZE + SUB_MARGIN - 31, 300, 67,
|
||||
activeItemBgImage, 1, 0)
|
||||
end
|
||||
|
||||
gfx.Save()
|
||||
gfx.FontSize(SUB_FONT_SIZE)
|
||||
gfx.Text(ascLabelText, x, y + FONT_SIZE + SUB_MARGIN * 2);
|
||||
gfx.Text(dscLabelText, x,
|
||||
y + FONT_SIZE + SUB_MARGIN * 2 + SUB_FONT_SIZE + SUB_MARGIN);
|
||||
gfx.Restore()
|
||||
|
||||
spaceAfter = spaceAfter + SUB_FONT_SIZE * 2 + SUB_MARGIN * 4;
|
||||
end
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.Text(label, x, y);
|
||||
|
||||
return spaceAfter;
|
||||
end
|
||||
|
||||
function tickTransitions(deltaTime)
|
||||
-- ENTRY TRANSITION
|
||||
if transitionEnterReverse then
|
||||
if transitionEnterScale > 0 then
|
||||
transitionEnterScale = transitionEnterScale - deltaTime / 0.25 -- transition should last for that time in seconds
|
||||
else
|
||||
transitionEnterScale = 0
|
||||
end
|
||||
else
|
||||
if transitionEnterScale < 1 then
|
||||
transitionEnterScale = transitionEnterScale + deltaTime / 0.25 -- transition should last for that time in seconds
|
||||
else
|
||||
transitionEnterScale = 1
|
||||
end
|
||||
end
|
||||
|
||||
transitionEnterOffsetX = Easing.inOutQuad(1 - transitionEnterScale) * 416
|
||||
end
|
||||
|
||||
local drawSortWheel = function (x,y,w,h, deltaTime)
|
||||
gfx.Scissor(x,y,w,h);
|
||||
gfx.Translate(x,y);
|
||||
gfx.Scale(w/1080, h/1920);
|
||||
gfx.Scissor(0,0,1080,1920);
|
||||
|
||||
-- Draw the dark overlay above song wheel
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0, 0, 0, math.floor(transitionEnterScale * 192));
|
||||
gfx.Rect(0, 0, 1080, 1920);
|
||||
gfx.Fill();
|
||||
|
||||
-- Draw the panel background
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(desw - 416 + transitionEnterOffsetX, 0, 416, desh,
|
||||
panelBgImage, 1, 0)
|
||||
|
||||
gfx.LoadSkinFont("Digital-Serial-Bold.ttf");
|
||||
gfx.FontSize(FONT_SIZE);
|
||||
gfx.FillColor(255, 255, 255, 255);
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
|
||||
gfx.GlobalAlpha(transitionEnterScale)
|
||||
|
||||
-- Starting position of the first sort option
|
||||
local x = 889 + transitionEnterOffsetX;
|
||||
local y = desh / 2 - -- Center point
|
||||
(#sorts / 2 / 2) * (FONT_SIZE + MARGIN) - -- Space taken up by half the sort options (we remove the duplicate one)
|
||||
((SUB_FONT_SIZE * 2 + SUB_MARGIN * 4) / 2); -- Space for taken by order options
|
||||
|
||||
-- Draw the title image
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(x - 72, y - 27, 144, 54, titleTextImage, 1, 0)
|
||||
y = y + (54 + MARGIN)
|
||||
|
||||
-- Draw all the sorting options
|
||||
for i, f in ipairs(sorts) do
|
||||
local spaceAfter = drawButton(i, f, x, y);
|
||||
y = y + spaceAfter;
|
||||
end
|
||||
end
|
||||
|
||||
function setSkinSetting()
|
||||
for i, f in ipairs(sorts) do
|
||||
if i == selection then
|
||||
local label = f:gsub(' ^', '')
|
||||
label = label:gsub(' v', '')
|
||||
|
||||
game.SetSkinSetting('_songWheelActiveSortOptionLabel', label);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function render(deltaTime, shown)
|
||||
gfx.Save()
|
||||
gfx.ResetTransform()
|
||||
renderedButtonLabels = {};
|
||||
|
||||
|
||||
if (shown ~= previousActiveState) then
|
||||
if (shown) then
|
||||
game.PlaySample('sort_wheel/enter.wav');
|
||||
else
|
||||
game.PlaySample('sort_wheel/leave.wav');
|
||||
end
|
||||
|
||||
previousActiveState = shown;
|
||||
end
|
||||
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution();
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
if not shown then
|
||||
transitionEnterReverse = true
|
||||
if (transitionEnterScale > 0) then drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime) end
|
||||
else
|
||||
transitionEnterReverse = false
|
||||
drawSortWheel((resX - fullX) / 2, 0, fullX, fullY, deltaTime)
|
||||
end
|
||||
tickTransitions(deltaTime)
|
||||
setSkinSetting();
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
function set_selection(index) selection = index end
|
||||
|
|
|
@ -1,215 +1,214 @@
|
|||
|
||||
local common = require('common.util');
|
||||
local Sound = require("common.sound")
|
||||
local Numbers = require('components.numbers')
|
||||
|
||||
game.LoadSkinSample('song_transition_screen/transition_enter.wav');
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
|
||||
local bgImage = gfx.CreateSkinImage("songtransition/bg.png", 0)
|
||||
local glowOverlayImage = gfx.CreateSkinImage("songtransition/glowy.png", 0)
|
||||
local frameOverlayImage = gfx.CreateSkinImage("songtransition/frames.png", 0)
|
||||
|
||||
local albumBgImage = gfx.CreateSkinImage("songtransition/album_crop.png", 0)
|
||||
local infoOverlayPanel = gfx.CreateSkinImage("songtransition/info_panels_crop.png", 0)
|
||||
|
||||
local linkedHexagonsImage = gfx.CreateSkinImage("songtransition/linked_hexagons_crop.png", 0)
|
||||
local hexagonImages = {
|
||||
gfx.CreateSkinImage("songtransition/hex1.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/hex2.png", 0)
|
||||
}
|
||||
|
||||
local difficultyNumbers;
|
||||
|
||||
local difficultyLabelImages = {
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/nov.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/adv.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/exh.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/mxm.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/inf.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
|
||||
}
|
||||
|
||||
local timer = 0
|
||||
local transitionProgress = 0;
|
||||
local outProgress = 0
|
||||
|
||||
local flickerTime = 0.050 --seconds (50ms)
|
||||
|
||||
-- Window variables
|
||||
local resX, resY = game.GetResolution()
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
|
||||
local noJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
|
||||
local wasEnterSfxPlayed = false;
|
||||
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
if not wasEnterSfxPlayed then
|
||||
Sound.stopMusic();
|
||||
game.PlaySample('song_transition_screen/transition_enter.wav');
|
||||
wasEnterSfxPlayed = true;
|
||||
end
|
||||
if not difficultyNumbers then
|
||||
difficultyNumbers = Numbers.load_number_image('diff_num')
|
||||
end
|
||||
|
||||
local x_offset = (resX - fullX) / 2
|
||||
local y_offset = 0
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
gfx.Rect(0, 0, resX, resY)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
|
||||
gfx.Fill()
|
||||
|
||||
gfx.Translate(x_offset, y_offset);
|
||||
gfx.Scale(fullX / 1080, fullY / 1920);
|
||||
gfx.Scissor(0, 0, 1080, 1920);
|
||||
|
||||
render_screen();
|
||||
|
||||
transitionProgress = transitionProgress + deltaTime * 0.2
|
||||
transitionProgress = math.min(transitionProgress,1)
|
||||
|
||||
if transitionProgress < 0.25 then
|
||||
local whiteAlpha = math.max(0, (1-transitionProgress/0.25))
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(255,255,255,math.floor(255*whiteAlpha));
|
||||
gfx.Rect(0,0,desw,desh);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath();
|
||||
end
|
||||
|
||||
if transitionProgress > 0.85 then
|
||||
local blackAlpha = math.min(1, ((transitionProgress-0.85)/0.15))
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0,0,0,math.floor(255*blackAlpha));
|
||||
gfx.Rect(0,0,desw,desh);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath();
|
||||
end
|
||||
|
||||
timer = timer + deltaTime
|
||||
return transitionProgress >= 1
|
||||
end
|
||||
|
||||
function render_out(deltaTime)
|
||||
outProgress = outProgress + deltaTime * 0.2
|
||||
outProgress = math.min(outProgress, 1)
|
||||
|
||||
timer = timer + deltaTime
|
||||
return outProgress >= 1;
|
||||
end
|
||||
|
||||
function sign(x)
|
||||
return x>0 and 1 or x<0 and -1 or 0
|
||||
end
|
||||
|
||||
function render_screen()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, 1080, 1920, bgImage,1,0);
|
||||
|
||||
if transitionProgress < 0.35 then
|
||||
local hex1alpha = math.max(0, (1-transitionProgress/0.35))
|
||||
local hex2alpha = math.max(0, (1-transitionProgress/0.3))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0, desw, desh, hexagonImages[1], hex1alpha, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0, desw, desh, hexagonImages[2], hex2alpha, 0)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0,1080,1920,frameOverlayImage,1,0);
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, 1080, 1920, glowOverlayImage,1,0);
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(37.5, 1074, 1180*0.85, 343*0.85, infoOverlayPanel, 1, 0);
|
||||
|
||||
if (timer % flickerTime) < (flickerTime / 2) then --flicker with 20Hz (50ms), 50% duty cycle
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(37.5, 1074, 1180*0.85, 189*0.85, linkedHexagonsImage, 0.1, 0);
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(10, 195.5, 1060, 1015, albumBgImage,1,0);
|
||||
|
||||
local jacket = song.jacket == 0 and noJacket or song.jacket
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(235, 385, 608, 608, jacket, 1, 0)
|
||||
gfx.ClosePath();
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
gfx.FontSize(55)
|
||||
gfx.Text(song.title,desw/2, 1114)
|
||||
|
||||
gfx.FontSize(30)
|
||||
gfx.Text(song.artist, desw/2 , 1182)
|
||||
|
||||
|
||||
local EFFECTOR_LABEL_Y = 1288
|
||||
local ILLUSTRATOR_LABEL_Y = 1347
|
||||
|
||||
gfx.FontSize(22)
|
||||
|
||||
gfx.Text(song.effector, desw/2+70 , EFFECTOR_LABEL_Y-1)
|
||||
gfx.Text(song.illustrator, desw/2+70 , ILLUSTRATOR_LABEL_Y-3)
|
||||
|
||||
-- Draw song diff level
|
||||
gfx.BeginPath();
|
||||
Numbers.draw_number(933, 1140, 1.0, song.level, 2, difficultyNumbers, false, 1, 1)
|
||||
|
||||
-- Draw song diff label (NOV/ADV/EXH/MXM/etc.)
|
||||
gfx.BeginPath();
|
||||
local diffLabelImage = difficultyLabelImages[song.difficulty+1];
|
||||
local diffLabelW, diffLabelH = gfx.ImageSize(diffLabelImage);
|
||||
gfx.ImageRect(952-diffLabelW/2, 1154-diffLabelH/2, diffLabelW, diffLabelH, diffLabelImage,1,0);
|
||||
gfx.ClosePath();
|
||||
|
||||
gfx.Save();
|
||||
gfx.FontSize(24)
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath();
|
||||
|
||||
gfx.Text('BPM', 127, 1140)
|
||||
gfx.Text(song.bpm, 127, 1167)
|
||||
|
||||
-- temp ref overlay
|
||||
-- gfx.BeginPath()
|
||||
-- gfx.ImageRect(0, 0, 1080, 1920, refBgImage,0.5,0);
|
||||
|
||||
gfx.ClosePath();
|
||||
gfx.Restore();
|
||||
end
|
||||
|
||||
function reset()
|
||||
transitionProgress = 0
|
||||
resX, resY = game.GetResolution()
|
||||
fullX = portraitWidescreenRatio * resY
|
||||
fullY = resY
|
||||
outProgress = 0
|
||||
wasEnterSfxPlayed = false;
|
||||
|
||||
local common = require('common.common');
|
||||
local Numbers = require('common.numbers')
|
||||
|
||||
game.LoadSkinSample('song_transition_screen/transition_enter.wav');
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
|
||||
local bgImage = gfx.CreateSkinImage("songtransition/bg.png", 0)
|
||||
local glowOverlayImage = gfx.CreateSkinImage("songtransition/glowy.png", 0)
|
||||
local frameOverlayImage = gfx.CreateSkinImage("songtransition/frames.png", 0)
|
||||
|
||||
local albumBgImage = gfx.CreateSkinImage("songtransition/album_crop.png", 0)
|
||||
local infoOverlayPanel = gfx.CreateSkinImage("songtransition/info_panels_crop.png", 0)
|
||||
|
||||
local linkedHexagonsImage = gfx.CreateSkinImage("songtransition/linked_hexagons_crop.png", 0)
|
||||
local hexagonImages = {
|
||||
gfx.CreateSkinImage("songtransition/hex1.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/hex2.png", 0)
|
||||
}
|
||||
|
||||
local difficultyNumbers;
|
||||
|
||||
local difficultyLabelImages = {
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/nov.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/adv.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/exh.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/mxm.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/inf.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/grv.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/hvn.png", 0),
|
||||
gfx.CreateSkinImage("songtransition/difficulty_labels/vvd.png", 0),
|
||||
}
|
||||
|
||||
local timer = 0
|
||||
local transitionProgress = 0;
|
||||
local outProgress = 0
|
||||
|
||||
local flickerTime = 0.050 --seconds (50ms)
|
||||
|
||||
-- Window variables
|
||||
local resX, resY = game.GetResolution()
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
|
||||
local noJacket = gfx.CreateSkinImage("song_select/loading.png", 0)
|
||||
|
||||
local wasEnterSfxPlayed = false;
|
||||
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
if not wasEnterSfxPlayed then
|
||||
common.stopMusic();
|
||||
game.PlaySample('song_transition_screen/transition_enter.wav');
|
||||
wasEnterSfxPlayed = true;
|
||||
end
|
||||
if not difficultyNumbers then
|
||||
difficultyNumbers = Numbers.load_number_image('diff_num')
|
||||
end
|
||||
|
||||
local x_offset = (resX - fullX) / 2
|
||||
local y_offset = 0
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
gfx.Rect(0, 0, resX, resY)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
|
||||
gfx.Fill()
|
||||
|
||||
gfx.Translate(x_offset, y_offset);
|
||||
gfx.Scale(fullX / 1080, fullY / 1920);
|
||||
gfx.Scissor(0, 0, 1080, 1920);
|
||||
|
||||
render_screen();
|
||||
|
||||
transitionProgress = transitionProgress + deltaTime * 0.2
|
||||
transitionProgress = math.min(transitionProgress,1)
|
||||
|
||||
if transitionProgress < 0.25 then
|
||||
local whiteAlpha = math.max(0, (1-transitionProgress/0.25))
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(255,255,255,math.floor(255*whiteAlpha));
|
||||
gfx.Rect(0,0,desw,desh);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath();
|
||||
end
|
||||
|
||||
if transitionProgress > 0.85 then
|
||||
local blackAlpha = math.min(1, ((transitionProgress-0.85)/0.15))
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(0,0,0,math.floor(255*blackAlpha));
|
||||
gfx.Rect(0,0,desw,desh);
|
||||
gfx.Fill();
|
||||
gfx.ClosePath();
|
||||
end
|
||||
|
||||
timer = timer + deltaTime
|
||||
return transitionProgress >= 1
|
||||
end
|
||||
|
||||
function render_out(deltaTime)
|
||||
outProgress = outProgress + deltaTime * 0.2
|
||||
outProgress = math.min(outProgress, 1)
|
||||
|
||||
timer = timer + deltaTime
|
||||
return outProgress >= 1;
|
||||
end
|
||||
|
||||
function sign(x)
|
||||
return x>0 and 1 or x<0 and -1 or 0
|
||||
end
|
||||
|
||||
function render_screen()
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, 1080, 1920, bgImage,1,0);
|
||||
|
||||
if transitionProgress < 0.35 then
|
||||
local hex1alpha = math.max(0, (1-transitionProgress/0.35))
|
||||
local hex2alpha = math.max(0, (1-transitionProgress/0.3))
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0, desw, desh, hexagonImages[1], hex1alpha, 0)
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0, desw, desh, hexagonImages[2], hex2alpha, 0)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0,0,1080,1920,frameOverlayImage,1,0);
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(0, 0, 1080, 1920, glowOverlayImage,1,0);
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(37.5, 1074, 1180*0.85, 343*0.85, infoOverlayPanel, 1, 0);
|
||||
|
||||
if (timer % flickerTime) < (flickerTime / 2) then --flicker with 20Hz (50ms), 50% duty cycle
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(37.5, 1074, 1180*0.85, 189*0.85, linkedHexagonsImage, 0.1, 0);
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
gfx.ImageRect(10, 195.5, 1060, 1015, albumBgImage,1,0);
|
||||
|
||||
local jacket = song.jacket == 0 and noJacket or song.jacket
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(235, 385, 608, 608, jacket, 1, 0)
|
||||
gfx.ClosePath();
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
|
||||
gfx.FontSize(55)
|
||||
gfx.Text(song.title,desw/2, 1114)
|
||||
|
||||
gfx.FontSize(30)
|
||||
gfx.Text(song.artist, desw/2 , 1182)
|
||||
|
||||
|
||||
local EFFECTOR_LABEL_Y = 1288
|
||||
local ILLUSTRATOR_LABEL_Y = 1347
|
||||
|
||||
gfx.FontSize(22)
|
||||
|
||||
gfx.Text(song.effector, desw/2+70 , EFFECTOR_LABEL_Y-1)
|
||||
gfx.Text(song.illustrator, desw/2+70 , ILLUSTRATOR_LABEL_Y-3)
|
||||
|
||||
-- Draw song diff level
|
||||
gfx.BeginPath();
|
||||
Numbers.draw_number(933, 1140, 1.0, song.level, 2, difficultyNumbers, false, 1, 1)
|
||||
|
||||
-- Draw song diff label (NOV/ADV/EXH/MXM/etc.)
|
||||
gfx.BeginPath();
|
||||
local diffLabelImage = difficultyLabelImages[song.difficulty+1];
|
||||
local diffLabelW, diffLabelH = gfx.ImageSize(diffLabelImage);
|
||||
gfx.ImageRect(952-diffLabelW/2, 1154-diffLabelH/2, diffLabelW, diffLabelH, diffLabelImage,1,0);
|
||||
gfx.ClosePath();
|
||||
|
||||
gfx.Save();
|
||||
gfx.FontSize(24)
|
||||
gfx.LoadSkinFont('Digital-Serial-Bold.ttf')
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.BeginPath();
|
||||
|
||||
gfx.Text('BPM', 127, 1140)
|
||||
gfx.Text(song.bpm, 127, 1167)
|
||||
|
||||
-- temp ref overlay
|
||||
-- gfx.BeginPath()
|
||||
-- gfx.ImageRect(0, 0, 1080, 1920, refBgImage,0.5,0);
|
||||
|
||||
gfx.ClosePath();
|
||||
gfx.Restore();
|
||||
end
|
||||
|
||||
function reset()
|
||||
transitionProgress = 0
|
||||
resX, resY = game.GetResolution()
|
||||
fullX = portraitWidescreenRatio * resY
|
||||
fullY = resY
|
||||
outProgress = 0
|
||||
wasEnterSfxPlayed = false;
|
||||
end
|
|
@ -0,0 +1,215 @@
|
|||
local mposx = 0;
|
||||
local mposy = 0;
|
||||
local hovered = nil;
|
||||
local cursorIndex = 1
|
||||
local buttonWidth = 250;
|
||||
local buttonHeight = 50;
|
||||
local buttonBorder = 2;
|
||||
local label = -1;
|
||||
|
||||
local gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
|
||||
gfx.GradientColors(0,127,255,255,0,128,255,0)
|
||||
local gradient = gfx.LinearGradient(0,0,0,1)
|
||||
local bgPattern = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX + gfx.IMAGE_REPEATY)
|
||||
local bgAngle = 0.5
|
||||
local bgPaint = gfx.ImagePattern(0,0, 256,256, bgAngle, bgPattern, 1.0)
|
||||
local bgPatternTimer = 0
|
||||
local cursorYs = {}
|
||||
local buttons = nil
|
||||
local resx, resy = game.GetResolution();
|
||||
|
||||
view_update = function()
|
||||
if package.config:sub(1,1) == '\\' then --windows
|
||||
updateUrl, updateVersion = game.UpdateAvailable()
|
||||
os.execute("start " .. updateUrl)
|
||||
else --unix
|
||||
--TODO: Mac solution
|
||||
os.execute("xdg-open " .. updateUrl)
|
||||
end
|
||||
end
|
||||
|
||||
mouse_clipped = function(x,y,w,h)
|
||||
return mposx > x and mposy > y and mposx < x+w and mposy < y+h;
|
||||
end;
|
||||
|
||||
draw_button = function(button, x, y)
|
||||
local name = button[1]
|
||||
local rx = x - (buttonWidth / 2);
|
||||
local ty = y - (buttonHeight / 2);
|
||||
gfx.BeginPath();
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
|
||||
gfx.FontSize(40);
|
||||
|
||||
if mouse_clipped(rx,ty, buttonWidth, buttonHeight) then
|
||||
hovered = button[2];
|
||||
r, b_g, b_b, b_a = game.GetSkinSetting("col_test")
|
||||
gfx.FillColor(0, 125, 255);
|
||||
gfx.Text(name, x+1, y+1);
|
||||
gfx.Text(name, x-1, y+1);
|
||||
gfx.Text(name, x+1, y-1);
|
||||
gfx.Text(name, x-1, y-1);
|
||||
end
|
||||
gfx.FillColor(255,255,255);
|
||||
gfx.Text(name, x, y);
|
||||
return buttonHeight + 5
|
||||
end;
|
||||
|
||||
function updateGradient()
|
||||
gr_r, gr_g, gr_b, gr_a = game.GetSkinSetting("col_test")
|
||||
if gr_r == nil then return end
|
||||
gfx.GradientColors(gr_r,gr_g,gr_b,gr_a,0,128,255,0)
|
||||
--gradient = gfx.LinearGradient(0,0,0,1)
|
||||
end
|
||||
|
||||
function updatePattern(dt)
|
||||
bgPatternTimer = (bgPatternTimer + dt) % 1.0
|
||||
local bgx = math.cos(bgAngle) * (bgPatternTimer * 256)
|
||||
local bgy = math.sin(bgAngle) * (bgPatternTimer * 256)
|
||||
gfx.UpdateImagePattern(bgPaint, bgx, bgy, 256, 256, bgAngle, 1.0)
|
||||
end
|
||||
|
||||
function setButtons()
|
||||
if buttons == nil then
|
||||
buttons = {}
|
||||
buttons[1] = {"Start", Menu.Start}
|
||||
buttons[2] = {"Multiplayer", Menu.Multiplayer}
|
||||
buttons[3] = {"Challenges", Menu.Challenges}
|
||||
buttons[4] = {"Get Songs", Menu.DLScreen}
|
||||
buttons[5] = {"Settings", Menu.Settings}
|
||||
buttons[6] = {"Exit", Menu.Exit}
|
||||
end
|
||||
end
|
||||
|
||||
local renderY = resy/2
|
||||
function draw_cursor(x,y,deltaTime)
|
||||
gfx.Save()
|
||||
gfx.BeginPath()
|
||||
|
||||
local size = 8
|
||||
|
||||
renderY = renderY - (renderY - y) * deltaTime * 30
|
||||
|
||||
gfx.MoveTo(x-size,renderY-size)
|
||||
gfx.LineTo(x,renderY)
|
||||
gfx.LineTo(x-size,renderY+size)
|
||||
|
||||
gfx.StrokeWidth(3)
|
||||
gfx.StrokeColor(255,255,255)
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
|
||||
function sign(x)
|
||||
return x>0 and 1 or x<0 and -1 or 0
|
||||
end
|
||||
|
||||
function roundToZero(x)
|
||||
if x<0 then return math.ceil(x)
|
||||
elseif x>0 then return math.floor(x)
|
||||
else return 0 end
|
||||
end
|
||||
|
||||
function deltaKnob(delta)
|
||||
if math.abs(delta) > 1.5 * math.pi then
|
||||
return delta + 2 * math.pi * sign(delta) * -1
|
||||
end
|
||||
return delta
|
||||
end
|
||||
|
||||
|
||||
|
||||
local lastKnobs = nil
|
||||
local knobProgress = 0
|
||||
function handle_controller()
|
||||
if lastKnobs == nil then
|
||||
lastKnobs = {game.GetKnob(0), game.GetKnob(1)}
|
||||
else
|
||||
local newKnobs = {game.GetKnob(0), game.GetKnob(1)}
|
||||
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2
|
||||
|
||||
lastKnobs = newKnobs
|
||||
|
||||
if math.abs(knobProgress) > 1 then
|
||||
cursorIndex = (((cursorIndex - 1) + roundToZero(knobProgress)) % #buttons) + 1
|
||||
knobProgress = knobProgress - roundToZero(knobProgress)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
render = function(deltaTime)
|
||||
setButtons()
|
||||
updateGradient()
|
||||
updatePattern(deltaTime)
|
||||
resx,resy = game.GetResolution();
|
||||
mposx,mposy = game.GetMousePos();
|
||||
gfx.Scale(resx, resy / 3)
|
||||
gfx.Rect(0,0,1,1)
|
||||
gfx.FillPaint(gradient)
|
||||
gfx.Fill()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.Scale(0.5,0.5)
|
||||
gfx.Rect(0,0,resx * 2,resy * 2)
|
||||
gfx.GlobalCompositeOperation(gfx.BLEND_OP_DESTINATION_IN)
|
||||
gfx.FillPaint(bgPaint)
|
||||
gfx.Fill()
|
||||
gfx.ResetTransform()
|
||||
gfx.BeginPath()
|
||||
gfx.GlobalCompositeOperation(gfx.BLEND_OP_SOURCE_OVER)
|
||||
|
||||
cursorGet = 1
|
||||
buttonY = resy / 2;
|
||||
hovered = nil;
|
||||
|
||||
gfx.LoadSkinFont("NotoSans-Regular.ttf");
|
||||
|
||||
for i=1,#buttons do
|
||||
cursorYs[i] = buttonY
|
||||
buttonY = buttonY + draw_button(buttons[i], resx / 2, buttonY);
|
||||
if hovered == buttons[i][2] then
|
||||
cursorIndex = i
|
||||
end
|
||||
end
|
||||
|
||||
handle_controller()
|
||||
|
||||
draw_cursor(resx/2 - 100, cursorYs[cursorIndex], deltaTime)
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.FillColor(255,255,255);
|
||||
gfx.FontSize(120);
|
||||
if label == -1 then
|
||||
label = gfx.CreateLabel("ExperimentalGear ALPHA 1.8.7 ''README.TXT''", 120, 0);
|
||||
end
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE);
|
||||
gfx.DrawLabel(label, resx / 2, resy / 2 - 200, resx-40);
|
||||
updateUrl, updateVersion = game.UpdateAvailable()
|
||||
if updateUrl then
|
||||
gfx.BeginPath()
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_BOTTOM + gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.FontSize(30)
|
||||
gfx.Text(string.format("Version %s is now available", updateVersion), 5, resy - buttonHeight - 10)
|
||||
draw_button({"View", view_update}, buttonWidth / 2 + 5, resy - buttonHeight / 2 - 5);
|
||||
draw_button({"Update", Menu.Update}, buttonWidth * 1.5 + 15, resy - buttonHeight / 2 - 5)
|
||||
end
|
||||
end;
|
||||
|
||||
mouse_pressed = function(button)
|
||||
if hovered then
|
||||
hovered()
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function button_pressed(button)
|
||||
if button == game.BUTTON_STA then
|
||||
buttons[cursorIndex][2]()
|
||||
elseif button == game.BUTTON_BCK then
|
||||
Menu.Exit()
|
||||
end
|
||||
end
|
|
@ -1,97 +1,446 @@
|
|||
require("common.globals")
|
||||
local Common = require("common.util")
|
||||
|
||||
local bootScreen = require('titlescreen.boot')
|
||||
local splashScreen = require('titlescreen.splash')
|
||||
local titleScreen = require('titlescreen.title')
|
||||
local modeSelectScreen = require('titlescreen.modeselect')
|
||||
local serviceScreen = require('titlescreen.service')
|
||||
require('common')
|
||||
local Footer = require('components.footer');
|
||||
local Background = require('components.background');
|
||||
|
||||
local screens = {
|
||||
boot = {
|
||||
screen = bootScreen
|
||||
},
|
||||
splash = {
|
||||
screen = splashScreen
|
||||
},
|
||||
title = {
|
||||
screen = titleScreen
|
||||
},
|
||||
mode_select = {
|
||||
screen = modeSelectScreen
|
||||
},
|
||||
service = {
|
||||
screen = serviceScreen
|
||||
local lang = require("language.call")
|
||||
|
||||
local cursorIndex = 3;
|
||||
local buttonHeight = 128 + 16;
|
||||
|
||||
local SELECTOR_BAR_OFFSET_FROM_CENTER = 128;
|
||||
|
||||
local BAR_ALPHA = 191;
|
||||
local HEADER_HEIGHT = 100
|
||||
|
||||
local buttons = nil
|
||||
local resx, resy = game.GetResolution()
|
||||
local desw = 1080
|
||||
local desh = 1920
|
||||
local scale;
|
||||
|
||||
local backgroundImage = gfx.CreateSkinImage("bg_pattern.png", gfx.IMAGE_REPEATX | gfx.IMAGE_REPEATY)
|
||||
local headerTitleImage = gfx.CreateSkinImage('titlescreen/title.png', 0);
|
||||
local selectorBgImage = gfx.CreateSkinImage('titlescreen/selector_bg.png', 0);
|
||||
local selectorArrowsImage = gfx.CreateSkinImage(
|
||||
'titlescreen/selector_arrows.png', 0);
|
||||
|
||||
local unselectedButtonImage = gfx.CreateSkinImage(
|
||||
'titlescreen/unselected_button.png', 0);
|
||||
|
||||
local selectedButtonBgImage = gfx.CreateSkinImage(
|
||||
'titlescreen/selected_button_bg.png', 0);
|
||||
local selectedButtonOverImage = gfx.CreateSkinImage(
|
||||
'titlescreen/selected_button_over.png', 0);
|
||||
|
||||
local skillLabelImage = gfx.CreateSkinImage('titlescreen/labels/skill.png', 0);
|
||||
local friendLabelImage = gfx.CreateSkinImage('titlescreen/labels/friend.png', 0);
|
||||
local normalLabelImage = gfx.CreateSkinImage('titlescreen/labels/normal.png', 0);
|
||||
local nauticaLabelImage = gfx.CreateSkinImage('titlescreen/labels/nautica.png',
|
||||
0);
|
||||
local settingsLabelImage = gfx.CreateSkinImage(
|
||||
'titlescreen/labels/settings.png', 0);
|
||||
local exitLabelImage = gfx.CreateSkinImage('titlescreen/labels/exit.png', 0);
|
||||
|
||||
local creww = game.GetSkinSetting("single_idol")
|
||||
|
||||
-- ANIMS
|
||||
local idolAnimation = gfx.LoadSkinAnimation('crew/anim/'..creww, 1 / 30, 0, true);
|
||||
|
||||
-- AUDIO
|
||||
game.LoadSkinSample('titlescreen/bgm.wav');
|
||||
game.LoadSkinSample('titlescreen/cursor_change.wav');
|
||||
game.LoadSkinSample('titlescreen/cursor_select.wav');
|
||||
|
||||
local selectorDescriptionLabel = gfx.CreateLabel(lang.Start.desc , 22, 0);
|
||||
|
||||
local selectorLegendScrollLabel = gfx.CreateLabel(lang.Start.sc , 20, 0);
|
||||
local selectorLegendSelectLabel = gfx.CreateLabel(lang.Start.st3 , 20, 0);
|
||||
|
||||
local scrollTransitionScale = 1; -- Goes from 0 to 1 when transition is happening, sits at 1 when it's not.
|
||||
local buttonsMovementScale = 0; -- Basically same as `scrollTransitionScale` but with a +/- sign for the scroll direction and goes from 1 to 0
|
||||
|
||||
local idolAnimTransitionScale = 0;
|
||||
|
||||
local oldCursorIndex = 3;
|
||||
local scrollingUp = false;
|
||||
local playedBgm = false;
|
||||
|
||||
-- Window variables
|
||||
local resX, resY
|
||||
|
||||
-- Aspect Ratios
|
||||
local landscapeWidescreenRatio = 16 / 9
|
||||
local landscapeStandardRatio = 4 / 3
|
||||
local portraitWidescreenRatio = 9 / 16
|
||||
|
||||
-- Portrait sizes
|
||||
local fullX, fullY
|
||||
|
||||
local resolutionChange = function(x, y)
|
||||
resX = x
|
||||
resY = y
|
||||
fullX = portraitWidescreenRatio * y
|
||||
fullY = y
|
||||
end
|
||||
|
||||
function resetLayoutInformation()
|
||||
resx, resy = game.GetResolution()
|
||||
desw = 1080
|
||||
desh = 1920
|
||||
scale = resx / desw
|
||||
end
|
||||
|
||||
draw_button = function(button, x, y, selected, index)
|
||||
local labelImage = button[1];
|
||||
local labelWidth = button[2];
|
||||
local descriptionText = button[4];
|
||||
|
||||
if (selected) then
|
||||
-- Draw button background
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x, y + (196 / 2 * (1 - scrollTransitionScale)), 505,
|
||||
196 * scrollTransitionScale, selectedButtonBgImage, 1, 0);
|
||||
-- Draw button main label
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x + 256 - (labelWidth / 2),
|
||||
(y + 58) + (64 / 2 * (1 - scrollTransitionScale)),
|
||||
labelWidth, 64 * scrollTransitionScale, labelImage, 1, 0);
|
||||
|
||||
-- Draw description
|
||||
|
||||
gfx.GlobalAlpha((scrollTransitionScale - 0.8) * 5)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_CENTER + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(40);
|
||||
gfx.BeginPath();
|
||||
gfx.Text(descriptionText, x + 256, y + 28);
|
||||
gfx.GlobalAlpha(1)
|
||||
|
||||
-- Draw the glow overlay
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x + 2, (y - 42) + (277 / 2 * (1 - scrollTransitionScale)),
|
||||
501, 277 * scrollTransitionScale, selectedButtonOverImage,
|
||||
1, 0);
|
||||
else
|
||||
if scrollingUp then
|
||||
if (index == 3 or index == 0) then
|
||||
gfx.GlobalAlpha(1 - scrollTransitionScale);
|
||||
end
|
||||
if (index == 2 or index == 5) then
|
||||
gfx.GlobalAlpha(scrollTransitionScale);
|
||||
end
|
||||
else
|
||||
if (index == 3 or index == 6) then
|
||||
gfx.GlobalAlpha(1 - scrollTransitionScale);
|
||||
end
|
||||
if (index == 1 or index == 4) then
|
||||
gfx.GlobalAlpha(scrollTransitionScale);
|
||||
end
|
||||
end
|
||||
-- Draw button background
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x, y + buttonsMovementScale * buttonHeight, 1026 / 2,
|
||||
257 / 2, unselectedButtonImage, 1, 0);
|
||||
|
||||
-- Draw button main label
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(x + 64, y + 28 + buttonsMovementScale * buttonHeight,
|
||||
labelWidth, 64, labelImage, 1, 0);
|
||||
-- Draw description
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE)
|
||||
gfx.FontSize(28);
|
||||
gfx.BeginPath();
|
||||
gfx.Text(descriptionText, x + 64,
|
||||
y + 18 + buttonsMovementScale * buttonHeight);
|
||||
|
||||
gfx.GlobalAlpha(1)
|
||||
end
|
||||
end;
|
||||
|
||||
draw_buttons = function()
|
||||
indexes = {
|
||||
getCorrectedButtonIndex(cursorIndex, -2),
|
||||
getCorrectedButtonIndex(cursorIndex, -1), cursorIndex,
|
||||
getCorrectedButtonIndex(cursorIndex, 1),
|
||||
getCorrectedButtonIndex(cursorIndex, 2)
|
||||
}
|
||||
}
|
||||
|
||||
local currentScreen = game.GetSkinSetting("animations_skipIntro") and screens.title or screens.boot -- show boot screen if skipIntro is not set
|
||||
local yBase = desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER;
|
||||
|
||||
local function deltaKnob(delta)
|
||||
centerButtonY = yBase - buttonHeight / 2 - 28; -- to fit with the selector bg
|
||||
marginFromDesHCenter = 128;
|
||||
|
||||
if scrollingUp then
|
||||
draw_button(buttons[indexes[5]], desw - 512,
|
||||
yBase - marginFromDesHCenter - buttonHeight * 3, false, 0); -- Placeholder for fadeout transition
|
||||
end
|
||||
|
||||
draw_button(buttons[indexes[1]], desw - 512,
|
||||
yBase - marginFromDesHCenter - buttonHeight * 2, false, 1);
|
||||
draw_button(buttons[indexes[2]], desw - 512,
|
||||
yBase - marginFromDesHCenter - buttonHeight, false, 2);
|
||||
|
||||
draw_button(buttons[indexes[3]], desw - 512, centerButtonY, true); -- The main selected center button
|
||||
|
||||
if scrollingUp then
|
||||
draw_button(buttons[indexes[3]], desw - 512,
|
||||
yBase + marginFromDesHCenter - buttonHeight, false, 3); -- Placeholder for transition that goes to the bottom
|
||||
else
|
||||
draw_button(buttons[indexes[3]], desw - 512, centerButtonY, false, 3); -- Placeholder for transition that goes to the top
|
||||
end
|
||||
|
||||
draw_button(buttons[indexes[4]], desw - 512,
|
||||
yBase + marginFromDesHCenter + 10, false, 4);
|
||||
draw_button(buttons[indexes[5]], desw - 512,
|
||||
yBase + marginFromDesHCenter + buttonHeight + 10, false, 5);
|
||||
|
||||
if not scrollingUp then
|
||||
draw_button(buttons[indexes[1]], desw - 512,
|
||||
yBase + marginFromDesHCenter + buttonHeight * 2, false, 6);
|
||||
end
|
||||
end;
|
||||
|
||||
function getCorrectedButtonIndex(from, offset)
|
||||
buttonsNum = #buttons;
|
||||
|
||||
index = from + offset;
|
||||
|
||||
if index < 1 then
|
||||
index = buttonsNum + (from + offset) -- this only happens if the offset is negative
|
||||
end
|
||||
|
||||
if index > buttonsNum then
|
||||
indexesUntilEnd = buttonsNum - from;
|
||||
index = offset - indexesUntilEnd -- this only happens if the offset is positive
|
||||
end
|
||||
|
||||
return index;
|
||||
end
|
||||
|
||||
function drawTexts()
|
||||
|
||||
currentFullDescriptionText = buttons[cursorIndex][5];
|
||||
gfx.BeginPath();
|
||||
gfx.UpdateLabel(selectorDescriptionLabel, currentFullDescriptionText, 22)
|
||||
|
||||
gfx.BeginPath();
|
||||
-- gfx.UpdateLabel(selectorLegendScrollLabel, 'SCROLL', 20);
|
||||
|
||||
-- descriptionAlpha = math.abs(selectedButtonScaleY - 0.5) * 2;
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT + gfx.TEXT_ALIGN_MIDDLE);
|
||||
|
||||
-- Description
|
||||
gfx.FillColor(255, 255, 255, math.floor(scrollTransitionScale * 255));
|
||||
gfx.BeginPath();
|
||||
gfx.DrawLabel(selectorDescriptionLabel, 64,
|
||||
desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER - 52);
|
||||
|
||||
-- Legend on the selector
|
||||
gfx.FillColor(217, 177, 126);
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.DrawLabel(selectorLegendScrollLabel, 118,
|
||||
desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER + 56);
|
||||
|
||||
gfx.BeginPath();
|
||||
gfx.DrawLabel(selectorLegendSelectLabel, 360,
|
||||
desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER + 56);
|
||||
|
||||
gfx.FillColor(255, 255, 255);
|
||||
end
|
||||
|
||||
function setButtons()
|
||||
if buttons == nil then
|
||||
buttons = {}
|
||||
buttons[1] = {
|
||||
skillLabelImage, 412, Menu.Challenges,
|
||||
lang.Challanges.ch, lang.Challanges.ch1
|
||||
}
|
||||
buttons[2] = {
|
||||
friendLabelImage, 169, Menu.Multiplayer,
|
||||
lang.Multiplayer.mp, lang.Multiplayer.mp2
|
||||
}
|
||||
buttons[3] = {
|
||||
normalLabelImage, 210, Menu.Start,
|
||||
lang.Start.st, lang.Start.st2
|
||||
}
|
||||
buttons[4] = {
|
||||
nauticaLabelImage, 230, Menu.DLScreen,
|
||||
lang.Nautica.dls, lang.Nautica.dls2
|
||||
}
|
||||
buttons[5] = {
|
||||
settingsLabelImage, 247, Menu.Settings,
|
||||
lang.Settings.se, lang.Settings.se1
|
||||
}
|
||||
buttons[6] = {
|
||||
exitLabelImage, 110, Menu.Exit,
|
||||
lang.Exit.ex, lang.Exit.ex2
|
||||
}
|
||||
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, headerTitleImage, 1, 0)
|
||||
end
|
||||
|
||||
function sign(x) return x > 0 and 1 or x < 0 and -1 or 0 end
|
||||
|
||||
function roundToZero(x)
|
||||
if x < 0 then
|
||||
return math.ceil(x)
|
||||
elseif x > 0 then
|
||||
return math.floor(x)
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function deltaKnob(delta)
|
||||
if math.abs(delta) > 1.5 * math.pi then
|
||||
return delta + 2 * math.pi * Common.sign(delta) * -1
|
||||
return delta + 2 * math.pi * sign(delta) * -1
|
||||
end
|
||||
return delta
|
||||
end
|
||||
|
||||
local lastKnobs = nil
|
||||
local knobProgress = 0
|
||||
local function handleKnobs()
|
||||
if not currentScreen.screen.onKnobsChange then
|
||||
return
|
||||
end
|
||||
|
||||
function handle_controller()
|
||||
if lastKnobs == nil then
|
||||
lastKnobs = {game.GetKnob(0), game.GetKnob(1)}
|
||||
else
|
||||
local newKnobs = {game.GetKnob(0), game.GetKnob(1)}
|
||||
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) * 1.2
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) * 1.2
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[1] - newKnobs[1]) *
|
||||
1.2
|
||||
knobProgress = knobProgress - deltaKnob(lastKnobs[2] - newKnobs[2]) *
|
||||
1.2
|
||||
|
||||
lastKnobs = newKnobs
|
||||
|
||||
if math.abs(knobProgress) > 1 then
|
||||
if (knobProgress < 0) then
|
||||
-- Negative
|
||||
currentScreen.screen.onKnobsChange(-1)
|
||||
else
|
||||
-- Positive
|
||||
currentScreen.screen.onKnobsChange(1)
|
||||
cursorIndex = (((cursorIndex - 1) + roundToZero(knobProgress)) %
|
||||
#buttons) + 1
|
||||
|
||||
scrollTransitionScale = 0; -- Reset transitions and play them
|
||||
|
||||
scrollingUp = false;
|
||||
if ((cursorIndex > oldCursorIndex and
|
||||
not (cursorIndex == 6 and oldCursorIndex == 1)) or
|
||||
(cursorIndex == 1 and oldCursorIndex == 6)) then
|
||||
scrollingUp = true;
|
||||
end
|
||||
knobProgress = knobProgress - Common.roundToZero(knobProgress)
|
||||
|
||||
game.PlaySample('titlescreen/cursor_change.wav');
|
||||
|
||||
oldCursorIndex = cursorIndex;
|
||||
|
||||
knobProgress = knobProgress - roundToZero(knobProgress)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handleScreenResponse(res)
|
||||
if res and res.eventType == 'switch' then
|
||||
if not screens[res.toScreen] then
|
||||
game.Log('Undefined screen ' .. res.toScreen, game.LOGGER_ERROR)
|
||||
return
|
||||
end
|
||||
currentScreen = screens[res.toScreen]
|
||||
if currentScreen.screen.reset then
|
||||
currentScreen.screen.reset()
|
||||
draw_titlescreen = function (x, y, w, h, deltaTime)
|
||||
gfx.Scissor(x,y,w,h);
|
||||
gfx.Translate(x,y);
|
||||
gfx.Scale(w/1080, h/1920);
|
||||
|
||||
gfx.LoadSkinFont("segoeui.ttf")
|
||||
|
||||
-- Draw background
|
||||
gfx.BeginPath();
|
||||
Background.draw(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.BeginPath();
|
||||
gfx.ImageRect(0, 0, desw, desh, idolAnimation, 1, 0);
|
||||
gfx.GlobalAlpha(1);
|
||||
end
|
||||
|
||||
-- Draw selector background
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(0, (desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER) - 280 / 2,
|
||||
1079, 280, selectorBgImage, 1, 0);
|
||||
|
||||
setButtons()
|
||||
|
||||
buttonY = (desh / 2) - 2 * (257 + 5);
|
||||
|
||||
draw_buttons();
|
||||
drawTexts();
|
||||
|
||||
-- Draw the arrows around the selected button
|
||||
gfx.BeginPath();
|
||||
gfx.ImageRect(desw - 512, desh / 2 + SELECTOR_BAR_OFFSET_FROM_CENTER -
|
||||
buttonHeight - 8, 501, 300, selectorArrowsImage, 1, 0);
|
||||
|
||||
-- Draw top and bottom bars
|
||||
drawHeader();
|
||||
Footer.draw(deltaTime);
|
||||
|
||||
gfx.ResetTransform();
|
||||
end
|
||||
|
||||
function render(deltaTime)
|
||||
handleKnobs()
|
||||
|
||||
handleScreenResponse(currentScreen.screen.render(deltaTime))
|
||||
end
|
||||
|
||||
function mouse_pressed(button)
|
||||
if (currentScreen.screen.onMousePressed) then
|
||||
currentScreen.screen.onMousePressed(button)
|
||||
render = function(deltaTime)
|
||||
if not playedBgm then
|
||||
game.PlaySample('titlescreen/bgm.wav', true);
|
||||
playedBgm = true;
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
game.SetSkinSetting('_currentScreen', 'title')
|
||||
|
||||
-- detect resolution change
|
||||
local resx, resy = game.GetResolution();
|
||||
if resx ~= resX or resy ~= resY then
|
||||
resolutionChange(resx, resy)
|
||||
end
|
||||
|
||||
gfx.BeginPath()
|
||||
local bgImageWidth, bgImageHeight = gfx.ImageSize(backgroundImage)
|
||||
gfx.Rect(0, 0, resX, resY)
|
||||
gfx.FillPaint(gfx.ImagePattern(0, 0, bgImageWidth, bgImageHeight, 0, backgroundImage, 0.2))
|
||||
gfx.Fill()
|
||||
|
||||
draw_titlescreen((resX - fullX) / 2, 0, fullX, fullY, deltaTime);
|
||||
|
||||
handle_controller()
|
||||
|
||||
scrollTransitionScale = scrollTransitionScale + 1 / 60 * 5;
|
||||
if (scrollTransitionScale > 1) then scrollTransitionScale = 1; end
|
||||
|
||||
if scrollingUp then
|
||||
buttonsMovementScale = 1 - scrollTransitionScale
|
||||
else
|
||||
buttonsMovementScale = -1 + scrollTransitionScale
|
||||
end
|
||||
|
||||
gfx.BeginPath();
|
||||
end;
|
||||
|
||||
mouse_pressed = function(button) return 0 end
|
||||
|
||||
function button_pressed(button)
|
||||
if (currentScreen.screen.onButtonPressed) then
|
||||
currentScreen.screen.onButtonPressed(button)
|
||||
if button == game.BUTTON_STA then
|
||||
game.PlaySample('titlescreen/cursor_select.wav');
|
||||
game.StopSample('titlescreen/bgm.wav');
|
||||
buttons[cursorIndex][3]()
|
||||
elseif button == game.BUTTON_BCK then
|
||||
Menu.Exit()
|
||||
end
|
||||
end
|
||||
|
||||
-- the thing is... titlescreen script does not have a call to reset function... WHYYYYY
|
||||
function reset() playedBgm = false; end
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
local Dim = require("common.dimensions")
|
||||
local Wallpaper = require("components.wallpaper")
|
||||
local BootPage = require("titlescreen.pages.boot.bootpage")
|
||||
local PageView = require("components.pager.pageview")
|
||||
|
||||
local bootpage = BootPage.new()
|
||||
local pageview = PageView.new(bootpage)
|
||||
|
||||
local function render(deltaTime)
|
||||
Dim.updateResolution()
|
||||
|
||||
Wallpaper.render()
|
||||
|
||||
Dim.transformToScreenSpace()
|
||||
|
||||
pageview:render(deltaTime)
|
||||
|
||||
--pageview will be empty when you `back()` out of the root page
|
||||
if not pageview:get() then
|
||||
return {eventType = "switch", toScreen = "splash"}
|
||||
end
|
||||
end
|
||||
|
||||
local function onButtonPressed(button)
|
||||
pageview:get():handleButtonInput(button)
|
||||
end
|
||||
|
||||
return {render = render, onButtonPressed = onButtonPressed}
|
|
@ -1,71 +0,0 @@
|
|||
require("common.class")
|
||||
local Util = require("common.util")
|
||||
local ServiceField = require("titlescreen.fields.service.servicefield")
|
||||
|
||||
---@class CheckUpdateField: ServiceField
|
||||
---@field onUpdateAvailable nil|fun(url: string, version: string)
|
||||
---@field _timer number
|
||||
local CheckUpdateField = {
|
||||
__tostring = function() return "CheckUpdateField" end,
|
||||
PROGRESS_FREQ = 1 / 5, -- 5Hz
|
||||
CHECK_UPDATE_TIMEOUT = 5, -- seconds
|
||||
}
|
||||
|
||||
---Create a new CheckUpdateField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return CheckUpdateField
|
||||
function CheckUpdateField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o._timer = o._timer or 0
|
||||
o.onUpdateAvailable = o.onUpdateAvailable or nil
|
||||
|
||||
local this = CreateInstance(CheckUpdateField, o, ServiceField)
|
||||
|
||||
this._url = nil
|
||||
this._version = nil
|
||||
this._onUpdateAvailableFired = false
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
function CheckUpdateField:drawLabel(deltaTime)
|
||||
local text = self.label
|
||||
local progress = math.ceil(Util.lerp(self._timer % self.PROGRESS_FREQ,
|
||||
0, 0, self.PROGRESS_FREQ, 4
|
||||
))
|
||||
text = text .. string.rep(".", progress)
|
||||
|
||||
gfx.FontSize(self.FONT_SIZE)
|
||||
gfx.LoadSkinFont(self.FONT_FACE)
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT | gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FillColor(table.unpack(self.FONT_COLOR))
|
||||
gfx.Text(text, 0, 0)
|
||||
end
|
||||
|
||||
function CheckUpdateField:drawValue(deltaTime)
|
||||
|
||||
end
|
||||
|
||||
function CheckUpdateField:tick(deltaTime)
|
||||
if not self._onUpdateAvailableFired then
|
||||
if self._timer > self.CHECK_UPDATE_TIMEOUT then
|
||||
self._url, self._version = game.UpdateAvailable()
|
||||
-- self._url = "" -- debug code to force onUpdateAvailable()
|
||||
if self._url then
|
||||
self.onUpdateAvailable(self._url, self._version)
|
||||
self._onUpdateAvailableFired = true
|
||||
else
|
||||
self:getParentPage().viewHandler:clear() -- Exit out of bootscreen
|
||||
end
|
||||
end
|
||||
end
|
||||
self._timer = self._timer + deltaTime
|
||||
end
|
||||
|
||||
function CheckUpdateField:render(deltaTime)
|
||||
self:tick(deltaTime)
|
||||
ServiceField.render(self, deltaTime)
|
||||
end
|
||||
|
||||
return CheckUpdateField
|
|
@ -1,149 +0,0 @@
|
|||
require("common.class")
|
||||
local ContainerField = require("components.pager.containerfield")
|
||||
|
||||
---@class DialogField: ContainerField
|
||||
---@field _symbolMargin number
|
||||
---@field _symbolSize number
|
||||
local DialogField = {
|
||||
__tostring = function() return "ContainerField" end,
|
||||
BGCOLOR = {0, 0, 0, 255}, --{r, g, b, a}
|
||||
DEFAULT_WIDTH = 400,
|
||||
DEFAULT_HEIGHT = 200,
|
||||
FONT_SIZE = 16,
|
||||
FONT_FACE = "dfmarugoth.ttf",
|
||||
FONT_COLOR = {255, 255, 255, 255},
|
||||
BORDERCOLOR = {255, 255, 255, 255},
|
||||
BORDERRADII = 12,
|
||||
BORDERWIDTH = 2,
|
||||
HEADER = {
|
||||
title = "Title",
|
||||
code = "0-0000-0000"
|
||||
},
|
||||
TEXT = {
|
||||
"Top text,",
|
||||
"Sample text,",
|
||||
"Bottom text."
|
||||
},
|
||||
LEGEND = {
|
||||
{
|
||||
label = "BUTTON",
|
||||
text = "DESCRIPTION"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
---Create a new DialogField instance
|
||||
---
|
||||
---Inherits from ContainerField
|
||||
---@param o ContainerField
|
||||
---@return DialogField
|
||||
function DialogField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.aabbW = o.aabbW or DialogField.DEFAULT_WIDTH
|
||||
o.aabbH = o.aabbH or DialogField.DEFAULT_HEIGHT
|
||||
|
||||
local this = CreateInstance(DialogField, o, ContainerField)
|
||||
|
||||
this._symbolMargin = 8
|
||||
this._symbolSize = 48
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
---Draw the dialog symbol
|
||||
---
|
||||
---Default implementation is a yellow triangle with an exclamation mark
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function DialogField:drawSymbol(deltaTime)
|
||||
local symbolColor = {255, 255, 0, 255}
|
||||
gfx.Save()
|
||||
gfx.Translate(self._symbolMargin, self._symbolMargin)
|
||||
gfx.FillColor(table.unpack(symbolColor))
|
||||
gfx.BeginPath()
|
||||
local symbolBottomY = math.sqrt(3) / 2 * self._symbolSize
|
||||
gfx.MoveTo(0, symbolBottomY)
|
||||
gfx.LineTo(self._symbolSize / 2, 0)
|
||||
gfx.LineTo(self._symbolSize, symbolBottomY)
|
||||
gfx.Fill()
|
||||
-- exclamation mark
|
||||
local excTopMargin = 10
|
||||
local excBottomMargin = 4
|
||||
local excThickness = 5
|
||||
local excColor = {0, 0, 0, 255}
|
||||
gfx.FillColor(table.unpack(excColor))
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(
|
||||
self._symbolSize / 2 - excThickness / 2, -- x
|
||||
excTopMargin, -- y
|
||||
excThickness, -- w
|
||||
symbolBottomY - excTopMargin - excBottomMargin - 3 / 2 * excThickness -- h
|
||||
)
|
||||
gfx.Rect(
|
||||
self._symbolSize / 2 - excThickness / 2, -- x
|
||||
symbolBottomY - excBottomMargin - excThickness, -- y
|
||||
excThickness, excThickness -- w, h
|
||||
)
|
||||
gfx.Fill()
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function DialogField:drawBackground(deltaTime)
|
||||
local textMargin = 4
|
||||
-- border
|
||||
local borderH = self.aabbH - #self.LEGEND * self.FONT_SIZE - textMargin
|
||||
gfx.BeginPath()
|
||||
gfx.StrokeColor(table.unpack(self.BORDERCOLOR))
|
||||
gfx.StrokeWidth(self.BORDERWIDTH)
|
||||
gfx.FillColor(table.unpack(self.BGCOLOR))
|
||||
gfx.RoundedRect(0, 0, self.aabbW, borderH, self.BORDERRADII)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
gfx.FontSize(self.FONT_SIZE)
|
||||
gfx.LoadSkinFont(self.FONT_FACE)
|
||||
|
||||
-- draw symbol
|
||||
self:drawSymbol(deltaTime)
|
||||
|
||||
-- legend
|
||||
local legendX = 0
|
||||
local legendY = borderH + textMargin
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_TOP | gfx.TEXT_ALIGN_LEFT)
|
||||
gfx.FillColor(table.unpack(self.FONT_COLOR))
|
||||
for _, legend in ipairs(self.LEGEND) do
|
||||
gfx.Text(legend.label .. " = " .. legend.text, legendX, legendY)
|
||||
legendY = legendY + self.FONT_SIZE
|
||||
end
|
||||
|
||||
-- header
|
||||
local headerX = self._symbolSize + self._symbolMargin + 16
|
||||
local headerY = self._symbolMargin
|
||||
gfx.Save()
|
||||
gfx.Translate(headerX, headerY)
|
||||
gfx.Text(self.HEADER.title, 0, 0)
|
||||
local separatorY = self.FONT_SIZE + textMargin
|
||||
local separatorThickness = 1
|
||||
gfx.StrokeWidth(separatorThickness)
|
||||
gfx.BeginPath()
|
||||
gfx.MoveTo(0, separatorY)
|
||||
gfx.LineTo(self.aabbW - headerX - self._symbolMargin, separatorY)
|
||||
gfx.Stroke()
|
||||
local codeY = separatorY + textMargin
|
||||
gfx.Text(self.HEADER.code, 0, codeY)
|
||||
gfx.Restore()
|
||||
end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function DialogField:drawForeground(deltaTime)
|
||||
local textX = 12
|
||||
local textY = 64
|
||||
local lineHeight = self.FONT_SIZE + 4
|
||||
for _, line in ipairs(self.TEXT) do
|
||||
gfx.Text(line, textX, textY)
|
||||
textY = textY + lineHeight
|
||||
end
|
||||
end
|
||||
|
||||
return DialogField
|
|
@ -1,134 +0,0 @@
|
|||
require("common.class")
|
||||
local Util = require("common.util")
|
||||
local ServiceField = require("titlescreen.fields.service.servicefield")
|
||||
|
||||
---@class SelfTestStatusEnum
|
||||
SelfTestStatusEnum = {
|
||||
IDLE = 1,
|
||||
INPROGRESS = 2,
|
||||
OK = 3,
|
||||
PASS = 4,
|
||||
ERROR = 5
|
||||
}
|
||||
|
||||
local function statusToString(status)
|
||||
local statusName = {"IDLE", "INPROGRESS", "OK", "PASS", "ERROR"}
|
||||
return statusName[status]
|
||||
end
|
||||
|
||||
---@class SelfTestField: ServiceField
|
||||
---@field checkTask nil|fun(obj: any): SelfTestStatusEnum # a function that will run asynchronously on activating the Field
|
||||
---@field status SelfTestStatusEnum
|
||||
---@field onStatusChange nil|fun(status) # a callback function on finishing the checkTask
|
||||
---@field _thread thread
|
||||
---@field _timer number
|
||||
local SelfTestField = {
|
||||
__tostring = function () return "SelfTestField" end,
|
||||
COLOR_INPROGRESS = {255, 255, 255, 255},
|
||||
COLOR_OK = {0, 255, 0, 255},
|
||||
COLOR_PASS = {255, 255, 0, 255},
|
||||
COLOR_ERROR = {255, 0, 0, 255},
|
||||
INPROGRESS_FREQ = 1 / 20, --20Hz
|
||||
}
|
||||
|
||||
---Create a new SelfTestField instance
|
||||
---@param o? table
|
||||
---@return SelfTestField
|
||||
function SelfTestField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.status = o.status or SelfTestStatusEnum.IDLE
|
||||
o._timer = 0
|
||||
o._thread = nil
|
||||
|
||||
assert((not o.onStatusChange) or (o.checkTask and o.onStatusChange),
|
||||
"Failed to construct SelfTestField, checkTask is mandatory when onStatusChange is defined!\n" .. debug.traceback()
|
||||
)
|
||||
|
||||
return CreateInstance(SelfTestField, o, ServiceField)
|
||||
end
|
||||
|
||||
function SelfTestField:_closeThread()
|
||||
if self._thread and coroutine.status(self._thread) ~= "dead" then
|
||||
coroutine.close(self._thread)
|
||||
end
|
||||
end
|
||||
|
||||
function SelfTestField:_resumeThread()
|
||||
if self._thread and coroutine.status(self._thread) == "suspended" then
|
||||
local success, status = coroutine.resume(self._thread)
|
||||
game.Log(self.label .. ": success: " .. tostring(success) ..
|
||||
", status: " .. status .. " (" .. statusToString(status) .. ")",
|
||||
game.LOGGER_DEBUG
|
||||
)
|
||||
if success and status ~= self.status then
|
||||
self.status = status
|
||||
if self.onStatusChange then
|
||||
game.Log("SKIN CONFIG: onStatusChange(" .. status .. ") (" ..
|
||||
statusToString(status) .. ")",
|
||||
game.LOGGER_DEBUG
|
||||
)
|
||||
self.onStatusChange(status)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SelfTestField:activate(obj)
|
||||
self:_closeThread()
|
||||
|
||||
if self.checkTask then
|
||||
self._thread = coroutine.create(self.checkTask)
|
||||
self:_resumeThread()
|
||||
end
|
||||
end
|
||||
|
||||
function SelfTestField:deactivate(obj)
|
||||
self:_closeThread()
|
||||
end
|
||||
|
||||
function SelfTestField:tick(deltaTime)
|
||||
self:_resumeThread()
|
||||
|
||||
self._timer = self._timer + deltaTime
|
||||
end
|
||||
|
||||
function SelfTestField:drawValue(deltaTime)
|
||||
gfx.Translate(self.VALUE_OFFSETX, 0)
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_RIGHT | gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FillColor(table.unpack(self.FONT_COLOR))
|
||||
gfx.Text(": ", 0, 0)
|
||||
|
||||
local color, text
|
||||
if self.status == SelfTestStatusEnum.IDLE then
|
||||
color = self.FONT_COLOR
|
||||
text = ""
|
||||
elseif self.status == SelfTestStatusEnum.INPROGRESS then
|
||||
local progress = math.ceil(Util.lerp(self._timer % self.INPROGRESS_FREQ,
|
||||
0, 0, self.INPROGRESS_FREQ, 4
|
||||
))
|
||||
color = self.COLOR_INPROGRESS
|
||||
text = string.rep(".", progress)
|
||||
elseif self.status == SelfTestStatusEnum.OK then
|
||||
color = self.COLOR_OK
|
||||
text = "OK"
|
||||
elseif self.status == SelfTestStatusEnum.PASS then
|
||||
color = self.COLOR_PASS
|
||||
text = "PASS"
|
||||
elseif self.status == SelfTestStatusEnum.ERROR then
|
||||
color = self.COLOR_ERROR
|
||||
text = "ERROR"
|
||||
end
|
||||
|
||||
gfx.TextAlign(gfx.TEXT_ALIGN_LEFT | gfx.TEXT_ALIGN_TOP)
|
||||
gfx.FillColor(table.unpack(color))
|
||||
gfx.Text(text, 0, 0)
|
||||
end
|
||||
|
||||
function SelfTestField:render(deltaTime)
|
||||
self:tick(deltaTime)
|
||||
ServiceField.render(self, deltaTime)
|
||||
end
|
||||
|
||||
return SelfTestField
|
|
@ -1,46 +0,0 @@
|
|||
require("common.class")
|
||||
local Util = require("common.util")
|
||||
local ServiceField = require("titlescreen.fields.service.servicefield")
|
||||
|
||||
---@class ColorGradientField: ServiceField
|
||||
local ColorGradientField = {
|
||||
__tostring = function() return "ColorGradientField" end,
|
||||
GRADIENT_X_OFFSET = 128,
|
||||
GRADIENT_WIDTH = 576,
|
||||
GRADIENT_STEPS = 32
|
||||
}
|
||||
|
||||
---Create a new ColorGradientField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return ColorGradientField
|
||||
function ColorGradientField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.value = o.value or {0, 0, 0, 255}
|
||||
|
||||
return CreateInstance(ColorGradientField, o, ServiceField)
|
||||
end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function ColorGradientField:activate(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function ColorGradientField:focus(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function ColorGradientField:deactivate(obj) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function ColorGradientField:drawValue(deltaTime)
|
||||
local stepW = self.GRADIENT_WIDTH / self.GRADIENT_STEPS
|
||||
for i = 0, self.GRADIENT_STEPS - 1 do
|
||||
local posX = self.GRADIENT_X_OFFSET + i * stepW
|
||||
local colorA = math.ceil(Util.lerp(i, 0, 0, self.GRADIENT_STEPS - 1, self.value[4]))
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(posX, 0, stepW, self.aabbH)
|
||||
gfx.FillColor(self.value[1], self.value[2], self.value[3], colorA)
|
||||
gfx.Fill()
|
||||
end
|
||||
end
|
||||
|
||||
return ColorGradientField
|
|
@ -1,43 +0,0 @@
|
|||
require("common.class")
|
||||
local ServiceField = require("titlescreen.fields.service.servicefield")
|
||||
|
||||
---@class InputButtonField: ServiceField
|
||||
---@field button integer
|
||||
local InputButtonField = {
|
||||
__tostring = function() return "InputButtonField" end,
|
||||
}
|
||||
|
||||
---Create a new InputButtonField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return InputButtonField
|
||||
function InputButtonField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.button = o.button or nil
|
||||
|
||||
return CreateInstance(InputButtonField, o, ServiceField)
|
||||
end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputButtonField:activate(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputButtonField:focus(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputButtonField:deactivate(obj) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function InputButtonField:drawValue(deltaTime)
|
||||
gfx.Translate(self.VALUE_OFFSETX, 0)
|
||||
|
||||
if not self.button then
|
||||
gfx.Text("<BUTTON NOT SET>", 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
self.value = game.GetButton(self.button) and "ON" or "OFF"
|
||||
gfx.Text(self.value, 0, 0)
|
||||
end
|
||||
|
||||
return InputButtonField
|
|
@ -1,80 +0,0 @@
|
|||
require("common.class")
|
||||
local Util = require("common.util")
|
||||
local ServiceField = require("titlescreen.fields.service.servicefield")
|
||||
|
||||
---@class InputKnobField: ServiceField
|
||||
---@field knob integer
|
||||
local InputKnobField = {
|
||||
__tostring = function() return "InputKnobField" end,
|
||||
SLIDER_SIZE = {200, 16}, --{w, h}
|
||||
SLIDER_BGCOLOR = {255, 0, 0, 255},
|
||||
SLIDER_FRAME_COLOR = ServiceField.FONT_COLOR,
|
||||
SLIDER_FRAME_WIDTH = 1,
|
||||
SLIDER_OFFSETX = 64,
|
||||
SLIDER_INDICATOR_COLOR = {0, 255, 0, 255},
|
||||
SLIDER_INDICATOR_WIDTH = 4
|
||||
}
|
||||
|
||||
---Create a new InputKnobField instance
|
||||
---@param o? table # initial parameters
|
||||
---@return InputKnobField
|
||||
function InputKnobField.new(o)
|
||||
o = o or {}
|
||||
|
||||
o.knob = o.knob or nil
|
||||
|
||||
return CreateInstance(InputKnobField, o, ServiceField)
|
||||
end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputKnobField:activate(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputKnobField:focus(obj) end
|
||||
|
||||
---@param obj? any # message object for the field
|
||||
function InputKnobField:deactivate(obj) end
|
||||
|
||||
---@param deltaTime number # frametime in seconds
|
||||
function InputKnobField:drawValue(deltaTime)
|
||||
gfx.Translate(self.VALUE_OFFSETX, 0)
|
||||
|
||||
if not self.knob then
|
||||
gfx.Text("<KNOB NOT SET>", 0, 0)
|
||||
return
|
||||
end
|
||||
|
||||
local knobAngle = game.GetKnob(self.knob)
|
||||
local sliderWidth = self.SLIDER_SIZE[1]
|
||||
local sliderHeight = self.SLIDER_SIZE[2]
|
||||
local sliderBgColor = self.SLIDER_BGCOLOR
|
||||
local sliderFrameColor = self.SLIDER_FRAME_COLOR
|
||||
local sliderFrameWidth = self.SLIDER_FRAME_WIDTH
|
||||
|
||||
local maxValue = 1024
|
||||
self.value = math.floor(Util.lerp(knobAngle,0, 0, 2 * math.pi, maxValue)) % maxValue
|
||||
|
||||
--draw value
|
||||
gfx.Text(self.value, 0, 0)
|
||||
|
||||
--draw slider
|
||||
gfx.Translate(self.SLIDER_OFFSETX, 0)
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(0, 0, sliderWidth, sliderHeight)
|
||||
gfx.FillColor(table.unpack(sliderBgColor))
|
||||
gfx.StrokeColor(table.unpack(sliderFrameColor))
|
||||
gfx.StrokeWidth(sliderFrameWidth)
|
||||
gfx.Fill()
|
||||
gfx.Stroke()
|
||||
|
||||
local sliderIndicatorX = Util.lerp(self.value, 0, 0, maxValue, sliderWidth)
|
||||
local sliderIndicatorWidth = self.SLIDER_INDICATOR_WIDTH
|
||||
local sliderIndicatorColor = self.SLIDER_INDICATOR_COLOR
|
||||
--draw indicator
|
||||
gfx.BeginPath()
|
||||
gfx.Rect(sliderIndicatorX, sliderFrameWidth, sliderIndicatorWidth, sliderHeight - 2 * sliderFrameWidth)
|
||||
gfx.FillColor(table.unpack(sliderIndicatorColor))
|
||||
gfx.Fill()
|
||||
end
|
||||
|
||||
return InputKnobField
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue