diff --git a/.changelog/Release_0-02.md b/.changelog/Release_0-02.md index 8f527283ef..987d27eb8d 100644 --- a/.changelog/Release_0-02.md +++ b/.changelog/Release_0-02.md @@ -5,6 +5,8 @@ Binary only release to replace Stepmania.exe. +### Added +- Usable but hidden Jumpstream generators - [c5b3d03](../../../commit/c5b3d031c021d8de0015dc51d9156f0cfe63f8b4) ### Changed - For Windows, use D3DX instead of RageMath for some calculations - [2d4c053](../../../commit/2d4c0538a683da87c0a09ac68304f953b04542fa) - FPS with Holds on screen improved by 37% diff --git a/.changelog/Release_0-51.md b/.changelog/Release_0-51.md index d8b482e702..1bca3c0d14 100644 --- a/.changelog/Release_0-51.md +++ b/.changelog/Release_0-51.md @@ -9,7 +9,7 @@ Windows installer release. - DivideByZero Noteskin Rolls - [6f19852](../../../commit/6f198526182c78ac6f6c8cce55d276e55b91a86f) - Grades based on Wife Percent - [e97836d](../../../commit/e97836da3a71ae3636669a66058e330f9ac3c535) - Player SSR (Score Specific Rating) recalculator functions - [a1f4df8](../../../commit/a1f4df86aa00dc9d5f57f96b0873de741e212aaf) [8edfc38](../../../commit/8edfc3824cf40031f130a9a1fe649ad75bc9c805) [1a0339b](../../../commit/1a0339b8a69c4dba45b164deff3fa980f0e0a7ea) [1741687](../../../commit/1741687ee9834bf98fdcde9c9b0a562eb7cfada6) -- Song rates up to 2.5x - [9543b49](../../../commiT/9543B499774d672f038ff1666e2a28e30cb29a54) +- Song rates up to 2.5x - [9543b49](../../../commit/9543B499774d672f038ff1666e2a28e30cb29a54) - Song Search - [4795319](../../../commit/4795319b309d8af2370ce6fbf7afe81a3bd57bae) [4f64c6b](../../../commit/4f64c6bbb84278c331c2d0bb1f400288ed56fbc5) [5a65b28](../../../commit/5a65b28338a3782b96726963eac41f06d8b6f5a5) ### Changed - Installer won't overwrite preexisting files & won't delete the song cache - [ad5b0ff](../../../commit/ad5b0ff0f725577f89a8ed31e3b347789eef4b82) diff --git a/.changelog/Release_0-71.md b/.changelog/Release_0-71.md new file mode 100644 index 0000000000..8e073fbf03 --- /dev/null +++ b/.changelog/Release_0-71.md @@ -0,0 +1,380 @@ +# Release Changelog + + +## [0.71.0] - 2021-12-10 - Rebirth + +### Added +- A new theme - [676aab4](../../../commit/676aab4cba40fa967cbcc1b3f164affbb9f2d106) [431a5b5](../../../commit/431a5b5bc2acca33d7a15591a41c012c268df75b) [4012cf0](../../../commit/4012cf0904a2f929a1e89c28b9d2d0254382cf8d) [d7426c1](../../../commit/d7426c1b025bbaadcfc77ac2320d313156c4e183) [c754f50](../../../commit/c754f5056206e637acfe67f95ff34a25a429be12) [f9797b6](../../../commit/f9797b69951e6da7a23dd8bfe47c4e6e2171f657) [4a98a4a](../../../commit/4a98a4a8fab2fadcf672b414340d4531767ccb68) [2b385ee](../../../commit/2b385eebe31ed82adbbb8e53dea0dce1e9104570) [daeeaf2](../../../commit/daeeaf28b61f9e989eb0bf2317cc8dd9c3267649) [2075357](../../../commit/2075357d8fc2dc66a116eafa8f36365e9b8b4928) [5ea7db3](../../../commit/5ea7db3034ad6f521b50b0f99cff54225cf855d1) [e2bcd3c](../../../commit/e2bcd3c724d18a100e1b11897244c0b33316222a) [54bac02](../../../commit/54bac028bce63cd280de4ff79374b21e4b09ad21) [878321c](../../../commit/878321c0507920cf9a52ddfa43049a2779b8682f) [265bd82](../../../commit/265bd8275ba4cde90f3a8de8c387c850bd58c8a9) [c9fbe3c](../../../commit/c9fbe3c73eb7c0023ba2e90faeb093b813ce8091) [686b0b8](../../../commit/686b0b85d6d88a6f9cc2447924f377601e07ba4d) [ce91072](../../../commit/ce910728f42ff1a8c7ce818e1111c159fa70938b) [cde5be8](../../../commit/cde5be82ba2081bf0b7693f284a7718af33e2349) [134421f](../../../commit/134421fe6495c22c459b6aa89e5aded3c3bad57b) [7a6c392](../../../commit/7a6c392e8edb1f1532096148c7746551c489a58f) [b1d778c](../../../commit/b1d778c0ec90e444e89a6e2e662d06d5a2a2cde4) [12733b4](../../../commit/12733b47cb4e40d94946fcb47c69f64b4e6152b7) [e779370](../../../commit/e7793705866c2c388a436b4fd3f64caa3f970327) [9392492](../../../commit/9392492f8bf1eb26dabbed216ed7d545f2d7f01b) [6657fd6](../../../commit/6657fd63f1f830b069b37d5bf8907574ce5752f1) [cac2642](../../../commit/cac2642e0fdc130ba7f5fb56f17536a263a4f5ca) [1bd4d6a](../../../commit/1bd4d6ac4abf1f1632cfc667fd443dfdb2c326c2) [cea57a1](../../../commit/cea57a110f2e5231c397c1af5416b659cb3921d3) [bcf783c](../../../commit/bcf783c6cb289edfd1327aad1ca4519bf6443a35) [5e4b03c](../../../commit/5e4b03c98a98af4ec4fd85ba1e9f8b62ddf8007e) [3af5285](../../../commit/3af52852300631a2019edc8b078f5ebfec6798b9) [b172f04](../../../commit/b172f04380c88a2104f7d222d5a8e670eb82f07e) [aa72ea9](../../../commit/aa72ea9a326a0e9417d70f46d28c808c68fa2caf) [8ec6d46](../../../commit/8ec6d46f32fdbc7527a5de58fa3778efa9f55d2a) [0e08087](../../../commit/0e08087c7b690846a16bfbe5b1bf777f8e9b782b) [b7c4fdb](../../../commit/b7c4fdb96b329bc815813889add69fe825e825e1) [daf305e](../../../commit/daf305e26530816bc44d0c0af94a7a82e115ec99) [f48916b](../../../commit/f48916b197adff41a5a0c293516e7a4022eb57c6) [bf9af03](../../../commit/bf9af03a309c30d7dd91ea0a2d8d92aefd17bf5a) [125504e](../../../commit/125504ee67a2ebadfe4cf78d4ed995fa4cb7f687) [8d28267](../../../commit/8d2826742bd597c12e4adf55c4d0e8dd714a0cf5) [7e0e53d](../../../commit/7e0e53d22371643d32ba4b1becf932ec45131294) [297cc68](../../../commit/297cc6887dd473b4c9cb9f9487f46dac4ac70214) [7bd5698](../../../commit/7bd5698d1f8568742490133d323ea12ebf0fb9ff) [bc24267](../../../commit/bc24267aca68c513fd61c978cbb7b8b84e4955a3) [c4e453b](../../../commit/c4e453b1fd63f4c64d2a288fa5fd85df16d0f7cd) [3b9d0b1](../../../commit/3b9d0b17adc2fc9249d20b31ede08e5a57a3effe) [23d4ab8](../../../commit/23d4ab8ccb4a662482500d50be9d97fd72c2cd17) [f2b11da](../../../commit/f2b11da1fc3dd3ea1b166d2927d7a2e73b5e685a) [ceef847](../../../commit/ceef8475e86fdc5ab07f9dd3eae3fb673ab230e2) [0e33609](../../../commit/0e33609b27167b54534e1a26e9984d1e5fc72d46) [342bf4b](../../../commit/342bf4be13a9dfa3737cf865633befdf34b9311a) [564e11d](../../../commit/564e11d76b8e1c2fb3013e9bab1e4138335a4f4a) [d80ffda](../../../commit/d80ffda787533adca9ad97b23e476bac25249766) [e9163c4](../../../commit/e9163c445b980050d1f0da5ca40c541bfa20bf7b) [1fb4ad3](../../../commit/1fb4ad3bb843c961a3fb6335429a317e792d1c56) [fcf4118](../../../commit/fcf4118ba0531117d512ec5f356898c78f221ba3) [1a00fb1](../../../commit/1a00fb1837b96959a0f60666297a551b68b52c31) [9b119e3](../../../commit/9b119e3df180b70aa97cf37bd782de2b469aa961) [c01732c](../../../commit/c01732cbe221d8c0362eefb53a7202aba25c5c6b) [2f2ac63](../../../commit/2f2ac63a78dfd58822cb83eeddc987b77d463c59) [45fd594](../../../commit/45fd594f311d579fb54f3307c8339aa90131ef01) [4569e34](../../../commit/4569e34695e882d9dba0af23e35849b9bc7241b3) [ef4e07f](../../../commit/ef4e07f2f3b8a84d5d594c1d6c4a966e452627ee) [55cc9a9](../../../commit/55cc9a97ab443ae2c63f96cc0f2bd014ca2911ce) [55cc9a9](../../../commit/55cc9a97ab443ae2c63f96cc0f2bd014ca2911ce) [f05ccfd](../../../commit/f05ccfd4e9b1dcc9896cc7325bedb83e8a8bfce9) [d34c8c3](../../../commit/d34c8c3ef9b4747dd895259b91033b905d5488f3) [9927452](../../../commit/99274523928da24ecbd12421830b1b685396f3a4) [b80c6b9](../../../commit/b80c6b9fe34d588b7f5df49b0d335333de41c8a6) [1161f83](../../../commit/1161f83f49a85c391e9e1203c2bec66ef37fc07b) [7b9b0f7](../../../commit/7b9b0f7460479afb2d876873ebbe1e5a6bcf0b47) ... countless more +- Actor methods GetTrueZoomX and GetTrueZoomY - [e6f853f](../../../commit/e6f853f60b532919eddabc1d890e4561e809bb2f) +- ActorProxy is back - [9cf90f3](../../../commit/9cf90f3880efe83cd8d52919d4b1ccf9e28dfb75) +- Alpine build instructions - [44e9de0](../../../commit/44e9de033760d319eb9ac84808a743efad3ebdbb) +- Better issue templates - [4b6f47b](../../../commit/4b6f47b686135991831b2bd795eca70e88578746) +- Beat default skin replacement - [dbfa9d0](../../../commit/dbfa9d02c5a612d4adfc44ca10a80f89d7fbf80c) +- Calculator C++ Docs (very light) - [a94f8c1](../../../commit/a94f8c10f3f64f674d68fc6ac1da8ebbc7743cc8) [1150611](../../../commit/1150611779730e3c3703a15cbf786aa3d1730914) [7349efb](../../../commit/7349efb5e5aa0cb7e861ab1956267145042948ec) [9a39e99](../../../commit/9a39e991c1bfa449a0fcacb2e6953b9b36115842) +- Crashes will now output a .dmp file in the CrashData folder - [4126fc7](../../../commit/4126fc7890a2c5d53ce256187687d3d5d3adc737) +- Crashes will now upload automatically to a server run by the dev team if you opt-in - [a046846](../../../commit/a04684657747af7437ffed8211aea8308d91503b) [b8efbaa](../../../commit/b8efbaaf84127f0b99e5c9cc41454ea685e4abe6) [2c418fb](../../../commit/2c418fb311e976a69048434f712bd6e522e306e4) [5b842ea](../../../commit/5b842ea4451169ef41d148dfeeba3d5dbb78915c) [024d92e](../../../commit/024d92e037d440d57d4f3ae3694518a70de5231e) [3e26f73](../../../commit/3e26f73b807be8d92aa1debc3e391c0e805d08d3) [ff76b8e](../../../commit/ff76b8e7d8a25079e032210fdaa25a3af96745e7) [35ab36c](../../../commit/35ab36cfedcadc9257c3c574118feadc8811f41e) [4cbb53a](../../../commit/4cbb53acdc429bed3e2bb979658814c8eae75dc5) [a266893](../../../commit/a2668938fa29600827774ed9f9b0aa4e4b34a493) +- Crashes will now upload the latest log file to a server run by the dev team if you opt-in - [024d92e](../../../commit/024d92e037d440d57d4f3ae3694518a70de5231e) +- Cryptmanager now has SHA256 - [7a51d09](../../../commit/7a51d09b1e3a50fb481ac4586db616792330ab32) +- CurrentStepsChanged added to replace CurrentStepsP1Changed being removed - [960e30e](../../../commit/960e30e69d0e7bc79cbebc4b151e380f2216bc72) +- Customize Gameplay allows pressing Backspace to reset a selected element to its default position - [14a6d69](../../../commit/14a6d698af36d12be710352a040e24371b99e518) +- Debug menu page F8 allows opening the current Song folder - [74b0c91](../../../commit/74b0c91b3180935fff9dcafbb2ccae88bc9f944f) [c711d08](../../../commit/c711d0895eaca305ea755c131f49f7d652508cad) [e0388a7](../../../commit/e0388a7054c05cde98a7c586a7f6fc3b78770d56) [e1b2451](../../../commit/e1b2451a5a78444fc504355b769be36fb4126854) +- Debug menu page F8 shows current Chartkey - [c711d08](../../../commit/c711d0895eaca305ea755c131f49f7d652508cad) +- Double setup is tracked on a per score level - [1530816](../../../commit/1530816e766aafd42aa80793ac098b745823b7e8) +- DownloadManager sends a message when bundles and packs are refreshed - [4f67139](../../../commit/4f671390f957e21cd6758d25159356bb890160f2) +- Evaluation scoreboard in Til Death can be clicked to switch scores - [5d4d6d2](../../../commit/5d4d6d22230ddf88925fdc13dd5581051362962f) +- Filters in Til Death allow checking highest clear percent per song - [2e77918](../../../commit/2e77918640708ea0399f4cc0f7a9cc665afdf08e) [a1b65f5](../../../commit/a1b65f537facca2e1fadf3e1c962c57009140e6b) [a7040e5](../../../commit/a7040e56ef14476a959a8992c0bc5cd2dec24853) [6a8453f](../../../commit/6a8453ff7a51521baa0b1bc8bac603b2c4f1cc80) +- Filters in Til Death allow checking highest difficulty only - [d1428d6](../../../commit/d1428d68de4f7cf72dcd10d53a17ebf06d0d15af) +- Goals can now use the added AAA-AAAAA percentages - [10e3235](../../../commit/10e3235fc1ab2c188c71acdeca4128f0fc2bb186) [1d8def0](../../../commit/1d8def0fb514bb181edbb9b5a99433fdf165a3ff) +- InputData is being recorded and saved compressed - [d9f235f](../../../commit/d9f235f951f76003ae28febce542856d36cfe9dc) [2688bb5](../../../commit/2688bb517f4e8e5cdd12fa2cae8302e5f838d602) +- InputMapper access via Lua (saving, loading, key mapping, etc) - [92dc8ca](../../../commit/92dc8caa371ea1c3b240718a4447dfa795fc87bd) [8344dee](../../../commit/8344dee77004ab243aa9a18dd610d90f46f0ed6d) [9d44638](../../../commit/9d446387dc200034aaba2e7d44d9ea6677955816) [317879e](../../../commit/317879e97296aff714c1e9322b2ca8616a960b2b) +- Insert Mods AnchorJS, JackJS, IcyWorld are now selectable - [1bc52f1](../../../commit/1bc52f1cd1478e490640d22134bbab285b467625) +- Log files are now one per game session and do not self delete - [8ceedce](../../../commit/8ceedce57226d48da584ebbbfa77974061d57424) [743cae4](../../../commit/743cae4f3583f84aef154415755befe898ff7cb6) +- Log output for 0 bpm crashes because a fix is not yet found - [0c3689c](../../../commit/0c3689c7fb7a3deae7004240578726fc3a161d64) +- Lua binding for Actor GetTrueRotationZ - [c474a89](../../../commit/c474a898daa9437abeaaf9307a0d549a25863f73) +- Lua binding for calculator versions - [46b4cb5](../../../commit/46b4cb57b523b16336e74d8463f60467570d7f1a) +- Lua binding for current language - [6bbefed](../../../commit/6bbefedc54a73d566d6805850843afb59383ff88) [fe60d14](../../../commit/fe60d143d9fac3a1529b6f6551f6a11418e9c7a3) +- Lua binding for Download mirror links - [bac4ade](../../../commit/bac4adeb448da4087fda4df6f2d1b5374c4a22a9) +- Lua binding for Filtermanager HighestDifficultyOnly - [4a6cbd5](../../../commit/4a6cbd5145d665d3212c21c5fe47788998323280) [a01090e](../../../commit/a01090e142062081aece4380e7648d9e7d636d83) +- Lua binding for flushing directory cache in RageFileManager - [da41fbf](../../../commit/da41fbf602b4baa5924d1f812dad0a1046d37379) +- Lua binding for Game from string - [83572df](../../../commit/83572df0a3511be2f860eab332e3c4195e61a5b9) +- Lua binding for GetChartsOfCurrentGameMode - [1b12858](../../../commit/1b128580744634d83d9e168dc13e0f5a693b0aaf) +- Lua binding for Goal creation - [2e35a76](../../../commit/2e35a76b0af6cf4dc374eef4cc8fb762d93c8306) +- Lua binding for Hold Replay Data - [f77bcd5](../../../commit/f77bcd514e476968186e972c261aa1999532479b) +- Lua binding for Playlist renaming - [361c684](../../../commit/361c68450276c0215a50156d4b9ef26f1e078fad) +- Lua binding for player rating over time calculations - [e32637f](../../../commit/e32637f977c0ec25a5acb4faa03c7880fab2fb11) +- Lua binding for Preferred Stepstype - [f86ac17](../../../commit/f86ac17365fd6dbd16b9f3575d8fb1eff83f1b44) +- Lua bindings for ScreenSelectMaster cursor movement and menu audio playing - [0e9dea8](../../../commit/0e9dea8ebd229735fcaa3be578088bdd1b4c8fbf) +- Lua bindings for ScreenSelectMusic hotkeys - [2ab1729](../../../commit/2ab17299456df2e54a074094a0dd2da5a2c1911f) +- Lua binding for Song - GetChartsMatchingFilter - [cfabb18](../../../commit/cfabb18046674134240933b728b887263e4b69b9) [93362d2](../../../commit/93362d21fafb57f4c58bd875d4ebb964f8c26f05) +- Lua binding for SongUtil comparators - [24cc4c5](../../../commit/24cc4c554b2489eabb4fc4361c352eebf186d8cb) +- Lua binding for SwitchThemeAndLanguage - [bb63429](../../../commit/bb634295b65f4b39dcf898afcfd783d77a5a7207) +- Lua binding for playing sample music in ScreenSelectMusic - [7b35ccd](../../../commit/7b35ccd30fbaf37367858421ce87e0dee977a59c) +- Lua binding to set Player Options from a string - [513dc66](../../../commit/513dc66eea9b34ab8997aa6a215d664e58c4cf12) +- Lua input event now contains a no modifier version of the char like c++ usually does - [d2fced8](../../../commit/d2fced875e70f78ae24a9f7915bb8c597762c20b) +- Lua script for color comparison - [cf83d2f](../../../commit/cf83d2ffac07d177dcc59432a35c1882554f3d4e) +- Lua script for finding best Grade on a Song - [6529522](../../../commit/65295227c865c0644841afacac5c37b4c8a57780) +- Lua script for finding judgment bounds for all timing scales - [f46f3b3](../../../commit/f46f3b35e75815d2486533282a28e8b0578a2fad) [b7c49cd](../../../commit/b7c49cdf704602f389a8280356d8f97296ee048d) +- MacOS is now able to use SetCursorVisible - [536e96f](../../../commit/536e96f5fcd3e35afd7e906fe6d6deaa1445abde) +- MSD extrapolation now supports rates below 0.7x - [1829d77](../../../commit/1829d77b496c0eb63f3092f931903e35c654bc11) +- Multi toasty is back, defaulted off, as the MultiToasty Preference - [bc0424b](../../../commit/bc0424bdcf8768c977959b891a3bde6423da011f) +- Multiplayer ReplayData reports NoteType - [9ca8bee](../../../commit/9ca8bee472619823e3f238a6842f264817472b08) +- Multiplayer score messages track RadarValues - [f1011d5](../../../commit/f1011d51fc5846d55807fef43d49d9b7adfc20b6) [1d9b473](../../../commit/1d9b4737bf23d49cdf407d3e73f4117c8b72ba6b) +- Multiplayer score messages track WifeVersion - [403e830](../../../commit/403e8303908591f579bc5908c7f2b45a8ee63926) +- MusicWheel supports listing pack completion counts natively, off by default behind a Preference - [78dba8f](../../../commit/78dba8f582f815e4d8e81dba162a3f8a21a1c674) [3e6cbbc](../../../commit/3e6cbbc843748910a4f30938f741b13e53430529) +- Normalized judgments now in the Etterna.xml - [0f1d5b9](../../../commit/0f1d5b909261b747cb2298e141f2e48349bb0524) +- NoteField now supports loading an Actor in front of it called "cover" similar to the NoteField "board" - [0dfac80](../../../commit/0dfac8001e3f7713c969e6312777e6f7bfc74a41) +- NotesLoaderOSU supports 3k, 9k, and 10k - [b9d0491](../../../commit/b9d04915a710b9ce5fcb776d7b3c394212fca9af) +- Pack downloads trigger the game icon to flash or bounce when finishing - [c6c66dd](../../../commit/c6c66dde4cb4abbc2b4f5f1b70ab124f2ad3eeba) [1eff728](../../../commit/1eff728a3fc575a861f4f6419570974f95f3b227) [3e1c319](../../../commit/3e1c31904b9bb9633dbf22c623cad24effdcaf36) +- Popn is now supported in Customize Gameplay (5k, 9k) - [6a695ae](../../../commit/6a695ae4141e7286078dbf6c3bd98812d95a9abc) +- Popn is now supported in Noteskin Preview - [86b1623](../../../commit/86b1623e0c5e73b1a44caa7b45ccc6db2835f78d) +- Preference to control Calculator parameter loading - [817c913](../../../commit/817c913ce6d3d4771480eafe1f8b64e723b2ee87) +- RageTexture can calculate its average color, granted Lua access - [8afee89](../../../commit/8afee89abfae7bfe1a3d1bbcfce36d535ebd8f81) [88c1b43](../../../commit/88c1b436a0dec0d004ef2997fafc815ed6d4a07a) [3f9a541](../../../commit/3f9a5410f13ce83f9d9199399f7e69d056d4af36) [1e18c40](../../../commit/1e18c40fc995e5ade71387ddd63593615705e440) [610ab27](../../../commit/610ab277c941b2eaebe36b24f0535267f8d17f8c) +- Replay Snapshots now contain mean and standard deviation - [df5cd86](../../../commit/df5cd8685b8bd39976042c2e3f2b1bf2a84d1949) [680189f](../../../commit/680189fdc96d80224b722cfc4bd78e1dee92d0d2) +- RestartGameplay can now be pressed when in Evaluation - [c9dda64](../../../commit/c9dda642f8729cfe668b81ea480fdfc61ba93f06) +- SampleMusicPreviewMode_Nothing to make no sample music play ever - [52b6374](../../../commit/52b63743c513c3e92fa15788d4929210942062f0) [263d395](../../../commit/263d3953ef3e6127b66bb5662f14bb7e350f4218) +- Scoremanager functions and bindings to get HighScore counts per skillset per profile - [44bc50b](../../../commit/44bc50bbee4802f2871efd8dd3086646ce5666df) +- Scores This Session information available to Lua - [7a9236a](../../../commit/7a9236a59ac66d70e1a1499a5c1206224c9dbb7a) +- ScreenTextEntry fires a few new useful messages for the theme to react to - [b383d25](../../../commit/b383d25f7305facc27baaf900fe31a60f87ef0fa) +- ScreenTextEntry will remember and reset the redirected input state if necessary - [790a1f3](../../../commit/790a1f33ed759baecf3189cb411f1a306e36b245) +- Til Death chord density graph shows current NPS at the hovered position - [e71b19f](../../../commit/e71b19f9c6adb55a2e0a69a6010baa838b6784a0) [8ce9f39](../../../commit/8ce9f397ce4a1a0d5beecad415ab329d987a3bcb) [37e26b6](../../../commit/37e26b620f109da7fb0f539413bbd8facc94cef7) [617c12e](../../../commit/617c12e78c621657dce3c4138c5202055e4e65c8) +- Til Death chord density graph shows current BPM at the hovered position - [94a405f](../../../commit/94a405f0a5c637fc6328115ac50c7c15c227e96f) +- Til Death "Press Enter for Player Options" shows up when entering songs, and a Preference to hide it - [3e12781](../../../commit/3e127814418c84f51e11978888ce4be46fb54fa7) [0d7f792](../../../commit/0d7f792bd0fc2db0c07e40dfd5df2c1f4d8229f9) [2e63cdc](../../../commit/2e63cdc90e5caef711839b0e1500c36bd820ba76) +- Til Death tags can now exclude songs instead of requiring - [bc1ec61](../../../commit/bc1ec614ef977a7de4c46ac2beb1bf8704df883f) +- Til Death theme option for centering the combo text - [8c4542d](../../../commit/8c4542d418ed796bb800b64474155d8bb45f3790) +- Til Death theme option for combo animations - [e0c31f7](../../../commit/e0c31f7baa5a66b1db908eb33bfaa38d7b01d185) +- Til Death theme option for judgment animations - [e484549](../../../commit/e4845491ce2bef050608037b3fd9d77a90153c48) +- Time display in Til Death offset plot hover - [b39a67f](../../../commit/b39a67f9404b384b724274f1875737085dd9118f) [c752d72](../../../commit/c752d7238f3f63e2526f80907004f23d01b8af8a) +- Translations for the short version of Skillsets - [2beadd4](../../../commit/2beadd42f4a8b8dca1738b6dcc7e857fc5bc5a34) +- Ungrouped sortmode to just remove all the folders - [c9edaed](../../../commit/c9edaedcd68bc064023c72e1a9562005f47ee1ac) + + +### Changed +- AAAAA is moved to 99.9935% - [ab2e24b](../../../commit/ab2e24b312cf597a9bab928b56247d76f5105dbc) +- All keymodes are no longer automatically invalidated due to full calculator support (still don't give rating) - [bddda2d](../../../commit/bddda2d48cde65c6ba1c629b603dfaa8c730b64e) +- All turn and transform mods other than mirror will invalidate - [7ebdfbd](../../../commit/7ebdfbdc5b7735d8cb4864e7d7e29e6ef769d74d) +- Application info code refactored - [d835418](../../../commit/d835418470d5ba3baf489866ce14554ac1f0453d) +- ArchHooks rewritten - [6c1f765](../../../commit/6c1f765e52aa207b7ba7bc3bb18e4ad5f1007a78) +- ArchHooks Lua binding is renamed to ArchHooks - [7f88cb5](../../../commit/7f88cb5b926fe8eea65d9565eeafb89bdde8e0e3) [d5227b6](../../../commit/d5227b6a8a551049e788710d1a841b77ca1a667c) +- Brightness being set to 0 does not prevent Lua from turning on the background if any bgchanges are present - [06aafb0](../../../commit/06aafb0be37ba90914a32e55e8c989de751092c3) +- Broke very high non pitch rates on Linux (probably) - [88f4fd4](../../../commit/88f4fd444ca32b4a732fcf9dd265087be6908100) [4b03179](../../../commit/4b03179bf7511ddb0188823fb94e4f2226f74527) [cc92336](../../../commit/cc923363ddbab7787e7187d4427edf3759425868) +- Calculator accuracy downscaling highly buffed - [b5acc64](../../../commit/b5acc6406e338f55f95c2048e4cdf3b43eab0c43) [0245024](../../../commit/02450242096aac9b2313f8d2ef2090a49d05e6fe) +- Calculator aggregation functions generalized and centralized - [9a37e08](../../../commit/9a37e08a20e9d382acb3155f3e6eef1c1e4da3ea) +- Calculator chisel (the binary search that takes half the run time) optimized, MinaCalc now on par or faster than pre version 263 - [8b267c0](../../../commit/8b267c0663079f0b3762dea2d14debff60a07acf) [86cf235](../../../commit/86cf2359f3c1b6519c3173972b13f269c484ed2a) +- Calculator chordjack main patternmod has a slight decay factor - [dd907f8](../../../commit/dd907f8c6d84c86b0e642e9b3870fde5a1f71621) +- Calculator chordjacks now has a patternmod that detects anchors and chains - [37ffe68](../../../commit/37ffe681b8ed733796c27af391abecefdd1f1887) [e9bc565](../../../commit/e9bc565b03b19dcf36a4390e7d6e3b20be845aef) [7b7d038](../../../commit/7b7d038c0f8aff36d3f2e3ee69964dd1196aea73) [b94ec56](../../../commit/b94ec56ee3de4ca0b8161eaad857434d5cd1c398) [cb4a723](../../../commit/cb4a7236772124c081f0fe6ee7ff77a71ee4b3a7) [e4373cc](../../../commit/e4373cc647f85c6979044bac35effff987b09bee) [bc1cd77](../../../commit/bc1cd7731c2cb6133db231c4ad3f15b9ddbf2a1e) [9d5eb4f](../../../commit/9d5eb4f5e44faa15d2fbf79c2518e24d672db8a9) [0ad1ac4](../../../commit/0ad1ac433d0618287e06bdf604e9b7f60287892e) [4ad1d75](../../../commit/4ad1d75d775b989f50475f3889b79c3d3397ebf7) +- Calculator chordjacks no longer smoothed - [765bdcc](../../../commit/765bdcc599f376ac9b8d2183d3e03a3160845bdb) +- Calculator chordjacks tuning - [a5c4f72](../../../commit/a5c4f72d3079ec402e980c5e4831f540b9f1d893) +- Calculator clamp function replaced with faster C++ function - [e167aa9](../../../commit/e167aa9a27b3e829d094cf1cd84a55d58f65bf73) [f50bf9b](../../../commit/f50bf9bb989b56d25f812837718369f93dbe4402) +- Calculator debug info contains grindscaler value - [d21573d](../../../commit/d21573da4dd12ae27400e0bfb5136ebe98b25dc7) +- Calculator debug info gets cleaned when entering Gameplay - [1bd86ba](../../../commit/1bd86badf767388c1553e0bf7b2b1b862e865f13) +- Calculator debug info jack difficulty graph scaling improved - [baa358b](../../../commit/baa358b502ea937f8b7e58c34213be48835227d6) +- Calculator debug info jack loss is now visible on the graph - [1d3709d](../../../commit/1d3709d59c948ede0086cbba285acef9bbdc593b) [7e356d2](../../../commit/7e356d2a74851ea34e67141f77c08774b9017a93) [8907f9a](../../../commit/8907f9a1428dd85045aa985b955109ff978488d0) [9adae51](../../../commit/9adae517e82b105620835dd6b739e3458d2fa006) +- Calculator designed for solo only now supports all keymodes - [6989346](../../../commit/6989346cac354890085394a0dd3fedf6e3611a6a) +- Calculator flamjam highly buffed - [b5acc64](../../../commit/b5acc6406e338f55f95c2048e4cdf3b43eab0c43) [4142ed9](../../../commit/4142ed954df451323e74e20ca9bfb1ad3bddc936) +- Calculator grindscaler made slightly more intelligent based on the density of the file - [8529a46](../../../commit/8529a469e5d8ad76d7781ad21043de8f1d8e07c5) [6386cce](../../../commit/6386ccede0cf2535956956a941b389f8a62576d0) [c43d188](../../../commit/c43d188667b41c28cfe1e135b1cd0622854af022) +- Calculator handstream density modifier added but inactive for this update - [d14f9d5](../../../commit/d14f9d5ca03e7d21c55fdb9681ce3de001e01acb) +- Calculator jackspeed difficulties are independent of anchor difficulties related to runningmen - [0f2ff70](../../../commit/0f2ff703fcba8894e3d76f80316d732211a2557f) +- Calculator jackspeed parameters modified to allow files like Porco Dio to be detected - [f8f2073](../../../commit/f8f20734eef5ccd1a6d40f5ef38a314635e6f2f5) +- Calculator jackspeed has been reparameterized which basically means it was bandaided - [13f43ed](../../../commit/13f43ed1bd3a4663e945ef4aa7dacc1ec2695634) [ca7c91e](../../../commit/ca7c91ee5f448e5de21861a13fb035211cc13732) [a311167](../../../commit/a311167c592579f457390242a2b0b5cc5a9e54bd) [14683bf](../../../commit/14683bf3656bf9e5c34111e4348811315501832d) [9bd8595](../../../commit/9bd859531fc5029370c7cecc4275743ed5f290d8) [eafd0b5](../../../commit/eafd0b53a34a35a5be2aa83769b387ace25f730a) +- Calculator parameter loading should properly happen only once per thread - [5c027d4](../../../commit/5c027d4f9fa97ee3ceed05e50cd97fa61a3abbe6) +- Calculator runningmen parameters and difficulty calculation slightly changed to target overrated jack bursts - [801ac69](../../../commit/801ac69c6060d7cc7255cba3e4b957877821c72c) [2c194f0](../../../commit/2c194f0dc92d1e27ae3b4876400e1f3c88442ae3) [e94841b](../../../commit/e94841b58c87476d91206ea6a4c6b63dca3b5b4f) +- Calculator skips files that are perceived to be infinite - [168df13](../../../commit/168df134c9d41e1a2f87106839369c0f493dc777) +- Calculator stamina buffed just enough to make stamina show up as a top skillset but not enough to make it overrated - [b5acc64](../../../commit/b5acc6406e338f55f95c2048e4cdf3b43eab0c43) +- Chart leaderboards are capable of displaying wife version - [237fc9b](../../../commit/237fc9b34381acb337e070e9b2504eba3d0f7fcd) +- Chart leaderboard information changed slightly to signify an unranked chart - [46fa50e](../../../commit/46fa50ed1cfb76cd0aff3abf6fe4486c0fd67eb9) [46fa50e](../../../commit/46fa50ed1cfb76cd0aff3abf6fe4486c0fd67eb9) +- Chart Preview being on forces all music to play in a synced state - [0de6294](../../../commit/0de62942ff3159ff2d75d924113cf5a7532ff8b4) +- Chart Preview in Til Death is attached to the old sample music playback - [373667a](../../../commit/373667a53bce24875a8e078f4b43289ff35694a9) +- Chart Preview rewritten into its own C++ class and allows the ability to follow PlayerOptions as well as loading arbitrary NoteData - [6449af3](../../../commit/6449af32f080d3f4babe1422e81f60e39e9c4c1b) [22a1f1a](../../../commit/22a1f1ac4f3bb8ea9728866522137a49b453b580) [845536a](../../../commit/845536acbffd6dbd110c3525255f2a59eb8d7040) [f161fcb](../../../commit/f161fcbcdea2fb74b73c05fc1eba7e41a9647d49) [8063385](../../../commit/80633850ec375be61b77c84a540e0098a69b180e) [9482760](../../../commit/94827605d1d3b9b93799a3c2d34272098a28fd50) [e4e023a](../../../commit/e4e023af571597c1613b18297654a9a8c593a2c0) [9263978](../../../commit/926397819bba918623f14317462d5c31fa1a5214) [cb91f06](../../../commit/cb91f06c19e15f9635f4462a357ae3722d264c23) [2b0dc35](../../../commit/2b0dc358117cfa4c7ae9c88e733eb0fc17fb823d) [ff47818](../../../commit/ff478180cf802ef72a9db18a2f8979149eb97813) [ff47818](../../../commit/ff478180cf802ef72a9db18a2f8979149eb97813) [b5ce723](../../../commit/b5ce72338a737c969366c9c647a8afaafca8620a) [77abb15](../../../commit/77abb15ea6bbae2849c8d04c7439643ca6f35b5c) [f2c5bfc](../../../commit/f2c5bfcae707082b5b5c4a95c2cdc8f61b8f2dc5) [0d8c09b](../../../commit/0d8c09ba3e47c50e43490aa786d6f2aabe21c0d3) [3a9a639](../../../commit/3a9a6392eb0354a39ab055d424fd958a614c8508) [3e5abbd](../../../commit/3e5abbd53de50b86ffed9858e81d074da59efe59) [4ec10f0](../../../commit/4ec10f03ac57cd18252c112e99e6fd93d8649883) [c0398a2](../../../commit/c0398a2ec2459df7a258b5f9b73f2e0ed29e0390) [5017323](../../../commit/5017323d37bc8ba5b8b422fd61af976484ff60b4) [169ec52](../../../commit/169ec5279cdfcfef0c5ae4b9f7e2f43e27d34aba) [2d6ff79](../../../commit/2d6ff799e9ad4e490472307ba5426c15ae166222) [45d3b18](../../../commit/45d3b186d18e814506277a2f94c3e9c22f4458be) [4f5e614](../../../commit/4f5e6142d9de3afc44b73b7d79f9a76ce45e0b91) [95c4793](../../../commit/95c47936fa76936e80f2bc2aaa88684deda3d24d) [1df6d32](../../../commit/1df6d32170f0bffe5e7a464ed1c34ae59dd72fad) [6c35397](../../../commit/6c353972ae5c5245f6c6f67f644dc2f8fafdb44e) [c5c0836](../../../commit/c5c0836823450e7f35c354e7934f807f4102ef33) [3d418c7](../../../commit/3d418c75455ab96976461c85be5b0985e62502dc) [b037826](../../../commit/b0378261e94b712dc1fb64466e897d2ee32cec7e) [209438d](../../../commit/209438df47cef253935089f2ed3c11659d3439b9) [36c62d0](../../../commit/36c62d097df8901211ec4d90da380bb047b07052) [967d10f](../../../commit/967d10fa743d309ce67684208e73938928fc9df9) [c3747c6](../../../commit/c3747c63d9f269eb5122c9f60b3b7abd866fc877) +- Crash handling system rewritten - [4126fc7](../../../commit/4126fc7890a2c5d53ce256187687d3d5d3adc737) +- Cryptmanager SHA1 and MD5 methods now use openssl instead of libtomcrypt - [ba5a8b4](../../../commit/ba5a8b4c2a3cbcb5822c4d2a27e5f2c9e8f7373e) +- CurrentStepsChanged and CurrentSongChanged are passed a param `ptr` that provides the resulting Steps or Song - [960e30e](../../../commit/960e30e69d0e7bc79cbebc4b151e380f2216bc72) +- Cursor in Til Death in fullscreen will now display at your monitor's refresh rate - [9f2a84c](../../../commit/9f2a84cbfd0a03804d6c147877b7a7f777777a55) +- DelayedTextureDelete is forced on for everyone by default now - [606e62d](../../../commit/606e62dd11a08389466b5b44c35b7467876a4831) +- DownloadManager allows access to the queue of packs and cancelling queued packs - [7b9b7af](../../../commit/7b9b7af4d67440fe2c95b7ce121e989c4263f3ea) +- Evaluation offset plot in Til Death has slight animation - [afd3dea](../../../commit/afd3dea1b9d61623d3f5603c1d7fb18a8a018f70) +- Evaluation should rely less on PlayerStageStats, instead on the HighScore - [597d382](../../../commit/597d3824b2ba4bc668794a5e73e5b1b2117a21d8) +- Fallback banner resolution changed - [5b633f9](../../../commit/5b633f90899983814405cc11a4605868fba16267) +- Filters will always check length regardless of exclusive filter state - [6db8dc3](../../../commit/6db8dc3baa8f28a3e1ae82c352baa2debfe23ab8) +- Filter rate limit lower bound moved to 0.05x - [daf0e6e](../../../commit/daf0e6ec03c53d95c5bad151709758524a5071c2) +- FFMPEG points to our own fork of it - [428803c](../../../commit/428803c0254165ba996a4b86059168833dcb4ceb) +- Game and Theme switches cause an overlay screen reload - [9e993a5](../../../commit/9e993a5e757b5bfd2bd99c2c0e1cfb1284c261c5) +- Game selection screen has been reordered based on general perceived mode popularity - [3b61cef](../../../commit/3b61cef6e8e7bacde78cb712c88f333f85b928bc) +- Gameplay failing is now a check for "the" player failing instead of "a" player failing - [31e346e](../../../commit/31e346ec8e0b046c58c11a21681197b05ca2d98e) +- GameSoundManager can access and modify running music parameters - [43d7d14](../../../commit/43d7d14330cae73adde3f4fbbd8d50180049449c) +- GameSoundManager Lua hook to sync playing music - [672ec95](../../../commit/672ec95d7714c6dc634583955705a38f713342e1) +- Goals are now priority 1 minimum instead of 0 - [55d4d54](../../../commit/55d4d54d37c7424b6f82533f0d648ed44c27f8e3) +- Goal tracker will ignore failed scores - [246768f](../../../commit/246768fce6de75633d4ef7667b58414c11e3f51e) +- Images packed with the game have been stripped of metadata for size reasons - [3811fde](../../../commit/3811fde156d2d0e2d2f67eb971697a8a9bf5ffa0) [5884b0b](../../../commit/5884b0b373ba9a5974fd350b6622c85f77b90dcf) +- Input mappings in the default column can be loaded and will never be overwritten - [43ca6aa](../../../commit/43ca6aaf339adb3dcfb7d978b1ad6fb54b5d7140) +- Input mappings up to 5 columns can now be loaded and saved using keymaps.ini - [43ca6aa](../../../commit/43ca6aaf339adb3dcfb7d978b1ad6fb54b5d7140) +- Key config now makes you wait less time - [ed867ee](../../../commit/ed867eeb107627554c913f8aab06fdb97d5e1ea1) +- Logging has been completely rewritten - [8ceedce](../../../commit/8ceedce57226d48da584ebbbfa77974061d57424) [743cae4](../../../commit/743cae4f3583f84aef154415755befe898ff7cb6) [7ca9add](../../../commit/7ca9add14bda178106612e9b2ce8af4ad069fde3) +- Lua bindings for viewing Evaluation and Replays return information on if the functions were successful or not - [1980844](../../../commit/1980844ee288cb975682070ad04be2347a4d2870) +- Lua is allowed to send a HighScore to the Evaluation PlayerStageStats faker function - [66a8d52](../../../commit/66a8d52a1084c3c179223fc27e275952407bbfd5) +- Lua is allowed to specify a timing scalar or window when recalculating PlayerStageStats - [78a69b0](../../../commit/78a69b0aea90758d3cb008d6eba630d5ec1f432e) +- Lua script for getting music rate added parameters for controlling output - [0b5df6e](../../../commit/0b5df6e6846e4e312d2e2aa9ef3abb3a1025c106) +- Lua scripts referring to CBs as misses are now referring to CBs as CBs - [e820078](../../../commit/e82007806425d244d424d0283222c8c2d2fd22b0) +- LuaJIT updated to slightly more recent version - [614c4d0](../../../commit/614c4d088218137cf2814561b43c5e4bfab39607) [055ec0a](../../../commit/055ec0a4aff7adb988eaa0288d27a6bbe0e03566) +- Metrics can determine the fallback Noteskin name for NoteFields instead of just the rest of the game - [521a699](../../../commit/521a69993ae19f12ecc1c3b19ba9a368f5531302) +- Multiplayer chat window resized and refactored for clarity and usability - [1efb16b](../../../commit/1efb16bb2146fe504aa784b3fc8238de6345ac36) [79aea80](../../../commit/79aea80834ca67664df1c058ec395d9e14608c6a) +- Multiplayer chat message sending is limited to 500 characters - [be74a6f](../../../commit/be74a6f278a0bf9a277bab4cce4c5e2d05dd55bf) +- NoteField disappears in GameplaySyncMachine in order to force the player to play by audio - [e4684d9](../../../commit/e4684d9b4aac35582f19e08ead4c920c9b8d8817) [b31e678](../../../commit/b31e678a8277b984cec1cb2216ad25ef54438a24) [a5a9804](../../../commit/a5a9804cd21372f18210f4d1378e649dc72c9f7d) +- NoteField NoteDisplays no longer sit in arrays - [cd27903](../../../commit/cd27903c7af99469d2a5f1bc38d954daa5650001) +- No longer possible to access Player Options when transitioning to view a Replay - [2f64656](../../../commit/2f646564355281c14a87bb0085b2bd09b204350c) +- No longer possible to cheat holds and rolls by using the slow/halt debug features - [0165b0c](../../../commit/0165b0c5c8b57de64366d5b63c794ea643edd0a3) [5ebf7da](../../../commit/5ebf7da6c29ecd35f68e6ea07724e151d4d4c2c0) +- Option menu cursors are somewhat faster - [00e2f19](../../../commit/00e2f195b4fe0f151177b05bcfcbe855fc436d00) +- Pack banners support movies - [3188e24](../../../commit/3188e24038f219eae4be2706f78827a40b28d9d4) +- Pack downloading messages are shorter - [6de978f](../../../commit/6de978faa69033fd4210cc0f99a4808f1239aff7) +- Player Options completely reorganized and split into pages to facilitate that organization - [e3ddf2c](../../../commit/e3ddf2cc88b0ee784b6da1bb0a98c4681b767835) [a170357](../../../commit/a1703575ae1a241e55eb0adbef8cb5d0d6e83ea5) [3a642bc](../../../commit/3a642bc24d728c2fd4395990058b0d1fdd41cbb0) [663040f](../../../commit/663040fedd66cdec63ff86a3315be575f89a8146) [877d559](../../../commit/877d559f3c85df4a7d029bf57e4bf9ec9d989f2a) [2a66db3](../../../commit/2a66db336a193a3d97ff62c244c601fd6cd6b607) +- PlayerOption mods which modify NoteData (called Transforms) all invalidate scores instead of just some - [2ea07a5](../../../commit/2ea07a5a7f83f65af9dba944c2a274fe29a1cd29) +- Practice mode resets life bar to 100 when changing song position - [3a8eb31](../../../commit/3a8eb31a9ff961c0c58d222896f507a30ef3ac51) +- RefreshFavourites is now RefreshFavorites - [db9a191](../../../commit/db9a1911d5006e5d2c17cec9f4a1bbcfa3ca47b6) [529ca01](../../../commit/529ca01aa8e7237abaf342362b77d4c41b6948a7) +- RageTexture subclasses keep track of the underlying RageSurface - [fbe045a](../../../commit/fbe045abc85076fd9872f6ecf984248bf9d29b86) +- RestartGameplay now respects the DelayedBack preference - [e3de4d1](../../../commit/e3de4d16158a7a6a13142cf9e1dcd00538ba565d) +- Reverse is now called Downscroll specifically in the options menu only - [61a9437](../../../commit/61a94377b663f39363d30f61e84cc5cff49d5f8e) [877d559](../../../commit/877d559f3c85df4a7d029bf57e4bf9ec9d989f2a) +- Sample music playback may also play the entire song - [d3e685e](../../../commit/d3e685ee536ce2d919d8dfd1318aaaecae8cb5ab) +- ScreenTextEntry Lua invalid check should actually stop the ability to accept an answer - [b3f61d9](../../../commit/b3f61d9dbe899ec9fae82bd864fc502c873a4fd6) +- Song cache version iteration - [205b57c](../../../commit/205b57c22cf39df51b7c0ec875e21cbec27dbb9c) [58cfbd5](../../../commit/58cfbd5dc2c9bc1cddc380da2824f2cb8ea9e0d7) +- StageStats no longer required for ComboGraph and GraphDisplay (life) - [39dd13e](../../../commit/39dd13e3bd9b57ff20b125accb23848bcc39c330) +- StepsDisplayList for Til Death is now written fully in Lua - [ab1dde3](../../../commit/ab1dde3a44fee1036ce229c466232528982ee72c) [bb22126](../../../commit/bb221265313dc8970222d7f7cacec0ce12679192) [fc50e2e](../../../commit/fc50e2e17346936a6ac8af43adca4aab384d38ba) [ca8b014](../../../commit/ca8b01470e850a2269ce3ecbcbd4f78e91e160ed) +- SurviveSeconds has been replaced with PlayedSeconds in HighScores - [7beb4bf](../../../commit/7beb4bf3a5a3436366be4f6b16970d4b934e3d4a) [e449093](../../../commit/e4490934381872de7455f9d685057db3af640666) [f0951e1](../../../commit/f0951e1b122c7995cac12116edb698539aead0a2) +- Sync overlays have been adjusted for visibility - [02d1d4f](../../../commit/02d1d4ffc3ab5c4fcd31717f95faedda77f4c7c9) [e0231c0](../../../commit/e0231c07285ba1601ac925d4dd40ec080341f4de) [004936b](../../../commit/004936b35dd2280b0c1b96abc3565b6e65d72c72) +- Tabs to space for a lot of theme stuff - [fc08911](../../../commit/fc08911f3412abbc493988ab690af759b8491092) [ab7288f](../../../commit/ab7288fafba4a7f6f2aea1bb6c9195bd0eef898e) [a22f97e](../../../commit/a22f97e9a5f10f50b2dd2593ce90f56e65cb0c5b) [9692b64](../../../commit/9692b64d94e8249b672ac2b8b809049c77713719) [9205543](../../../commit/920554344e1be4271b142b99c39bee5e032c3e68) +- Til Death Asset Settings changed for readability - [6c45237](../../../commit/6c4523755e1084cae34789f0ffddd9049783420a) [db21b2c](../../../commit/db21b2c56dc8876e96db7a2bf9fb0bd3e221918d) [f9c361c](../../../commit/f9c361cd8dd4ae8f59b0359efda1ed0dc4b86c39) +- Til Death branches file cleaned for duplicates and fallback pruned of useless paths - [5dca315](../../../commit/5dca315d70c748320d04075800f225dbd328b041) +- Til Death chart preview considers all scroll mods - [3ea97db](../../../commit/3ea97dbe9725801c41e6195a062c039f0636a710) [20a354d](../../../commit/20a354d04c6816151fea13415e36b5781de6387b) +- Til Death color config does not save alpha for scenarios where it is not needed - [1dd6181](../../../commit/1dd6181c5932afd078e8d16c900719b15eaf5c3f) +- Til Death debug text output is compatible with Lua tables - [d08d045](../../../commit/d08d04596cca3ad5af438aa0700148fee9bc5c75) +- Til Death editor link is probably safer - [49d2782](../../../commit/49d2782fb6e8d4f773b7734945b244d05eb65b54) +- Til Death evaluation minor changes to positioning, sizing, and quality - [29b5bfd](../../../commit/29b5bfd7c9eb39f7eb8ddd774cdb2983396e136e) [e8a2ef0](../../../commit/e8a2ef02010e98bcf9c44c4d449c1924289752b6) [81d9c0a](../../../commit/81d9c0ae0cb693dfe40145cf7517a66ba748cb8e) +- Til Death font is now doubleres by default - [4e3ac7e](../../../commit/4e3ac7e73133949ef5114e117fd2c5471465cd81) [ee63e78](../../../commit/ee63e78c3fc0cacdeef0d1901a9662a199a846f1) [89765fc](../../../commit/89765fc2fcae0ea5b92f39d43945e3b3e0b2c9a8) +- Til Death footer is slightly reorganized and recolored - [63ceea0](../../../commit/63ceea01da946ebcd668dcbd8f6f7302f4f0b0a1) [9ab1a4e](../../../commit/9ab1a4e784add7579fa643194b1d76ad3996d116) [f3cc213](../../../commit/f3cc2139ba27e74c307bcae112beb6f5eb45e568) [ef4d232](../../../commit/ef4d232e8a2f3d87be335de7495fbb6bb3629c32) [6169418](../../../commit/6169418c0451ffe701aaaa15d58c44cbf44011e5) [2c6aad8](../../../commit/2c6aad88e3351069c4191c2f7e2f84f49365338a) [5c38362](../../../commit/5c383620b019959c6d5436f3b8a1d5f5819409c7) +- Til Death gameplay elements very lightly touched, for clarity - [f02b2d4](../../../commit/f02b2d4a72d3c591b3875ef9404d9486a12e27e9) [79aaf6c](../../../commit/79aaf6c75c56291f62fd19a6d139165ed2710adc) +- Til Death init screen was modified - [88d5b02](../../../commit/88d5b023f459eae53963093b82fbda4edc64ae01) +- Til Death login is slightly more controlled in terms of input and continuity - [c900172](../../../commit/c9001729aaa5f6015a88d087d4714d91e7826188) [3045f46](../../../commit/3045f46ba19e1f3bba2fa6f38b22d4cdd3815c8b) +- Til Death minimum music rate is now 0.05x - [6ad9431](../../../commit/6ad943194f2f8b86865b38503f7227514af1099c) +- Til Death mouse input redone, all button logic changed - [2a7e252](../../../commit/2a7e25284e4c0838d9b9b5088bd8552c59ed9022) [3cf5a9c](../../../commit/3cf5a9c17206e71ec5b242f5288cf9c218e39403) [cbe7937](../../../commit/cbe79371e54716f9151b6ad6156a582b07fa8446) [00fd30c](../../../commit/00fd30c9cd9089ecbe6b8be013dd5bb0d539c69d) [48d4448](../../../commit/48d4448525738042e987b266ba733448c4f7c35c) [b04511b](../../../commit/b04511b22d52c1842602cf58e108fc20efba553e) [7d0b2e8](../../../commit/7d0b2e836adee5872b5135a706bf5974c0c4d4a4) [562e3b7](../../../commit/562e3b722469e0414d621769a27d7aa8c7c2e1b6) [bbbcb9c](../../../commit/bbbcb9c677f162da2ddce17070f775acf366a8fb) [7b56014](../../../commit/7b560140f6fca014a1111f837499b4c9815bef45) [f084077](../../../commit/f084077fcc06b4753bf62a6c25877a0f17e62db7) [7923109](../../../commit/79231093bd8bfad351a8d43564e40660ab6dda8d) [f2a7609](../../../commit/f2a7609122889370508fed953fa04ea0ff455a32) [bb78c72](../../../commit/bb78c72990a8ea4f9f4e27da9bd1f37f96b16014) [77b8d1f](../../../commit/77b8d1f88890903a1bf759696563b253f42af1db) +- Til Death multi Evaluation merged into regular evaluation to reduce copy paste - [c3bc2c9](../../../commit/c3bc2c972677d48f4bf3a6c05b0da1c2c9a4a3b2) +- Til Death multi Evaluation scoreboard player cap raised from 10 to 32 - [09eeacc](../../../commit/09eeacc10aab398732d0e0fc85e16bf4dec8bfbd) +- Til Death multi NetRoom reviewed and cleaned - [5e4cd60](../../../commit/5e4cd60bd7e797c3da8747c537b6c8a16decb451) [520af8b](../../../commit/520af8b5882ed03ef3c25eaa51aa1483effd757f) [0db9c1b](../../../commit/0db9c1b25f00c2dc719405ea26d0f070bfbc9026) +- Til Death multi NetSelectMusic tabs made less bad - [ff971d2](../../../commit/ff971d20ffd21924de0a4cd136b2fa177c4c2c94) [22f9a62](../../../commit/22f9a62683cc387fe6dbf35bdf9df60e90fe1e7c) +- Til Death multi user list moved and changed a little - [02b303d](../../../commit/02b303d51143290f78758e1f02d726a4d2d3a81a) [ef1bdc6](../../../commit/ef1bdc6ff690c22e1128d636b272d82bcaf45d56) +- Til Death musicwheel scroll bar can be dragged - [1355068](../../../commit/13550685171a7c503b454105ebfb4958cf387661) [c1cad55](../../../commit/c1cad55d6bd0864b0d098ce91143f7a60cfefc85) [48ac479](../../../commit/48ac479f4512f7fef45f4ce75beb9335bac315ee) +- Til Death musicwheel scroll bar position made slightly more accurate - [99fbff1](../../../commit/99fbff191b8b099c2b1bca32d6db31cef6a3e078) +- Til Death no longer says it is saving your profile when exiting a replay - [d2ee3c2](../../../commit/d2ee3c2b08c43cac8e9c0c84b1694ca7385c0509) +- Til Death player name display is restricted to 1 line - [3c0c19f](../../../commit/3c0c19fac627ad0986fef5ea06b2cd4d4a291e6e) +- Til Death playlist entries have better visibility - [251e43b](../../../commit/251e43b6f4ea8da58c4209998ade5d0d0c229837) +- Til Death playlist/profile lists have fewer entries (20 from 25) - [69c60c5](../../../commit/69c60c55c842566482e78d2a6062be45cda32e68) +- Til Death scores tab can be pressed a second time to switch nested tabs - [382125a](../../../commit/382125a43279342061859588be6f3a4cb325c91f) [b370bd8](../../../commit/b370bd851aab6db5d08bc2432f55ef452e0c0b95) +- Til Death search has an added theme preference to change the behavior of pressing numbers and ending up on a different tab - [2828be1](../../../commit/2828be13565006be6df7031f93fade9ebc8e8114) +- Til Death select music tabs have had numerous basic essential improvements to clarity, function, readability, etc - [de6b570](../../../commit/de6b5707f9a067be47777f865fab3b3e5b80ccbd) [9803a75](../../../commit/9803a75c220e5b8091dcae40619023592eab3870) [8598772](../../../commit/859877281fffed9140b4ebd0848a1e8dbaf88368) [8598772](../../../commit/859877281fffed9140b4ebd0848a1e8dbaf88368) [6841217](../../../commit/6841217eb67059a74f7c5586f897918989519a12) [f8ff4de](../../../commit/f8ff4de4bb4825a660120c1a53018c221be826a4) [7c14b98](../../../commit/7c14b983e4566488bc80a7936422f40005534c73) [d234ee0](../../../commit/d234ee0ab6e957423dd83c552070e25f59b8a46a) [90adb1e](../../../commit/90adb1ea40a5d33ca4533b6f319a2f5dc51ac650) [b445f8c](../../../commit/b445f8c58d9468e7e47170e79db78d443f62bc09) [8fbe95b](../../../commit/8fbe95b6801fd5289458f6674ff9e7bdc0527f09) [66a042b](../../../commit/66a042b05647dae529f5531ce4a913e860232582) [2cec6e5](../../../commit/2cec6e561efca76dcb052f12ce00659bdc612df0) [2ad81f5](../../../commit/2ad81f55c4bdd11a52374bd43b045ee47e930d9b) [c6873ac](../../../commit/c6873ac626f779722e70df347d370cec5cb35d3e) [e2a985a](../../../commit/e2a985a9cd4a75f7fc0a34c0928e356d7ea961c9) [066d7eb](../../../commit/066d7ebb2b4f3ac9c6e9e739768e6ebb6606ebd6) [a057f5f](../../../commit/a057f5f0479cbcea3b79c50127e548769578fc21) [0edf88e](../../../commit/0edf88e422e0f1c89155bcfadc1a9053c5256847) [037b5d7](../../../commit/037b5d742a818e4a043fe343a25157edab9f2c67) [95c3dbf](../../../commit/95c3dbf8ccd36e2006aad962d0b82fd9798f6007) [e519952](../../../commit/e5199522108828a3478d2a14bd42a88d0c7a7df7) [95fdb3e](../../../commit/95fdb3ed779dcc32717db05aa2ce693460478111) [3d33a11](../../../commit/3d33a11d3565bbbb89ef80238382f1682e929d26) [07890fd](../../../commit/07890fdf0e1fbbb7eca7534a7511ac7799fba172) [eb41c5b](../../../commit/eb41c5b7c7083aeb494fc1878a69cee7c565e8bf) [72edf70](../../../commit/72edf7017bf37edcbd6909aff94e934b0138234c) [33274c1](../../../commit/33274c1791a4fbb64df851327a78524b3b1f6a22) +- Til Death select profile updated for consistency - [5e32f47](../../../commit/5e32f478c6005eb46e6b8e70fbd63096598c8d93) [dbeb748](../../../commit/dbeb748a5553ac9d3ea28c2d09523777ee17d83a) [8932c67](../../../commit/8932c670b0e05c8cc730f7b3da275c6f13f09e23) +- Til Death text entry screens allow for a length input of 255 - [070346a](../../../commit/070346adabc7fdac95ed7a00d7405c9b3731d6a7) +- Til Death text entry screens have no transition and are less jarring - [80a3428](../../../commit/80a342802fd022d7023b54b1c4e1bbe676244931) +- Til Death title screen shows version and had minor color changes - [6be20f6](../../../commit/6be20f697a963ce3c615388f1fc8384c3bec707a) [da460ba](../../../commit/da460bad1102398e02903c8d99f432cb743251e4) +- Til Death top 3 skillsets do not show on charts outside of 4k - [cee1c90](../../../commit/cee1c909bc801592f661205feb5aa548e52e4efe) +- Til Death wifepercent displays raised precision in some places - [6a27844](../../../commit/6a278441d758e0fd914f928dbb74c9ab98398b39) [6c9036c](../../../commit/6c9036c4e35d60ee3c8bc55b442e05de34bc8709) [20ad19e](../../../commit/20ad19e2c770aa862f1b20ef2936aad518f11bbe) [2171b26](../../../commit/2171b26831d3b7ecf5fae5a97d52632ed250baeb) +- View Eval fades the screen when used successfully - [ce4d7cd](../../../commit/ce4d7cd65a4ad068931e999b8efb3b1c81b71dd8) +- VerboseLogging renamed to LoggingLevel and a lot of logging messages redone - [e2385a9](../../../commit/e2385a907e8e079c81f8fe51e6244497f5feeb96) [28ba767](../../../commit/28ba767cb311313ea3878646353f35881e83677a) +- Volume level is now actually the volume squared to make the perceived volume more distinct between low and high percentages - [fd8e8f3](../../../commit/fd8e8f3809e6cafa4528dcc32759aff8955a1d7f) +- Volume will now update dynamically instead of requiring a new sound to start before being set - [fd8e8f3](../../../commit/fd8e8f3809e6cafa4528dcc32759aff8955a1d7f) + + +### Removed +- AllowW1 (Fantastic Timing) - [613dbc4](../../../commit/613dbc47284c789e77ae46a1523657ef927d57c7) +- Calculator assert for 0ms deviations that caused too many crashes for Linux users - [7a87135](../../../commit/7a87135979cfc5cf2902b095108581c7c7fc8b25) +- CelShadeModels, PreferredSortUsesGroups, RegenComboAfterMiss, and MaxRegenComboAfterMiss Preferences - [780acf3](../../../commit/780acf3e3083e6283f56ce81db70922409553849) +- ComboPerRow and MissComboPerRow Preferences - [1f3ad79](../../../commit/1f3ad7969172c9753115e86947ae20a56614e263) +- Cryptmanager UUIDs and useless things - [d99d824](../../../commit/d99d8241f408958e9a0d70258fff89cf38f38cfc) [ba5a8b4](../../../commit/ba5a8b4c2a3cbcb5822c4d2a27e5f2c9e8f7373e) +- CurrentStepsP1Changed, CurrentStepsP2Changed, CurrentCourseChanged, CurrentTrailP1Changed, CurrentTrailP2Changed - [960e30e](../../../commit/960e30e69d0e7bc79cbebc4b151e380f2216bc72) +- Deprecated findsong/randomsong functions - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) +- GameCommand to search for a song - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) +- hELPidontDNOKNOWMessageCommand - [5a7be67](../../../commit/5a7be67b6e19a137695ae52eca299152e9888d8f) +- IsGame("pump") usage in Lua - [1134946](../../../commit/113494657668a48a8d08cdb23777843418850d8d) +- Libtomcrypt - [ba5a8b4](../../../commit/ba5a8b4c2a3cbcb5822c4d2a27e5f2c9e8f7373e) +- Libtommath - [040df3e](../../../commit/040df3ec15240228e1b9c4f8d522e8858c112e19) +- Per-profile chart edits - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) +- Picosha2 - [fa7639a](../../../commit/fa7639acf2b23e054645453996a41ac96d1eb676) +- Player Transform Commands for Combo and Judgment - [529d3e6](../../../commit/529d3e6efb454d24e9431f18b0684a54022e2ae9) [900d7a6](../../../commit/900d7a6f2e48b42283c8d83cdf436093f913a7d4) +- PlayerNumber stuff - [2eea76a](../../../commit/2eea76a24332aa62c89c3a4efe127fbf2da8f661) [667d6d5](../../../commit/667d6d5e0f156618e61ff946d74de6a7be8fe7e0) [83d5f7d](../../../commit/83d5f7d14a25936a9c0ba58a867e062fdd46f1ae) +- Preferred songs - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) +- Profile tab in Til Death clicking would change rates and that got annoying - [dd047b2](../../../commit/dd047b2b95f88bbe7e91f573ec9ac646e2d4b2e8) +- Profile signing - [c05316c](../../../commit/c05316cfeb71c87245e50dc1a3e9ab97a6a5812c) +- Pump specific visual features - [a0c82db](../../../commit/a0c82db6d2862275f76ff91ef53fe8e1aedadcb8) +- Restarting after a file has ended except on fails - [80ef4b9](../../../commit/80ef4b9eba8d6a59e026bddbf483480bb24f001a) [09c1c9f](../../../commit/09c1c9fdc3c235085781efc66ee188d4d764d27e) +- ScreenSelectMusic local multiplayer joins - [5d5a83c](../../../commit/5d5a83c078f517c01a4f9afd51f6b166a65f80b1) +- Shuffled songs cache - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) +- std::vector in global.h - [3d6c468](../../../commit/3d6c468131e1c8d961d0399d99a47caf7721e579) +- TestInitialScreen Preference - [29c0955](../../../commit/29c0955cbe0f8b08b0ff0a7ae6e6946e50c5e7d6) [fa68af2](../../../commit/fa68af2324d5a9905ae79358fed733d7bf269cf6) +- Theme Preferences for each specific Game - [d2e8d0d](../../../commit/d2e8d0d2f997efd1a76d756b8d0cd709f88a1afb) +- Unused default noteskin option - [1aaf7b6](../../../commit/1aaf7b68e96150e99e21283345d31d2332d68e88) +- Unused putty crypto folder - [3a1848a](../../../commit/3a1848a01054ffea510e8972f97ce20c787acb65) +- Unused theme stuff - [8bcd567](../../../commit/8bcd567a747b7a790a55f9e4b92f1faf62c784cd) [0751255](../../../commit/0751255021f5b78194c9162f4baf5ce9aab700da) + + +### Fixed +- Aspect ratios that aren't 4:3 and 16:9 in Til Death allowed for weird clicking behavior on the MusicWheel - [9b58864](../../../commit/9b588646a920ff903ffc79aae927a8763eb30fd5) +- Asset settings menus utilizing the fallback scripts had wrong ordering - [e02da27](../../../commit/e02da2764d40a278c2dc07c24e7ca0b212720557) +- AutoSetStyle broke certain screens and transitions - [4402f80](../../../commit/4402f8005cb1edbd2e98624ce70933a8f2143dca) +- bare-frames MSD display not updating when changing rates - [1e87052](../../../commit/1e870522683a2a119af68a8c982e1dbbec3ec4f5) +- Background videos failed to render for all openGL contexts - [c431382](../../../commit/c431382a81fa8351dfba746813037a34427fb111) +- Binding up and down to Gameplay buttons broke the up-down combo to close MusicWheel folders - [0537ace](../../../commit/0537ace574d815f8731aaaa331095f9d2b24ba92) +- Building MinaCalc alone separately from the game would not work using cmake - [c8dc2d1](../../../commit/c8dc2d1e988913393c9d691e9a08c6f4e98d32a7) +- Calculator data structure uses of std array broke MSVC - [8c4abf5](../../../commit/8c4abf5214c6808c36634c533fc25e8a2ef5e049) +- Calculator data did not fully reset - [0b7a28d](../../../commit/0b7a28d2371798a8138e78e5789d0014b16b4534) +- Calculator debug data for jacks is broken so don't let anyone see that - [7c265bf](../../../commit/7c265bf4b59331341a1573c3cc2dae0082182e64) +- Calculator debug graphs drew so many lines fps was trash - [9d5f336](../../../commit/9d5f3366c8b68dc358a250667c3bc5b8339bdac4) +- Calculator debug parameters stopped loading in almost every case - [05c7a06](../../../commit/05c7a0691f44933ed152b4054d3a0294a4a63d0b) [817c913](../../../commit/817c913ce6d3d4771480eafe1f8b64e723b2ee87) +- Calculator debug updates for visibility - [72e0007](../../../commit/72e000785fbbefb6d3c4ee4192cfb00004786c1b) +- Calculator forced recalculation did not actually update player ratings - [a25ec25](../../../commit/a25ec2504c26c85ae43dcf5cfd43d31a74a6f45c) +- Calculator jack difficulties were erroneous when a hard anchor is followed by no more taps on the same column - [0108194](../../../commit/0108194ec752c1e8ef197702490b0e64685d249b) [1c5efde](../../../commit/1c5efdeb4bbe3702cfefe93da70ac91506e1bb5c) +- Calculator state could be stale and reloaded in a way that breaks ratings - [ce9bfcd](../../../commit/ce9bfcd2e689adf0e7e3305868b44112320b5dfc) +- Cached song load loaded some songs more than once and some songs always from disk - [2af923a](../../../commit/2af923a1bfd816ecde569f859dc6509e1f149a3c) +- Caps lock not properly considered when pressing buttons on Mac/Linux - [9dc4e00](../../../commit/9dc4e00ed0fe0caa1bb6fb25fe159582d1497e03) +- Chart leaderboard entries had no Grade set - [9d57e7b](../../../commit/9d57e7b764099141190cd385d478f77da46fe193) +- Chart leaderboard scorekeys were not set until viewing Replays - [cb2069f](../../../commit/cb2069f0ca72a679f4a599a1a5d85b986be71558) +- Chart leaderboard wifeversion was not set - [f95912a](../../../commit/f95912a66d72311abb5ccae0a3515b579163ac73) +- Chat overlay showed up in some extra screens - [6401086](../../../commit/6401086228574def3472279295adae30030ce6ec) [3156cba](../../../commit/3156cba2303ced6f190e31a51d61c21d9274431d) +- Chord density graph did not support keymodes outside of 4k in Til Death - [0a62dac](../../../commit/0a62dac89b547806e43af3d35b0f919a802f2108) +- Chart Preview NoteData did not follow proper beat scratch positioning - [36c62d0](../../../commit/36c62d097df8901211ec4d90da380bb047b07052) +- Chart Preview clicking positions and progress bars did not match the playing chart - [0912e85](../../../commit/0912e85145e26e289e87a5f5887553f5f832c2e4) +- Cleartypes and Lua score displays used the wrong kind of Grade from HighScore - [0c60dc9](../../../commit/0c60dc935092a5ac588c9169e72409fa5c4ec89b) +- Clicking a goal when the song is not able to be selected would change the rate for no reason - [bbad86f](../../../commit/bbad86f86cc14d74f2ef7c13ae54a34a0371e6b4) +- Clicking a playlist when a song would appear in the menu immediately after sent you to a song for no reason - [2b2b5a1](../../../commit/2b2b5a1d6594654757827aba29f9336086c1932a) +- CodeMessageCommands in Til Death reduced due to buggy interactions with Gameplay bindings - [60e47b4](../../../commit/60e47b4d1e693e0c8351a66c3a293e663c0706d3) +- ComboGraph not synced with actual song position - [b033e70](../../../commit/b033e7022bbc9b70a96bdb301cee65e69d0ddb4b) +- Crash when closing the game - [7939ca2](../../../commit/7939ca29d2221fae4f4550832f8e3f59eabbb1cb) +- Crash when exiting GameplaySyncMachine in some cases when somehow the Style gets unset - [744ae0e](../../../commit/744ae0e06fe57b01d93074101178c166820bb665) +- Crash when getting HighScores when there are no HighScores - [901cfd9](../../../commit/901cfd9aae6e47f8a1cb278da978a03a2d9e0af1) +- Crash when getting newest HighScore when there is no HighScore - [376d140](../../../commit/376d140da8f5964efdebba34dbebbe237a1a0eda) +- Crash when getting Replay snapshots when no Replay snapshots exist - [244a61c](../../../commit/244a61cc28b9ffd61c58681212c79a19298d4a25) +- Crash when hovering a Song with over 24 difficulties due to DifficultyList limitations - [0a9e15e](../../../commit/0a9e15e1649881dd11d471c1d9e256f6f93fd83d) [ed479cf](../../../commit/ed479cfd51e905f048541a50890938bfa21d6f34) [a06f24a](../../../commit/a06f24a8b666e45b2d84381bfa33ebc75f0ba42f) +- Crash when passing purposely incorrect parameters to FilterManager Lua bindings - [c8c8025](../../../commit/c8c8025b2e24bdba864732f5ee6d32327bbbce54) +- Crash when PlayerState disappears before or after entering Gameplay - [7a3d4ea](../../../commit/7a3d4ead7fd7bc50f381e63b989e927244a9139a) +- Crash when pressing any button in Gameplay without any NoteData - [3ea10c8](../../../commit/3ea10c8e6bd0bb4c525e299a0a6653dec1b2b902) +- Crash when pressing RestartGameplay in GameplaySyncMachine - [83b2fc1](../../../commit/83b2fc1b17a868d3b1563e2d42521b4480ac7a3c) +- Debug output for jack stamina broke - [60aa6a4](../../../commit/60aa6a44b5742c7bd1a332aaa06332c77d7e35e7) [2144274](../../../commit/21442741c1b931931f17a9d952ce3f98628c2561) +- Deleting Favorites playlist directly crashes - [e90d616](../../../commit/e90d6164dfb533e08aea7fca53d88be7ba99e6be) +- Deleting Playlists leaves behind ghost playlists - [5c09dfb](../../../commit/5c09dfb89d4697a1750c3624bdfdb6505e0d64b7) [1a31542](../../../commit/1a315429d08dd6c6039cdb9801ad02ed0156995b) +- DirectX in cmake rarely hard to find - [fcdfcd2](../../../commit/fcdfcd2d3f357d93c7255d71995d105f58fcff53) +- DivideByZero and SubtractByZero misalignments - [c9c2b89](../../../commit/c9c2b89c2171e4bf525d7cba4659aad33b78e57c) [293b2d1](../../../commit/293b2d12413d880fb28b67e244987aeaa1f72671) +- Downloads coming from mirror links point to the wrong link when checking the queued/running download again - [e7ae391](../../../commit/e7ae39158486c6696b27c8defc318ab18f29678d) +- Downloads of bundles no longer forces mirrors only - [9545850](../../../commit/95458505fb78cf4314c3d0f797f01cd842d14516) +- Favorites Sortmode was folderized nonsensically - [fc697c3](../../../commit/fc697c34202ce3b855c4a72960880c221a5096ce) +- Filters considered every single chart on a song even if the chart is filtered out - [fdbdc20](../../../commit/fdbdc20b63a13b010839ba7b903a6e34ca6b393b) +- German Til Death refers to Etterna as not Etterna - [e8dd93c](../../../commit/e8dd93c304524aa41ab0458cd37267ecf39ac9c4) +- Git information didn't correctly appear in log files - [922e6a1](../../../commit/922e6a10bbff1a88d7a4b487f344860a51693258) +- HighestMSDOfSkillset returned wrong results in certain filter conditions - [fef0d38](../../../commit/fef0d38126879c22ed8032fb198df0245a31a3a2) +- HighScores with the exact same wifepercent or ssrnormpercent would be considered visually one single score - [6e4d19b](../../../commit/6e4d19b7d4086215e37bd868b653639831c71424) +- IsOver failed to consider vertically zooming an ActorFrame in the Actor tree - [e6f853f](../../../commit/e6f853f60b532919eddabc1d890e4561e809bb2f) +- Key config order was nonsensical for dance, pump, and solo - [2d9f5ff](../../../commit/2d9f5ff2a6100ac6feb37704628f9c6e637972f4) +- LifeGraph not synced with actual song position - [a47f47e](../../../commit/a47f47e3d7e65acb0bdd4158a8e3c165ad21fa06) +- Linux audio broke - [8340d00](../../../commit/8340d00a6321e03ac648a405825d0f3e0f9a7536) +- Login failures sometimes didn't indicate any kind of failure - [942869f](../../../commit/942869ffdcb586814d16b11ac6bac4a2c6556d31) +- Lua errors when evaluation is loaded without a replay - [349dd11](../../../commit/349dd11650020291823813f86f140f7ba56bc2b0) +- Lua errors when loading some broken noteskins in noteskin preview - [4b01e2c](../../../commit/4b01e2c276d10a63d2dff31814d5a12db174cc69) +- Lua errors when no scores are present in multiplayer eval, not even your own - [71f4365](../../../commit/71f4365f198da3e0c2b9dc87df275226100ba492) +- Lua global t was initialized on the title menu - [232ea03](../../../commit/232ea03bb5a1e75f3129967817396d9be1894406) +- Lua redirected input did not reset when pressing the key config button - [2cb16c6](../../../commit/2cb16c66286ae0dc55eb50af07245020a1d41463) +- Lua redirected input did not reset when pressing the service switch - [acb6209](../../../commit/acb6209fbea1ee10bee09177159c774cc14d1870) +- Machine sync screen crashed when used in Solo - [709f383](../../../commit/709f383eb329c5e054a04571ec6a8fb8c2226ded) +- Mac Big Sur could not run the game or compile it due to Lua - [614c4d0](../../../commit/614c4d088218137cf2814561b43c5e4bfab39607) +- Mac Big Sur could not run the game with audio - [f774e45](../../../commit/f774e4563dce49024fc9c43eada6ce09d2718c52) [e3015bb](../../../commit/e3015bba03225148db95401be98122f08dc1fb5b) +- Mac could not easily load local files due to app translocation - [c071278](../../../commit/c07127808c09f1f35e081b98483daaebb6c79878) [d227edd](../../../commit/d227edd13a473bca426b0a5fe160833e03b93bf9) [4849d1a](../../../commit/4849d1adbe9de7c9c824c53f8e087bb072eab06d) +- Mac could not compile on latest versions due to FFMpeg - [8d316ee](../../../commit/8d316eeea447b0b4cfd71488ce56e6e4cd747417) +- Mac M1s could not compile the game - [53ee619](../../../commit/53ee619149e58e4e3a1ec5fa28fea2912737d71f) +- Mac Retina displays made the game look insanely bad and tiny - [c543c29](../../../commit/c543c2904258f9c43c230cfdfa02d0887d154bbb) +- Malformed login responses left the game in a bad state - [57857a4](../../../commit/57857a4e9fec96c5ee07083a75d11f844b40d072) +- Mod strings had random commas - [4ccb355](../../../commit/4ccb3559edeb57dcd324cd5296f2411c9e97c52a) +- Months did not properly translate for MonthToLocalizedString - [20bfd45](../../../commit/20bfd457bf3b412b28956c457f2d4f76de40663c) +- muFFT memory leaks - [50fde44](../../../commit/50fde44cb831ca16ffa558fb7ba99b88dfa7c414) +- muFFT was preventing Debug target from compiling - [c05316c](../../../commit/c05316cfeb71c87245e50dc1a3e9ab97a6a5812c) +- muFFT crashing might be fixed, we tried like a thousand times - [b7fb03c](../../../commit/b7fb03cc7225be2b848d6c9b3bc21d8a3efd77a6) [6238ea8](../../../commit/6238ea88905161799d40af1007c72268b5ebe213) +- Multiplayer NetRoom names could be blank, breaking everything - [cd99d68](../../../commit/cd99d682dc80f0cc61645a03ccc44926c1dbf227) +- MusicWheel caching for each sortmode did not work properly - [a2136a1](../../../commit/a2136a1e7910fd9e9240e6b57a6f3fc8136925f1) +- MusicWheels of extraordinarily small sizes caused circularshift to crash - [2eb4c98](../../../commit/2eb4c984ee5d892f64eb3fc611976100354e4f68) +- MusicWheel showed grades of charts that were filtered out - [a341052](../../../commit/a341052692306fed2ad638f675124421a378724a) +- NoteField children other than the Cover and Board did not draw - [39a1361](../../../commit/39a136161391e6e03f996973abb200302cacd78f) +- Online scores that could convert to wife3 because of replay data didn't show that they were actually wife3 when converted - [cad313a](../../../commit/cad313a7434fc6bd2dd3f322be716eb02eebf5f7) +- Osu files could not be synced - [eb1585c](../../../commit/eb1585cb424ef322b350e067f0764179d8ad76f4) +- Osu files not loading the first difficulty due to some reason caused crashes - [c920350](../../../commit/c9203502ed0f24d87438b3e46075c3881a037527) +- Osu files with extremely small numbers in BPMs caused crashes - [50f320f](../../../commit/50f320ff6505157e5890d39b2d3e3ba2e564f37f) +- Playlist courses caused double saves on the last song - [9ff834c](../../../commit/9ff834c86048ee83fbaa5a061b6b1f976d6af4d6) +- Playlists functions would crash the game randomly - [381ce6c](../../../commit/381ce6c9bea2bfbcd4b766069e0322c5a432195c) +- Practice Mode reloads did not reload sync data - [8ab274b](../../../commit/8ab274bad110cd8bc3f61d7042ea324b7bf75b81) +- Profile did not save failtype, battery life, draintype, and lifetype - [8b67e4a](../../../commit/8b67e4a31c160cfad84201387b4135a0050c0992) +- Profile skillset field for Overall held garbage data and required a hack to not need - [59a248c](../../../commit/59a248c23468ad1916a7bbcafb21165edab0565a) +- Replay snapshots would not skip unjudgeable notes and called them misses - [8618511](../../../commit/861851188c96942b8a64da4af10123f4fcf5c74f) +- Replay type was not populated when creating HighScores immediately while finalizing a score - [1ba0f4d](../../../commit/1ba0f4df48107006667df9ffbed3d03c42344b88) +- Reloading packs or songs from disk could get stuck by pressing the button for too long - [528b65e](../../../commit/528b65ecb1f71e1d2396e1b672d80b611d42f500) +- Replays dropping holds caused ReplaySnapshots to have wrong percents - [fc5d5d3](../../../commit/fc5d5d32b8c81f6888877be92cbb5ace2c0521fc) +- Replays use wrong hidenote value and don't disappear notes - [939bcbe](../../../commit/939bcbe05b46e5fffb686ae6f8cf00a48bf68def) +- Replays hitting mines caused impossible negative scores - [3f5ce78](../../../commit/3f5ce78ecb0b971dc72d40a6481c900c7761d339) [a5517a2](../../../commit/a5517a2460ad0ac953c51cc59ba4da33a3c30566) +- Replay data received from online no longer has 180ms bads in place of misses - [54b123f](../../../commit/54b123f790fd0e3d85760eb960d63f91420073d5) +- Rampant texture memory leaks due to bad IDs or something - [d7dcfc6](../../../commit/d7dcfc663d325a4a0024273f86e2f57e59185bb2) [461695c](../../../commit/461695c1d217697992e00208982c0f0ea250d3a4) [e58b54f](../../../commit/e58b54fa8c583fab2a051cda6ab01e22b8b9bd11) +- Rare crash due to keyboard reported scancodes causing undefined behavior crashes in Lua input - [fb7c101](../../../commit/fb7c1012c92b48e4f92746d6ac1208db50a33bae) +- Rare crash due to the location of the script for logging in within Til Death - [41344c1](../../../commit/41344c12f6f4ddc0478355db0006a6dc70c3fdc8) +- Sample music playback after playlist creation caused errors - [f8d1b0e](../../../commit/f8d1b0ea6e78644cfe47e8e15a5503bfbe17b9ef) +- Scoreboard Evaluation Til Death scrolling broke - [57ca384](../../../commit/57ca384e06a55d2191ed7e63fe2bdc057dafe646) +- Screen interval functions did not unregister at destruction, causing weird stack corruption - [8f10cf0](../../../commit/8f10cf0ab6db9969fe2f953d7248695390e93817) +- SelectMusic hotkeys too integrated with the c++ logic - [721e773](../../../commit/721e7733822240a80312b8c53fb96aa8aa9e6220) +- Significant stuttering and sync issues when using certain combinations of vsync, framelimits, sound drivers, and sound hardware - [b07c8aa](../../../commit/b07c8aa3f2d66c6df8b24f709be90e672186605c) +- Songs did not correctly load when loaded from disk for the first time - [255389e](../../../commit/255389ea7941f14a9859e714abbc3062d87a6bd6) [3935df4](../../../commit/3935df43231cffb66b88d378da85951f6f25184d) +- Song search activating did not prevent switching tabs when typing the right parenthesis - [33c0331](../../../commit/33c0331d4a1ffb2dcabfe3e1d0991139cc5fa820) +- Song search did not support certain symbols - [cdfc12c](../../../commit/cdfc12cc27fdf3aadcce1182b3f179f2ea072854) [e713f0f](../../../commit/e713f0fd7ee6dc66654b85e93be583c731e31e71) +- SongFinishedMessage triggered too early to let any theme take advantage of it - [e9cd9d6](../../../commit/e9cd9d6f9bc4a652cdea9418ddbdfdac2d016f59) +- Sortmode menu button did not lead to the sortmode menu - [62c3fc9](../../../commit/62c3fc9b3d67084010905ba15aca0c627dcf10b3) +- SSC Scrolls broke Chart Preview - [1c01aa3](../../../commit/1c01aa3ce2c876ea28059de2cdfca51c33c3482b) +- SSC Scrolls broke when reloading in Practice mode - [6bf2c26](../../../commit/6bf2c26912cbed47d214abc703637f546bb98a17) +- SSC Timingdata never saved properly in cache - [00f66dd](../../../commit/00f66dd137d726d1e38e42f38510d8c61d6fe011) +- Sync changes using the F11/F12 keys did not update the actual timing data until a save, causing confusing situations - [28bf8b8](../../../commit/28bf8b86f85fd6efd8a68bf72c7bbaeb788b20b2) +- Syncing a chart with unknown Styles crashed - [4a51675](../../../commit/4a516753f9aba852f4d4629fda50173e17a84f98) +- Til Death evaluation scoring could be wildly wrong due to RadarValue sourcing - [750638d](../../../commit/750638d338c30d54cfca4a74d7abb7e7481b14f7) +- Til Death playlist detail screen did not use transliterated names - [4b15fdb](../../../commit/4b15fdb4543f06444c75011f843a8be969acda68) +- Til Death profile select would lock input for a really long time - [70c1267](../../../commit/70c1267d48eb4a7aa4d03b41c303b8182d77d247) +- Til Death select music lag induced by excessive command usage - [b9106bb](../../../commit/b9106bbbd69b0f2785400b33ccce8eaa6b2130de) +- Toasty triggered at note 251 instead of note 250 - [51f2241](../../../commit/51f22419eeebaea3b0da640148b45edf966e0d57) +- Top25 endpoint return function broke when 404ing - [eb058ff](../../../commit/eb058ff3d696da85ab9f75abe031a3b9b55b4564) +- Totaldancepoints did not update between stages, causing extremely old legacy code to break - [d360b0d](../../../commit/d360b0db60f38860c45981ac542bf64caf43f168) +- Transition into watching a Replay showed the Press Start for Options message - [021eae5](../../../commit/021eae5f9ffe1a0c9edf3003c129b48ac636cf6c) +- Warnings - [31117f8](../../../commit/31117f8229aa00587ff458360d69ca259e1dc9fc) [75ec3f8](../../../commit/75ec3f89e8050bedb4be2d60acba0301a37a792b) [50703b1](../../../commit/50703b1451611b87ba7f34f4f782555134fc1283) [3092318](../../../commit/30923189e9689e44ce4f35dd0241442bc5ce435c) \ No newline at end of file diff --git a/.ci/prepare_symbols.py b/.ci/prepare_symbols.py new file mode 100755 index 0000000000..bf05ba19cc --- /dev/null +++ b/.ci/prepare_symbols.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +""" +Symbol Prepare +Takes the Etterna.sym file, and creates a google breakpad directory structure. +Creates to simplify use when uploading to action artifacts. +""" +import os +import sys +import shutil +from pathlib import Path + + +def get_metadata(filename: str): + """Read first line to get relevant file data""" + with open(filename, 'r') as f: + first_line = f.readline().strip() # Remove newline + first_line = first_line.split(' ') # Split by spaces + return first_line[3], first_line[4] + + +def make_breakpad_directory(): + build_uuid, module_id = get_metadata('Etterna.sym') + path = Path("EtternaSymbolsUploadDir", "EtternaSymbols", module_id, build_uuid) + os.makedirs(path, exist_ok=True) + shutil.copyfile('Etterna.sym', path / 'Etterna.sym') + + +make_breakpad_directory() +sys.exit(0) diff --git a/.ci/upload_symbols.py b/.ci/upload_symbols.py new file mode 100755 index 0000000000..520a82be7f --- /dev/null +++ b/.ci/upload_symbols.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +""" +Etterna Symbol Uploader +Takes the Etterna.sym file, and uploads it to the S3 at the correct +google breakpad directory location. + +Google Breakpad Directory Structure is as follows: symbols///.sym +Directory Structure for Etterna: symbols/Platform.Arch/Etterna//Etterna.sym +"symbols/Platform.Arch" will be the directory when passing into minidump_stackwalk +""" +import os +import sys +import platform +import subprocess +from pathlib import PurePosixPath + +# Ensure AWS Environment Variables exist +if 'AWS_ACCESS_KEY_ID' not in os.environ or 'AWS_SECRET_ACCESS_KEY' not in os.environ: + print("Ensure 'AWS_ACCESS_KEY_ID' and 'AWS_SECRET_ACCESS_KEY' are properly defined environment variables") + sys.exit(1) + +# Program Variables +SYMBOL_FILE = "Etterna.sym" +AWS_BUCKET_NAME = "etterna" +AWS_ACCESS_KEY_ID = os.environ['AWS_ACCESS_KEY_ID'] +AWS_SECRET_ACCESS_KEY = os.environ['AWS_SECRET_ACCESS_KEY'] +ETTERNA_ARCH = os.environ['ETTERNA_ARCH'] + + +def get_s3_upload_directory(): + """ + Get the correct directory on AWS for symbol upload. + Current possible symbol directories include: + - symbols/Windows.i386/Etterna + - symbols/Windows.x64/Etterna + - symbols/Darwin.x64/Etterna + :return: The correct base directory + """ + base_dir = platform.system() + return "{}.{}/Etterna/".format(base_dir, ETTERNA_ARCH) + + +def get_metadata(filename: str): + """Read first line to get relevant file data""" + with open(filename, 'r') as f: + first_line = f.readline().strip() # Remove newline + first_line = first_line.split(' ') # Split by spaces + return first_line[3], first_line[4] + + +def upload_to_s3(filename: str): + # Collect Metadata + prefix_dir = get_s3_upload_directory() + build_uuid, module_id = get_metadata(filename) + upload_prefix = PurePosixPath('symbols', prefix_dir, build_uuid, filename) + print("The upload_prefix is {}".format(upload_prefix)) + + # Get git hash + git_command = ['git', 'rev-parse', 'HEAD'] + process = subprocess.run(git_command, capture_output=True) + full_hash = process.stdout.decode('utf-8').strip() + + # Upload to S3 + s3_command = [ + 'aws', 's3api', 'put-object', + '--bucket', AWS_BUCKET_NAME, + '--body', filename, + '--key', upload_prefix, + '--tagging', 'GitHash={}'.format(full_hash)] + subprocess.run(s3_command) + + +upload_to_s3(SYMBOL_FILE) +sys.exit(0) diff --git a/.editorconfig b/.editorconfig index 897438fe19..25e386e92b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,4 +17,8 @@ trim_trailing_whitespace = false [{CMakeLists.txt, *.cmake}] indent_style = tab -indent_size = 4 \ No newline at end of file +indent_size = 4 + +[*.lua] +indent_style = space +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/01-bug_report.yml b/.github/ISSUE_TEMPLATE/01-bug_report.yml new file mode 100644 index 0000000000..70e87b9678 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug_report.yml @@ -0,0 +1,79 @@ +name: 🐞 Bug Report +description: File a report to help us improve Etterna! +title: "[Bug]: " +labels: ["Type: Bug", "Needs triage"] + +body: + - type: markdown + attributes: + value: | + ## Thanks for taking the time to fill out this bug report! Please make sure to check if the bug has already been reported before submitting a new report. + + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + + - type: input + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: email@example.com, Discord#1234, Just reply to the issue... + validations: + required: false + + - type: dropdown + attributes: + label: Version Info + description: What version of Etterna are you running? + options: + - Latest available release + - Compiled from develop + validations: + required: true + + - type: dropdown + attributes: + label: What operating system are you seeing the problem on? + multiple: true + options: + - Windows + - macOS + - Linux (any distro) + + - type: textarea + attributes: + label: Bug Behavior + description: Describe the bug you're currently experiencing. + validations: + required: false + + - type: textarea + attributes: + label: Expected Behavior + description: What should have happened instead? + validations: + required: false + + - type: textarea + attributes: + label: Reproduction Steps + description: What steps should we follow to see the bug? + placeholder: | + 1. Run the game + 2. Select "Game Start" + 3. Open the debug menu + 4. '...' + + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/02-feature_report.yml b/.github/ISSUE_TEMPLATE/02-feature_report.yml new file mode 100644 index 0000000000..68ea9b9f22 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-feature_report.yml @@ -0,0 +1,39 @@ +name: 🛠️ Feature Request +description: Share your ideas on how to improve the game! +title: "[Feature Request]: " +labels: ["Type: Enhancement"] + +body: + - type: checkboxes + attributes: + label: Is there an existing issue for the feature? + description: Please search to see if an issue already suggested the feature you're thinking. + options: + - label: I have searched the existing feature requests + required: true + + - type: textarea + attributes: + label: Describe the Feature + description: What is the addition you are proposing? + validations: + required: true + + - type: textarea + attributes: + label: How Does The Feature Add To The Game? + description: (Explain the problem the feature aims to solve, or the new things it allows) + placeholder: | + In Etterna, it was previously not possible to ... + However, this feature lets players ... + validations: + required: true + + - type: textarea + attributes: + label: Additional Context + description: | + If you have any background information that would help implement the feature, put it here. + (e.g. knowing which section of the code would have to be changed, or describing how another game implemented a similar thing) + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03-build_issue.yml b/.github/ISSUE_TEMPLATE/03-build_issue.yml new file mode 100644 index 0000000000..29c7b1adf6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-build_issue.yml @@ -0,0 +1,47 @@ +name: ⚙️ Build Issue +description: Having a problem building Etterna? Let us know here! +labels: ["Type: Maintenance"] +title: "[Build Issue]: " +assignees: + - jameskr97 + +body: + - type: checkboxes + attributes: + label: Have you read through the build instructions before reading this? + description: All information related to building Etterna is written in that document + options: + - label: I have read the build instructions + required: true + + - type: dropdown + attributes: + label: What operating system are you seeing the problem on? + multiple: true + options: + - Windows + - macOS + - Linux (any distro) + + - type: textarea + attributes: + label: Describe the build issue + description: What problem do you have when building Etterna? + validations: + required: true + + - type: textarea + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index ec865ec5c9..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Version [e.g. 22] - - 64bit or 32bit - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..6096eaeb7c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 Contact us on the Etterna Dev Group Discord + url: https://discord.gg/ZqpUjsJ + about: Join the EDG Discord for support and discussion with the devs. \ No newline at end of file diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2e63b8d7b3..1fc4ddd2f7 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -1,5 +1,9 @@ name: Etterna CI -on: [push, pull_request] +on: + push: + pull_request: + release: + types: [published] jobs: linux-x64: @@ -9,60 +13,181 @@ jobs: fail-fast: false matrix: cfg: - - { os: ubuntu-18.04, cpp-version: g++-8 } - - { os: ubuntu-18.04, cpp-version: g++-9 } - - { os: ubuntu-18.04, cpp-version: clang++-7 } - - { os: ubuntu-18.04, cpp-version: clang++-8 } - - { os: ubuntu-18.04, cpp-version: clang++-9 } + - { os: ubuntu-18.04, cpp-version: g++-8, dist: false} + - { os: ubuntu-18.04, cpp-version: g++-9, dist: false} + - { os: ubuntu-18.04, cpp-version: clang++-7, dist: false} + - { os: ubuntu-18.04, cpp-version: clang++-8, dist: false} + - { os: ubuntu-18.04, cpp-version: clang++-9, dist: true} steps: - - name: Checkout project + - name: Checkout Etterna uses: actions/checkout@v2 + with: + path: main - - name: Install apt packages - run: sudo apt update && sudo apt install ${{ matrix.cfg.cpp-version }} nasm ninja-build libglew-dev libxrandr-dev libxtst-dev libpulse-dev libasound-dev libogg-dev libvorbis-dev + - name: Checkout CrashpadTools + uses: actions/checkout@v2 + with: + repository: etternagame/CrashpadTools + path: tools - - name: Print gcc + clang version - run: ${{matrix.cfg.cpp-version}} --version + - name: Install apt packages + run: sudo apt update && sudo apt install ${{ matrix.cfg.cpp-version }} nasm ninja-build libglew-dev libxrandr-dev libxtst-dev libpulse-dev libasound-dev libogg-dev libvorbis-dev xorg-dev libcurl4-openssl-dev - name: Generate CMake - run: mkdir build && cd build && cmake -G Ninja .. + run: mkdir main/build && cd main/build && cmake -G Ninja .. env: CXX: ${{matrix.cfg.cpp-version}} - name: Build Project - run: cd build && ninja + run: cd main/build && ninja + + - name: Generate binary + if: ${{matrix.cfg.dist}} + run: cd main/build && cpack + + - name: Upload Binary + if: ${{matrix.cfg.dist}} + uses: actions/upload-artifact@v2 + with: + name: "Etterna - Linux x64" + path: '${{github.workspace}}/main/build/*.tar.gz' + + - name: Update Environment Variables + if: ${{matrix.cfg.dist}} + run: echo "${{github.workspace}}/tools/linux" >> $GITHUB_PATH + + - name: Generate Symbols + if: ${{matrix.cfg.dist}} + run: | + echo "Running objcopy..." + mkdir ${{github.workspace}}/sym + objcopy --only-keep-debug ${{github.workspace}}/main/Etterna ${{github.workspace}}/sym/Etterna + echo "Dumping Symbols..." + dump_syms ${{github.workspace}}/sym/Etterna ${{github.workspace}}/main/Etterna > ${{github.workspace}}/sym/Etterna.sym + echo "Stripping debug symbols from binary..." + strip ${{github.workspace}}/main/Etterna + + - name: Prepare symbols for upload artifacts + if: ${{matrix.cfg.dist}} + run: cd ${{github.workspace}}/sym && ${{github.workspace}}/main/.ci/prepare_symbols.py + + - name: Upload Symbols to action artifacts + if: ${{matrix.cfg.dist}} + uses: actions/upload-artifact@v2 + with: + name: Etterna Symbols - ${{github.sha}} + path: '${{github.workspace}}/sym/EtternaSymbolsUploadDir' + + - name: Get version for CrashServer Upload + id: get_version + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + run: | + echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + + - name: Upload Symbols to Crash Server + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + run: symupload ${{github.workspace}}/main/Etterna.sym "https://crash.etterna.dev/api/symbol/upload?api_key=${{secrets.CRASHSERVER_API_KEY}}&version=${{steps.get_version.outputs.VERSION}}" + + - name: Upload files to Release + uses: softprops/action-gh-release@v1 + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + with: + files: ${{github.workspace}}/main/build/*.tar.gz macos: - name: macOS x64 - runs-on: macos-latest + name: macOS x64 ${{matrix.cfg.name}} + runs-on: ${{matrix.cfg.os}} + strategy: + fail-fast: false + matrix: + cfg: + - { os: macos-10.15, name: "Catalina", dist: true} + - { os: macos-11, name: "Big Sur", dist: false} + steps: - - uses: actions/checkout@v2 + + - name: Checkout Etterna + uses: actions/checkout@v2 + with: + path: main + + - name: Checkout CrashpadTools + uses: actions/checkout@v2 + with: + repository: etternagame/CrashpadTools + path: tools + - name: Install homebrew packages - run: brew install cmake nasm ninja + run: brew install cmake nasm ninja openssl - name: Generate CMake - run: mkdir build && cd build && cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -G Ninja .. + run: mkdir main/build && cd main/build && cmake -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja .. - name: Build Project - run: cd build && ninja + run: cd main/build && ninja - name: Generate binary - run: cd build && cpack + if: ${{matrix.cfg.dist}} + run: cd main/build && cpack - name: Upload Binary + if: ${{matrix.cfg.dist}} + uses: actions/upload-artifact@v2 + with: + name: "Etterna - macOS x64" + path: '${{github.workspace}}/main/build/*.dmg' + + - name: Update Environment Variables + if: ${{matrix.cfg.dist}} + run: | + echo "${{github.workspace}}/tools/mac" >> $GITHUB_PATH + echo "ETTERNA_ARCH=x64" >> $GITHUB_ENV + + - name: Generate Symbols + if: ${{matrix.cfg.dist}} + run: | + echo "Running dsymutil..." + dsymutil -o ${{github.workspace}}/main/Etterna.dsym main/Etterna.app/Contents/MacOS/Etterna > /dev/null + echo "Dumping Symbols..." + dump_syms -g ${{github.workspace}}/main/Etterna.dsym main/Etterna.app/Contents/MacOS/Etterna > ${{github.workspace}}/main/Etterna.sym + echo "Stripping debug symbols from binary..." + strip main/Etterna.app/Contents/MacOS/Etterna + + - name: Prepare symbols for upload artifacts + if: ${{matrix.cfg.dist}} + run: cd main && ${{github.workspace}}/main/.ci/prepare_symbols.py + + - name: Upload Symbols to action artifacts + if: ${{matrix.cfg.dist}} uses: actions/upload-artifact@v2 with: - name: "Etterna - macOS x64.dmg" - path: '${{github.workspace}}/build/*.dmg' + name: Etterna Symbols - ${{github.sha}} + path: '${{github.workspace}}/main/EtternaSymbolsUploadDir' + + - name: Get version for CrashServer Upload + id: get_version + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + run: | + echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + + - name: Upload Symbols to Crash Server + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + run: symupload ${{github.workspace}}/main/Etterna.sym "https://crash.etterna.dev/api/symbol/upload?api_key=${{secrets.CRASHSERVER_API_KEY}}&version=${{ steps.get_version.outputs.VERSION }}" + + - name: Upload files to Release + uses: softprops/action-gh-release@v1 + if: ${{ matrix.cfg.dist && github.event_name == 'release' && github.event.action == 'published' }} + with: + files: ${{github.workspace}}/main/build/*.dmg windows: # Windows x64 and x86 build matrix strategy: fail-fast: false # Don't cancel other matrix jobs if one fails matrix: cfg: - - { name: i386, arch: Win32, ssl-dir: 'C:\Program Files (x86)\OpenSSL-Win32' } - - { name: x64, arch: x64, ssl-dir: 'C:\Program Files\OpenSSL-Win64'} + - { name: i386, arch: x86, ssl-dir: 'C:\Program Files (x86)\OpenSSL-Win32' } + - { name: x64, arch: x64, ssl-dir: 'C:\Program Files\OpenSSL-Win64'} name: "Windows ${{matrix.cfg.name}}" runs-on: windows-2019 @@ -72,30 +197,48 @@ jobs: with: path: main + - name: Checkout CrashpadTools + uses: actions/checkout@v2 + with: + repository: etternagame/CrashpadTools + path: tools + - name: Checkout DirectX SDK uses: actions/checkout@v2 with: repository: nico-abram/dxsdk path: dxsdk - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v1.0.0 + - name: Update Environment Variables + run: | + echo "${{github.workspace}}/tools/win/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + echo "ETTERNA_ARCH=${{matrix.cfg.name}}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Setup Python 2.7 + uses: actions/setup-python@v2 + with: + python-version: '2.7' - name: Install chocolatey packages (i386) - if: ${{ matrix.cfg.arch == 'Win32' }} - run: choco install ninja nsis openssl -y --x86 + if: ${{ matrix.cfg.arch == 'x86' }} + run: choco install ninja nsis openssl curl -y --x86 - name: Install chocolatey packages (x64) if: ${{ matrix.cfg.arch == 'x64' }} - run: choco install ninja nsis openssl -y + run: choco install ninja nsis openssl curl -y + + - name: Configure MSBuild + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.cfg.arch }} - name: Generate CMake - run: mkdir main/build && cd main/build && cmake -G "Visual Studio 16 2019" -A "${{matrix.cfg.arch}}" -DOPENSSL_ROOT_DIR="${{matrix.cfg.ssl-dir}}" .. + run: mkdir main/build && cd main/build && cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo .. env: DXSDK_DIR: "${{github.workspace}}/dxsdk/" - name: Build Project - run: cd main/build && MSBuild Etterna.sln /p:Configuration=Release /p:Platform=${{matrix.cfg.arch}} + run: cd main/build && ninja - name: Generate binary run: cd main/build && cpack @@ -105,3 +248,38 @@ jobs: with: name: "Etterna - Windows ${{matrix.cfg.name}}" path: '${{github.workspace}}/main/build/*.exe' + + - name: Setup Python 3 + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Generate Symbols + run: | + echo "Dumping Symbols..." + dump_syms.exe main/Program/Etterna.pdb > ${{github.workspace}}/main/Etterna.sym + + - name: Prepare symbols for action artifacts + run: cd main && python ${{github.workspace}}/main/.ci/prepare_symbols.py + + - name: Upload Symbols to action artifacts + uses: actions/upload-artifact@v2 + with: + name: Etterna Symbols - ${{github.sha}} + path: '${{github.workspace}}/main/EtternaSymbolsUploadDir' + + - name: Get version for CrashServer Upload + id: get_version + if: ${{ github.event_name == 'release' && github.event.action == 'published' }} + run: $tag="${{ github.ref }}".Split('/')[-1]; echo "::set-output name=VERSION::$tag" + + - name: Upload Symbols to Crash Server + if: ${{ github.event_name == 'release' && github.event.action == 'published' }} + run: curl --request POST -F symbol_file=@${{github.workspace}}/main/Etterna.sym "https://crash.etterna.dev/api/symbol/upload?api_key=${{ secrets.CRASHSERVER_API_KEY }}&version=${{ steps.get_version.outputs.VERSION }}" + + - name: Upload files to Release + uses: softprops/action-gh-release@v1 + if: ${{ github.event_name == 'release' && github.event.action == 'published' }} + with: + files: | + ${{github.workspace}}/main/build/*.exe diff --git a/.gitignore b/.gitignore index 7d3d4adf27..ba9f2b275d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,78 +7,84 @@ # Ignore executables (Linux, macOS, Windows) /Etterna -*.app -*.exe +/Etterna-debug +/Etterna-MinSizeRelease +/Etterna-RelWithDebInfo +/*.app +/*.exe # Etterna Related Ignore -Cache/ -Logs/ -Program/ -Save/ -Screenshots/ -Songs/ +/Cache/ +/Logs/ +/Program/ +/Save/ +/Screenshots/ +/Songs/ # Misc Ignores -crashinfo.txt -nowplaying.txt +/CrashData +/Program/CrashData +/crashinfo.txt +/nowplaying.txt ## Specific Ignores (To allow playing on development builds) # Some not included here because pending removal or not frequently changing (user assets) -Announcers/* -!Announcers/instructions.txt +/Announcers/* +!/Announcers/instructions.txt -Assets/Avatars/* -!Assets/Avatars/_fallback.png -!Assets/Avatars/Divide By Zero.png -!Assets/Avatars/Take Control.png -!Assets/Avatars/takemetothepromisedland.png +/Assets/Avatars/* +!/Assets/Avatars/_fallback.png +!/Assets/Avatars/Divide By Zero.png +!/Assets/Avatars/Take Control.png +!/Assets/Avatars/takemetothepromisedland.png -Assets/Judgments/* -!Assets/Judgments/default 1x6 (Doubleres).png -!Assets/Judgments/Judgment Normal 2x6 (Doubleres).png +/Assets/Judgments/* +!/Assets/Judgments/default 1x6 (Doubleres).png +!/Assets/Judgments/Judgment Normal 2x6 (Doubleres).png -Assets/Toasties/* -!Assets/Toasties/default/ -!Assets/Toasties/legacy/ +/Assets/Toasties/* +!/Assets/Toasties/default/ +!/Assets/Toasties/legacy/ -NoteSkins/* -!NoteSkins/instructions.txt -!NoteSkins/beat/default/ -!NoteSkins/common/ +/NoteSkins/* +!/NoteSkins/instructions.txt +!/NoteSkins/beat/default/ +!/NoteSkins/common/ -!NoteSkins/dance/default/ -!NoteSkins/dance/DivideByInf/ -!NoteSkins/dance/DivideByZero_halved/ -!NoteSkins/dance/DivideByZero_semihalved/ -!NoteSkins/dance/DivideByZeroHollow/ -!NoteSkins/dance/MultiplyByZero/ -!NoteSkins/dance/MultiplyByZeroDoubleRes/ -!NoteSkins/dance/SubtractByZero/ +!/NoteSkins/dance/default/ +!/NoteSkins/dance/DivideByInf/ +!/NoteSkins/dance/DivideByZero_halved/ +!/NoteSkins/dance/DivideByZero_semihalved/ +!/NoteSkins/dance/DivideByZeroHollow/ +!/NoteSkins/dance/MultiplyByZero/ +!/NoteSkins/dance/MultiplyByZeroDoubleRes/ +!/NoteSkins/dance/SubtractByZero/ -!NoteSkins/kb7/default/ -!NoteSkins/kb7/orbital/ -!NoteSkins/kb7/retrobar/ -!NoteSkins/kb7/retrobar-iidx/ -!NoteSkins/kb7/retrobar-o2jam/ -!NoteSkins/kb7/retrobar-razor/ -!NoteSkins/kb7/retrobar-razor_o2/ +!/NoteSkins/kb7/default/ +!/NoteSkins/kb7/orbital/ +!/NoteSkins/kb7/retrobar/ +!/NoteSkins/kb7/retrobar-iidx/ +!/NoteSkins/kb7/retrobar-o2jam/ +!/NoteSkins/kb7/retrobar-razor/ +!/NoteSkins/kb7/retrobar-razor_o2/ -!NoteSkins/pump/cmd/ -!NoteSkins/pump/cmd-routine-p1/ -!NoteSkins/pump/cmd-routine-p2/ -!NoteSkins/pump/complex/ -!NoteSkins/pump/default/ -!NoteSkins/pump/delta/ -!NoteSkins/pump/delta-note/ -!NoteSkins/pump/delta-routine-p1/ -!NoteSkins/pump/delta-routine-p2/ -!NoteSkins/pump/frame5p/ -!NoteSkins/pump/newextra/ -!NoteSkins/pump/pad/ -!NoteSkins/pump/rhythm/ -!NoteSkins/pump/simple/ +!/NoteSkins/pump/cmd/ +!/NoteSkins/pump/cmd-routine-p1/ +!/NoteSkins/pump/cmd-routine-p2/ +!/NoteSkins/pump/complex/ +!/NoteSkins/pump/default/ +!/NoteSkins/pump/delta/ +!/NoteSkins/pump/delta-note/ +!/NoteSkins/pump/delta-routine-p1/ +!/NoteSkins/pump/delta-routine-p2/ +!/NoteSkins/pump/frame5p/ +!/NoteSkins/pump/newextra/ +!/NoteSkins/pump/pad/ +!/NoteSkins/pump/rhythm/ +!/NoteSkins/pump/simple/ -Themes/* -!Themes/_fallback/ -!Themes/Til Death -!Themes/bare-frames +/Themes/* +!/Themes/_fallback/ +!/Themes/Til Death +!/Themes/bare-frames +!/Themes/Rebirth diff --git a/Assets/Avatars/Divide By Zero.png b/Assets/Avatars/Divide By Zero.png index 0e93b062c2..f90c18a73f 100644 Binary files a/Assets/Avatars/Divide By Zero.png and b/Assets/Avatars/Divide By Zero.png differ diff --git a/Assets/Avatars/Take Control.png b/Assets/Avatars/Take Control.png index 0b8eba2902..ce16f98de1 100644 Binary files a/Assets/Avatars/Take Control.png and b/Assets/Avatars/Take Control.png differ diff --git a/Assets/Avatars/_fallback.png b/Assets/Avatars/_fallback.png index 8e5a265e14..6d86f844aa 100644 Binary files a/Assets/Avatars/_fallback.png and b/Assets/Avatars/_fallback.png differ diff --git a/Assets/Judgments/Judgment Normal 2x6 (Doubleres).png b/Assets/Judgments/Judgment Normal 2x6 (Doubleres).png index 6c8646020b..de402fe0ad 100644 Binary files a/Assets/Judgments/Judgment Normal 2x6 (Doubleres).png and b/Assets/Judgments/Judgment Normal 2x6 (Doubleres).png differ diff --git a/Assets/Judgments/default 1x6 (Doubleres).png b/Assets/Judgments/default 1x6 (Doubleres).png index 766b5c8f8a..14ab3ccf42 100644 Binary files a/Assets/Judgments/default 1x6 (Doubleres).png and b/Assets/Judgments/default 1x6 (Doubleres).png differ diff --git a/Assets/Toasties/default/default.png b/Assets/Toasties/default/default.png index 5cc4340746..91fe2ef58d 100644 Binary files a/Assets/Toasties/default/default.png and b/Assets/Toasties/default/default.png differ diff --git a/Assets/Toasties/legacy/default.lua b/Assets/Toasties/legacy/default.lua index 814613fc6a..e1381def7e 100644 --- a/Assets/Toasties/legacy/default.lua +++ b/Assets/Toasties/legacy/default.lua @@ -1,23 +1,23 @@ -- this is an example local t = - Def.ActorFrame { - Def.Sprite { - InitCommand = function(self) - self:xy(SCREEN_WIDTH + 100, SCREEN_CENTER_Y) - self:Load(getToastyAssetPath("image")) - self:zoom(0.999999999999999999999999999999999) - end, - StartTransitioningCommand = function(self) - self:diffusealpha(1):decelerate(0.25):x(SCREEN_WIDTH - 100):sleep(1.75):accelerate(0.5):x(SCREEN_WIDTH + 100):linear(0):diffusealpha(0) - end - }, - Def.Sound { - InitCommand = function(self) - self:load(getToastyAssetPath("sound")) - end, - StartTransitioningCommand = function(self) - self:play() - end - } + Def.ActorFrame { + Def.Sprite { + InitCommand = function(self) + self:xy(SCREEN_WIDTH + 100, SCREEN_CENTER_Y) + self:Load(getToastyAssetPath("image")) + self:zoom(0.999999999999999999999999999999999) + end, + StartTransitioningCommand = function(self) + self:diffusealpha(1):decelerate(0.25):x(SCREEN_WIDTH - 100):sleep(1.75):accelerate(0.5):x(SCREEN_WIDTH + 100):linear(0):diffusealpha(0) + end + }, + Def.Sound { + InitCommand = function(self) + self:load(getToastyAssetPath("sound")) + end, + StartTransitioningCommand = function(self) + self:play() + end + } } return t diff --git a/BGAnimations/flash/default.lua b/BGAnimations/flash/default.lua index 3ecd9ae5c5..dc8df0fccc 100644 --- a/BGAnimations/flash/default.lua +++ b/BGAnimations/flash/default.lua @@ -1,9 +1,9 @@ local flashColor = color(Var "Color1") return Def.Quad { - InitCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2):diffuse(flashColor) - end, - GainFocusCommand = function(self) - self:finishtweening():diffusealpha(1):accelerate(0.6):diffusealpha(0) - end + InitCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2):diffuse(flashColor) + end, + GainFocusCommand = function(self) + self:finishtweening():diffusealpha(1):accelerate(0.6):diffusealpha(0) + end } diff --git a/BGAnimations/white flash/default.lua b/BGAnimations/white flash/default.lua index e26d01609f..f7121eec60 100644 --- a/BGAnimations/white flash/default.lua +++ b/BGAnimations/white flash/default.lua @@ -1,8 +1,8 @@ return Def.Quad { - InitCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) - end, - GainFocusCommand = function(self) - self:finishtweening():diffusealpha(1):accelerate(0.6):diffusealpha(0) - end + InitCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) + end, + GainFocusCommand = function(self) + self:finishtweening():diffusealpha(1):accelerate(0.6):diffusealpha(0) + end } diff --git a/BGAnimations/white reverse flash/default.lua b/BGAnimations/white reverse flash/default.lua index 320232b4ab..2284ac3b54 100644 --- a/BGAnimations/white reverse flash/default.lua +++ b/BGAnimations/white reverse flash/default.lua @@ -1,8 +1,8 @@ return Def.Quad { - InitCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) - end, - GainFocusCommand = function(self) - self:finishtweening():diffusealpha(0):accelerate(0.6):diffusealpha(1) - end + InitCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) + end, + GainFocusCommand = function(self) + self:finishtweening():diffusealpha(0):accelerate(0.6):diffusealpha(1) + end } diff --git a/BGAnimations/yellow flash/default.lua b/BGAnimations/yellow flash/default.lua index dd9bd835f4..52263ab4c4 100644 --- a/BGAnimations/yellow flash/default.lua +++ b/BGAnimations/yellow flash/default.lua @@ -1,8 +1,8 @@ return Def.Quad { - InitCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) - end, - GainFocusCommand = function(self) - self:finishtweening():diffuse(color("#FFFFA0")):accelerate(0.6):diffusealpha(0) - end + InitCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH * 2, SCREEN_HEIGHT * 2) + end, + GainFocusCommand = function(self) + self:finishtweening():diffuse(color("#FFFFA0")):accelerate(0.6):diffusealpha(0) + end } diff --git a/BackgroundEffects/Centered.lua b/BackgroundEffects/Centered.lua index 1031770a8e..ed5d870d3c 100644 --- a/BackgroundEffects/Centered.lua +++ b/BackgroundEffects/Centered.lua @@ -1,23 +1,23 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:xy(_screen.cx, _screen.cy):diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:xy(_screen.cx, _screen.cy):diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/Checkerboard1File2x2.lua b/BackgroundEffects/Checkerboard1File2x2.lua index 402f372f20..fbda463cf3 100644 --- a/BackgroundEffects/Checkerboard1File2x2.lua +++ b/BackgroundEffects/Checkerboard1File2x2.lua @@ -1,58 +1,58 @@ local Color1 = color(Var "Color1") local a = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:cropto(_screen.w / 2, _screen.h / 2):diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:cropto(_screen.w / 2, _screen.h / 2):diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } local t = - Def.ActorFrame { - a .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - } + Def.ActorFrame { + a .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + } } return t diff --git a/BackgroundEffects/Checkerboard2File2x2.lua b/BackgroundEffects/Checkerboard2File2x2.lua index 058a64eaad..85c69563f0 100644 --- a/BackgroundEffects/Checkerboard2File2x2.lua +++ b/BackgroundEffects/Checkerboard2File2x2.lua @@ -2,72 +2,72 @@ local Color1 = color(Var "Color1") local Color2 = color(Var "Color2") local a1 = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } local a2 = - LoadActor(Var "File2") .. - { - OnCommand = function(self) - self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color2):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + LoadActor(Var "File2") .. + { + OnCommand = function(self) + self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color2):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } local t = - Def.ActorFrame { - a1 .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - end - }, - a2 .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - end - }, - a2 .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - a1 .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - } + Def.ActorFrame { + a1 .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + end + }, + a2 .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + end + }, + a2 .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + a1 .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + } } return t diff --git a/BackgroundEffects/Checkerboard2x2.lua b/BackgroundEffects/Checkerboard2x2.lua index a674d6b005..aff387e42a 100644 --- a/BackgroundEffects/Checkerboard2x2.lua +++ b/BackgroundEffects/Checkerboard2x2.lua @@ -3,49 +3,49 @@ local Color2 = color(Var "Color2") -- Alternating files being played back at once local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( - SCREEN_WIDTH / 2, - SCREEN_HEIGHT / 2 - ):diffuse(Color1) - end - }, - LoadActor(Var "File2") .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( - SCREEN_WIDTH / 2, - SCREEN_HEIGHT / 2 - ):diffuse(Color2) - end - }, - LoadActor(Var "File2") .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( - SCREEN_WIDTH / 2, - SCREEN_HEIGHT / 2 - ):diffuse(Color1) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( - SCREEN_WIDTH / 2, - SCREEN_HEIGHT / 2 - ):diffuse(Color2) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 + ):diffuse(Color1) + end + }, + LoadActor(Var "File2") .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 + ):diffuse(Color2) + end + }, + LoadActor(Var "File2") .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 + ):diffuse(Color1) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)):cropto( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 + ):diffuse(Color2) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + } } return t diff --git a/BackgroundEffects/File1FlashOverlay.lua b/BackgroundEffects/File1FlashOverlay.lua index 9d30450134..1d6d58fe7f 100644 --- a/BackgroundEffects/File1FlashOverlay.lua +++ b/BackgroundEffects/File1FlashOverlay.lua @@ -1,22 +1,22 @@ -local Color1 = color(Var "Color1") -local Color2 = color(Var "Color2") - -local t = Def.ActorFrame {} - -t[#t + 1] = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:diffuse(Color1):blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():linear( - 1 - ):diffusealpha(0) - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } - -return t +local Color1 = color(Var "Color1") +local Color2 = color(Var "Color2") + +local t = Def.ActorFrame {} + +t[#t + 1] = + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:diffuse(Color1):blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():linear( + 1 + ):diffusealpha(0) + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } + +return t diff --git a/BackgroundEffects/File2Flash.lua b/BackgroundEffects/File2Flash.lua index 44a9281aed..76cbce97c9 100644 --- a/BackgroundEffects/File2Flash.lua +++ b/BackgroundEffects/File2Flash.lua @@ -1,46 +1,46 @@ -local Color1 = color(Var "Color1") -local Color2 = color(Var "Color2") - -local t = Def.ActorFrame {} - -t[#t + 1] = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } - -if Var("File2") ~= nil then - t[#t + 1] = - LoadActor(Var("File2")) .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock("music"):linear(1):diffusealpha( - 0 - ) - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } -end - -return t +local Color1 = color(Var "Color1") +local Color2 = color(Var "Color2") + +local t = Def.ActorFrame {} + +t[#t + 1] = + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } + +if Var("File2") ~= nil then + t[#t + 1] = + LoadActor(Var("File2")) .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock("music"):linear(1):diffusealpha( + 0 + ) + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } +end + +return t diff --git a/BackgroundEffects/File2Normal.lua b/BackgroundEffects/File2Normal.lua index b77b4b6a1f..3f61cf3df2 100644 --- a/BackgroundEffects/File2Normal.lua +++ b/BackgroundEffects/File2Normal.lua @@ -1,44 +1,44 @@ -local Color1 = color(Var "Color1") -local Color2 = color(Var "Color2") - -local t = Def.ActorFrame {} - -t[#t + 1] = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } - -if Var("File2") ~= nil then - t[#t + 1] = - LoadActor(Var("File2")) .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } -end - -return t +local Color1 = color(Var "Color1") +local Color2 = color(Var "Color2") + +local t = Def.ActorFrame {} + +t[#t + 1] = + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } + +if Var("File2") ~= nil then + t[#t + 1] = + LoadActor(Var("File2")) .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } +end + +return t diff --git a/BackgroundEffects/Kaleidoscope2x2.lua b/BackgroundEffects/Kaleidoscope2x2.lua index 11ce1eaf0d..fded2200e2 100644 --- a/BackgroundEffects/Kaleidoscope2x2.lua +++ b/BackgroundEffects/Kaleidoscope2x2.lua @@ -1,60 +1,60 @@ local Color1 = color(Var "Color1") local a = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color1):zoomx(self:GetZoomX() * -1):zoomy( - self:GetZoomY() * -1 - ):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:cropto(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2):diffuse(Color1):zoomx(self:GetZoomX() * -1):zoomy( + self:GetZoomY() * -1 + ):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } local t = - Def.ActorFrame { - a .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - }, - a .. - { - OnCommand = function(self) - self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) - if self.SetDecodeMovie then - self:SetDecodeMovie(false) - end - end - } + Def.ActorFrame { + a .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(1, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(1, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + }, + a .. + { + OnCommand = function(self) + self:x(scale(3, 0, 4, SCREEN_LEFT, SCREEN_RIGHT)):y(scale(3, 0, 4, SCREEN_TOP, SCREEN_BOTTOM)) + if self.SetDecodeMovie then + self:SetDecodeMovie(false) + end + end + } } return t diff --git a/BackgroundEffects/OverlayAdd.lua b/BackgroundEffects/OverlayAdd.lua index b4cdb0dce9..21a781575c 100644 --- a/BackgroundEffects/OverlayAdd.lua +++ b/BackgroundEffects/OverlayAdd.lua @@ -1,19 +1,19 @@ -local Color1 = color(Var "Color1") - -local t = Def.ActorFrame {} - -t[#t + 1] = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:diffuse(Color1):blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background() - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } - -return t +local Color1 = color(Var "Color1") + +local t = Def.ActorFrame {} + +t[#t + 1] = + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:diffuse(Color1):blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background() + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } + +return t diff --git a/BackgroundEffects/SongBgWithMovieViz.lua b/BackgroundEffects/SongBgWithMovieViz.lua index 7e97cb6662..a83744a048 100644 --- a/BackgroundEffects/SongBgWithMovieViz.lua +++ b/BackgroundEffects/SongBgWithMovieViz.lua @@ -2,38 +2,38 @@ local Color1 = color(Var "Color1") local Color2 = color(Var "Color2") local t = - Def.ActorFrame { - Def.Sprite { - OnCommand = function(self) - self:LoadFromCurrentSongBackground() - self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) - self:scale_or_crop_background() - self:diffuse(Color1) - self:effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end - }, - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock( - "music" - ) - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + Def.Sprite { + OnCommand = function(self) + self:LoadFromCurrentSongBackground() + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + self:scale_or_crop_background() + self:diffuse(Color1) + self:effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end + }, + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock( + "music" + ) + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNoLoop.lua b/BackgroundEffects/StretchNoLoop.lua index b1a7fd753a..e587ad0442 100644 --- a/BackgroundEffects/StretchNoLoop.lua +++ b/BackgroundEffects/StretchNoLoop.lua @@ -1,34 +1,34 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) - self:scale_or_crop_background() - self:diffuse(Color1) - -- The playback rate in the simfile is used to set the update rate - -- on the ActorFrame, but effectclock("music") causes the sprite to - -- ignore the passed in delta time and use the music time instead. - -- So this passes the update rate in to the texture. -Kyz - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - if self.loop then - self:loop(false) - -- make videos start at beginning to prevent sticking on last frame - self:position(0) - end - self:effectclock("music") - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + self:scale_or_crop_background() + self:diffuse(Color1) + -- The playback rate in the simfile is used to set the update rate + -- on the ActorFrame, but effectclock("music") causes the sprite to + -- ignore the passed in delta time and use the music time instead. + -- So this passes the update rate in to the texture. -Kyz + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + if self.loop then + self:loop(false) + -- make videos start at beginning to prevent sticking on last frame + self:position(0) + end + self:effectclock("music") + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNormal.lua b/BackgroundEffects/StretchNormal.lua index ed368f9738..8ca7a25dbe 100644 --- a/BackgroundEffects/StretchNormal.lua +++ b/BackgroundEffects/StretchNormal.lua @@ -1,26 +1,26 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) - self:scale_or_crop_background() - self:diffuse(Color1) - self:effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + self:scale_or_crop_background() + self:diffuse(Color1) + self:effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNormalAlignLeft.lua b/BackgroundEffects/StretchNormalAlignLeft.lua index 3183f1cdef..2be6691408 100644 --- a/BackgroundEffects/StretchNormalAlignLeft.lua +++ b/BackgroundEffects/StretchNormalAlignLeft.lua @@ -2,23 +2,23 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNormalBlue.lua b/BackgroundEffects/StretchNormalBlue.lua index fe43c8d4e5..047b10462b 100644 --- a/BackgroundEffects/StretchNormalBlue.lua +++ b/BackgroundEffects/StretchNormalBlue.lua @@ -2,23 +2,23 @@ local Color1 = color("0,0,1,1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNormalGreen.lua b/BackgroundEffects/StretchNormalGreen.lua index 9cf827957d..c7a921a538 100644 --- a/BackgroundEffects/StretchNormalGreen.lua +++ b/BackgroundEffects/StretchNormalGreen.lua @@ -2,23 +2,23 @@ local Color1 = color("0,1,0,1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchNormalRed.lua b/BackgroundEffects/StretchNormalRed.lua index b4f4553f79..a1f6ac9921 100644 --- a/BackgroundEffects/StretchNormalRed.lua +++ b/BackgroundEffects/StretchNormalRed.lua @@ -2,23 +2,23 @@ local Color1 = color("1,0,0,1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchPaused.lua b/BackgroundEffects/StretchPaused.lua index faab8b33fc..07db43ebc5 100644 --- a/BackgroundEffects/StretchPaused.lua +++ b/BackgroundEffects/StretchPaused.lua @@ -1,23 +1,23 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):pause():effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):pause():effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/StretchRewind.lua b/BackgroundEffects/StretchRewind.lua index 019efb4241..1e6c64373f 100644 --- a/BackgroundEffects/StretchRewind.lua +++ b/BackgroundEffects/StretchRewind.lua @@ -1,29 +1,29 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) - self:scale_or_crop_background() - self:diffuse(Color1) - if self.position ~= nil then - self:position(0) - end - self:effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + self:scale_or_crop_background() + self:diffuse(Color1) + if self.position ~= nil then + self:position(0) + end + self:effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/UpperLeft.lua b/BackgroundEffects/UpperLeft.lua index ce1f1c6e4c..7acd52e40d 100644 --- a/BackgroundEffects/UpperLeft.lua +++ b/BackgroundEffects/UpperLeft.lua @@ -2,23 +2,23 @@ local Color1 = color(Var "Color1") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/Visualization2File.lua b/BackgroundEffects/Visualization2File.lua index c05b291100..3522150b24 100644 --- a/BackgroundEffects/Visualization2File.lua +++ b/BackgroundEffects/Visualization2File.lua @@ -2,41 +2,41 @@ local Color1 = color(Var "Color1") local Color2 = color(Var "Color2") local t = - Def.ActorFrame { - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - }, - LoadActor(Var "File2") .. - { - OnCommand = function(self) - self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH, SCREEN_HEIGHT):diffuse( - Color2 - ):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } + Def.ActorFrame { + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + }, + LoadActor(Var "File2") .. + { + OnCommand = function(self) + self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scaletoclipped(SCREEN_WIDTH, SCREEN_HEIGHT):diffuse( + Color2 + ):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } } return t diff --git a/BackgroundEffects/Visualization2FileFlash.lua b/BackgroundEffects/Visualization2FileFlash.lua index 0a48c5c776..3f57dff5fc 100644 --- a/BackgroundEffects/Visualization2FileFlash.lua +++ b/BackgroundEffects/Visualization2FileFlash.lua @@ -1,46 +1,46 @@ -local Color1 = color(Var "Color1") -local Color2 = color(Var "Color2") - -local t = Def.ActorFrame {} - -t[#t + 1] = - LoadActor(Var "File1") .. - { - OnCommand = function(self) - self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } - -if Var("File2") ~= nil then - t[#t + 1] = - LoadActor(Var("File2")) .. - { - OnCommand = function(self) - self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock( - "music" - ):linear(1):diffusealpha(0) - -- Explanation in StretchNoLoop.lua. - if self.GetTexture then - self:GetTexture():rate(self:GetParent():GetUpdateRate()) - end - end, - GainFocusCommand = function(self) - self:play() - end, - LoseFocusCommand = function(self) - self:pause() - end - } -end - -return t +local Color1 = color(Var "Color1") +local Color2 = color(Var "Color2") + +local t = Def.ActorFrame {} + +t[#t + 1] = + LoadActor(Var "File1") .. + { + OnCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color1):effectclock("music") + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } + +if Var("File2") ~= nil then + t[#t + 1] = + LoadActor(Var("File2")) .. + { + OnCommand = function(self) + self:blend("BlendMode_Add"):x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y):scale_or_crop_background():diffuse(Color2):effectclock( + "music" + ):linear(1):diffusealpha(0) + -- Explanation in StretchNoLoop.lua. + if self.GetTexture then + self:GetTexture():rate(self:GetParent():GetUpdateRate()) + end + end, + GainFocusCommand = function(self) + self:play() + end, + LoseFocusCommand = function(self) + self:pause() + end + } +end + +return t diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe063080b..3c53be22ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,36 @@ # Changelog All releases of Etterna are listed in this file as well as links to files detailing all of the changes for each. All changes for each version apply in supplement to the ones below it. Changes are not in chronological order, only versions are. +## [0.71.0] - 2021-12-10 - Rebirth + +Windows x64, Windows i386, and Mac installer release. Linux binary. +- New theme. Til Death refresher. A lot. +- [Notes](.changelog/Release_0-71.md) + +## [0.70.3] - 2020-07-17 - MSd Update + +Windows x64, Windows i386, and Mac installer release. +- Calc tuning and bare-frames release. +- [Notes](.changelog/Release_0-70-3.md) + +## [0.70.2] - 2020-07-13 - Hotfix 2 & MSD Update + +Windows x64, Windows i386, and Mac installer release. +- Calc tuning and bugfixes. +- [Notes](.changelog/Release_0-70-2.md) + +## [0.70.1] - 2020-07-09 - Hotfix + +Windows x64, Windows i386, and Mac installer release. +- Calc tuning and tiny feature additions. +- [Notes](.changelog/Release_0-70-1.md) + +## [0.70.0] - 2020-07-05 - Public Calc Test + +Windows x64, Windows i386, and Mac installer release. +- The MSD Calculator has been rewritten. +- [Notes](.changelog/Release_0-70-0.md) + ## [0.69.1] - 2020-05-22 - Hotfix Windows x64, Windows i386, and Mac installer release. diff --git a/CMake/CPack/Windows/ProductInfo.inc b/CMake/CPack/Windows/ProductInfo.inc deleted file mode 100644 index 16f3536479..0000000000 --- a/CMake/CPack/Windows/ProductInfo.inc +++ /dev/null @@ -1,29 +0,0 @@ -; Included by the NSIS installer script -; Don't forget to also change ProductInfo.h! - -; a friendly string to refer to the product -!define PRODUCT_FAMILY "Etterna" - -!define PRODUCT_ID "Etterna" -; TODO: This needs to be updated with the git rev hash -!define PRODUCT_VER "v0.70.3" -!define PRODUCT_DISPLAY "${PRODUCT_FAMILY} ${PRODUCT_VER}" -!define PRODUCT_BITMAP "ett" - -!define PRODUCT_URL "https://github.com/etternagame/etterna" -!define UPDATES_URL "https://github.com/etternagame/etterna" - -;!define INSTALL_EXTERNAL_PCKS -;!define INSTALL_INTERNAL_PCKS -!define INSTALL_PROGRAM_LIBRARIES -!define INSTALL_EXECUTABLES -!define INSTALL_NON_PCK_FILES -!define ASSOCIATE_SMZIP -!define ASSOCIATE_SMURL -;!define SHOW_AUTORUN -!define MAKE_OPEN_PROGRAM_FOLDER_SHORTCUT -!define MAKE_OPEN_SETTINGS_FOLDER_SHORTCUT -;!define DIRECTX_81_REDIST_PRESENT -;!define MAKE_UPDATES_SHORTCUT -!define CRC_CHECK -!define COMPRESS diff --git a/CMake/Helpers/CMakeMacOS.cmake b/CMake/Helpers/CMakeMacOS.cmake index fcc677d33d..55e1369208 100644 --- a/CMake/Helpers/CMakeMacOS.cmake +++ b/CMake/Helpers/CMakeMacOS.cmake @@ -1,7 +1,7 @@ # TODO: Remove CPU_X86_64, CPU_X86, and CRASH_HANDLER # CRASH_HANDLER is unnecessary as the game should have that as an option component # CPU_X86_64, CPU_X86 already exists as compiler predefined macros. Use those instead. -list(APPEND cdefs _XOPEN_SOURCE CPU_X86_64) +list(APPEND cdefs _XOPEN_SOURCE CPU_X86_64 GL_SILENCE_DEPRECATION) set_target_properties(Etterna PROPERTIES COMPILE_DEFINITIONS "${cdefs}") set_target_properties(Etterna PROPERTIES MACOSX_BUNDLE TRUE) set(CMAKE_EXE_LINKER_FLAGS "-pagezero_size 10000 -image_base 100000000") @@ -17,10 +17,12 @@ find_library(MAC_FRAME_AUDIOUNIT AudioUnit) find_library(MAC_FRAME_CARBON Carbon) find_library(MAC_FRAME_COREAUDIO CoreAudio) find_library(MAC_FRAME_IOKIT IOKit) +find_library(MAC_FRAME_METAL Metal) target_link_libraries(Etterna PRIVATE ${MAC_FRAME_AUDIOUNIT}) target_link_libraries(Etterna PRIVATE ${MAC_FRAME_CARBON}) target_link_libraries(Etterna PRIVATE ${MAC_FRAME_COREAUDIO}) target_link_libraries(Etterna PRIVATE ${MAC_FRAME_IOKIT}) +target_link_libraries(Etterna PRIVATE ${MAC_FRAME_METAL}) # Extern Libraries target_link_libraries(Etterna PRIVATE ffmpeg) diff --git a/CMake/Helpers/CMakeWindows.cmake b/CMake/Helpers/CMakeWindows.cmake index acdad8b662..8ef5d262c5 100644 --- a/CMake/Helpers/CMakeWindows.cmake +++ b/CMake/Helpers/CMakeWindows.cmake @@ -7,7 +7,7 @@ set_directory_properties(PROPERTIES VS_STARTUP_PROJECT Etterna) set_target_properties(Etterna PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$<1:${PROJECT_SOURCE_DIR}/Program>") # Universal Build Options -set_target_properties(Etterna PROPERTIES +set_target_properties(Etterna PROPERTIES COMPILE_FLAGS "/W3 /MP8 /INCREMENTAL /D_HAS_STD_BYTE=0" LINK_FLAGS "/SUBSYSTEM:WINDOWS /SAFESEH:NO /INCREMENTAL" COMPILE_DEFINITIONS "GLEW_STATIC") @@ -15,6 +15,9 @@ set_target_properties(Etterna PROPERTIES # By default MSVC has a 2^16 limit on the number of sections in an object file, and this needs more than that. set_source_files_properties(src/Etterna/Singletons/NetworkSyncManager.cpp PROPERTIES COMPILE_FLAGS /bigobj) +# Ignore the safer function variants provided by VC++. They are not portable. +target_compile_definitions(Etterna PRIVATE _CRT_SECURE_NO_WARNINGS) + # Linking - Windows Only target_link_libraries(Etterna PUBLIC ffmpeg) diff --git a/CMake/Helpers/CPackSetup.cmake b/CMake/Helpers/CPackSetup.cmake index b4d2da94d2..ea0f4d8dce 100644 --- a/CMake/Helpers/CPackSetup.cmake +++ b/CMake/Helpers/CPackSetup.cmake @@ -8,6 +8,20 @@ set(CPACK_COMPONENT_ETTERNA_REQUIRED TRUE) # Require Etterna component to be in # Custom Variables set(INSTALL_DIR "Etterna") +if(UNIX) + set(CPACK_GENERATOR TGZ) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "") + set(CPACK_PACKAGE_CONTACT https://github.com/etternagame/etterna) + + install(TARGETS Etterna COMPONENT Etterna DESTINATION ${INSTALL_DIR}) + install(FILES ${PROJECT_BINARY_DIR}/gn_crashpad/crashpad_handler + COMPONENT Etterna + DESTINATION ${INSTALL_DIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) +endif() + # Windows Specific CPack if(WIN32) set(CPACK_GENERATOR "NSIS") @@ -33,7 +47,8 @@ if(WIN32) # List every DLL etterna needs. list(APPEND WIN_DLLS "${PROJECT_SOURCE_DIR}/Program/avcodec-55.dll" "${PROJECT_SOURCE_DIR}/Program/avformat-55.dll" - "${PROJECT_SOURCE_DIR}/Program/avutil-52.dll" "${PROJECT_SOURCE_DIR}/Program/swscale-2.dll") + "${PROJECT_SOURCE_DIR}/Program/avutil-52.dll" "${PROJECT_SOURCE_DIR}/Program/swscale-2.dll" + ${PROJECT_BINARY_DIR}/gn_crashpad/crashpad_handler.exe) install(FILES ${WIN_DLLS} COMPONENT Etterna DESTINATION Program) install(TARGETS Etterna COMPONENT Etterna DESTINATION Program) install(FILES CMake/CPack/license_install.txt COMPONENT Etterna DESTINATION Docs) @@ -45,11 +60,15 @@ elseif(APPLE) set(CPACK_DMG_VOLUME_NAME Etterna) install(TARGETS Etterna COMPONENT Etterna DESTINATION Etterna) + install(FILES ${PROJECT_BINARY_DIR}/gn_crashpad/crashpad_handler + COMPONENT Etterna DESTINATION ${INSTALL_DIR} + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) endif() # Universal Install Directories ## Files Only -install(FILES portable.ini COMPONENT Etterna DESTINATION "${INSTALL_DIR}") install(FILES Songs/instructions.txt COMPONENT Etterna DESTINATION "${INSTALL_DIR}/Songs") install(FILES Announcers/instructions.txt COMPONENT Etterna DESTINATION "${INSTALL_DIR}/Announcers") diff --git a/CMake/Helpers/DocumentationTools.cmake b/CMake/Helpers/DocumentationTools.cmake index c3c999f86e..b817d48994 100644 --- a/CMake/Helpers/DocumentationTools.cmake +++ b/CMake/Helpers/DocumentationTools.cmake @@ -2,7 +2,7 @@ # doxygen find_package(Doxygen OPTIONAL_COMPONENTS dot) if(NOT DOXYGEN_FOUND) - message(WARNING "Doxygen not found. Documentation target will not be created.") + message(STATUS "Doxygen not found. Documentation target will not be created.") else() # set input and output files set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/Docs/Doxyfile.in) @@ -22,7 +22,7 @@ endif() # LDoc find_program(LDOC_EXE "ldoc") if(NOT LDOC_EXE) - message(WARNING "LDoc not found. Documentation target will not be created.") + message(STATUS "LDoc not found. Documentation target will not be created.") else() # set input and output files set(LDOC_IN ${PROJECT_SOURCE_DIR}/Docs/LDoc.in) diff --git a/CMake/Helpers/SetupAppInfo.cmake b/CMake/Helpers/SetupAppInfo.cmake new file mode 100644 index 0000000000..6b63124d2d --- /dev/null +++ b/CMake/Helpers/SetupAppInfo.cmake @@ -0,0 +1,16 @@ +# Get proper CRASHPAD_HANDLER_EXE +if(NOT DEFINED ENV{CI}) + set(CRASHPAD_HANDLER_EXE ${PROJECT_BINARY_DIR}/gn_crashpad/crashpad_handler) +else() + set(CRASHPAD_HANDLER_EXE crashpad_handler) +endif() + +if(WIN32) + string(APPEND CRASHPAD_HANDLER_EXE .exe) +endif() + +execute_process( + COMMAND git describe --tags --dirty + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE PROJECT_GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/CMake/Helpers/SetupFFMPEG.cmake b/CMake/Helpers/SetupFFMPEG.cmake index 6986a05c3b..3a2d7e6274 100644 --- a/CMake/Helpers/SetupFFMPEG.cmake +++ b/CMake/Helpers/SetupFFMPEG.cmake @@ -4,6 +4,10 @@ set(FFMPEG_ROOT "${PROJECT_BINARY_DIR}/ffmpeg_dl/ffmpeg-2.1.3-src") set(FFMPEG_BIN "${PROJECT_BINARY_DIR}/ffmpeg_dl/ffmpeg_dl-build") list(APPEND FFMPEG_CONFIGURE + "rm" + "-f" + "${FFMPEG_ROOT}/VERSION" + "&&" "${FFMPEG_ROOT}/configure" "--disable-programs" "--disable-doc" @@ -29,7 +33,7 @@ endif() if(APPLE) list(APPEND FFMPEG_CONFIGURE "--arch=x86_64" - "--cc=clang -m64" + "--cc=clang -arch x86_64" "--enable-sse") endif() list(APPEND FFMPEG_CONFIGURE "--enable-gpl") @@ -44,7 +48,7 @@ list(APPEND FFMPEG_BUILD_LIBS ExternalProject_Add(ffmpeg_dl PREFIX ${PROJECT_BINARY_DIR}/ffmpeg_dl - GIT_REPOSITORY "https://github.com/stepmania/ffmpeg.git" + GIT_REPOSITORY "https://github.com/etternagame/ffmpeg.git" GIT_PROGRESS TRUE GIT_SHALLOW TRUE GIT_TAG "n2.1.3" diff --git a/CMake/Modules/FindDLFCN.cmake b/CMake/Modules/FindDLFCN.cmake index eca9b7a610..7a46c700d0 100644 --- a/CMake/Modules/FindDLFCN.cmake +++ b/CMake/Modules/FindDLFCN.cmake @@ -18,7 +18,7 @@ find_library(DL_LIBRARIES dl ) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(DL DEFAULT_MSG DL_LIBRARIES DL_INCLUDE_DIR) +find_package_handle_standard_args(DLFCN DEFAULT_MSG DL_LIBRARIES DL_INCLUDE_DIR) mark_as_advanced(DL_INCLUDE_DIR DL_LIBRARIES) diff --git a/CMake/Modules/FindDirectX.cmake b/CMake/Modules/FindDirectX.cmake index 7f94aa8028..dce792805b 100644 --- a/CMake/Modules/FindDirectX.cmake +++ b/CMake/Modules/FindDirectX.cmake @@ -21,6 +21,7 @@ if(WIN32) else () set (ProgramFiles "$ENV{ProgramFiles}") endif () + # first check specified paths find_path (DirectX_ROOT_DIR Include/d3d9.h PATHS @@ -34,8 +35,17 @@ if(WIN32) "${ProgramFiles}/Microsoft DirectX SDK (November 2007)" "${ProgramFiles}/Microsoft DirectX SDK (August 2007)" "${ProgramFiles}/Microsoft DirectX SDK" + NO_DEFAULT_PATH DOC "DirectX SDK root directory" ) + # if specified paths do not contain dx then search PATH + if (NOT DirectX_ROOT_DIR) + find_path(DirectX_ROOT_DIR + Include/d3d9.h + DOC "DirectX SDK root directory" + ) + endif() + if (DirectX_ROOT_DIR) set (DIRECTX_INCLUDE_SEARCH_PATHS "${DirectX_ROOT_DIR}/Include") set (DIRECTX_LIBRARY_SEARCH_PATHS "${DirectX_ROOT_DIR}/Lib/${DirectX_ARCHITECTURE}") diff --git a/CMake/Modules/FindIconv.cmake b/CMake/Modules/FindIconv.cmake index 79ce26a772..47ca2ff9b3 100644 --- a/CMake/Modules/FindIconv.cmake +++ b/CMake/Modules/FindIconv.cmake @@ -16,7 +16,7 @@ set(ICONV_NAMES ${ICONV_NAMES} iconv libiconv libiconv-2 c) find_library(ICONV_LIBRARIES NAMES ${ICONV_NAMES}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ICONV DEFAULT_MSG ICONV_LIBRARIES ICONV_INCLUDE_DIR) +find_package_handle_standard_args(Iconv DEFAULT_MSG ICONV_LIBRARIES ICONV_INCLUDE_DIR) mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES) diff --git a/CMake/Modules/FindPulseAudio.cmake b/CMake/Modules/FindPulseAudio.cmake index 6128664c14..e65afd632f 100644 --- a/CMake/Modules/FindPulseAudio.cmake +++ b/CMake/Modules/FindPulseAudio.cmake @@ -21,7 +21,7 @@ if (PULSEAUDIO_INCLUDE_DIR AND PULSEAUDIO_LIBRARY) endif (PULSEAUDIO_INCLUDE_DIR AND PULSEAUDIO_LIBRARY) if (NOT WIN32) - include(FindPkgConfig) + find_package(PkgConfig) pkg_check_modules(PULSEAUDIO libpulse) if(PULSEAUDIO_FOUND) set(PULSEAUDIO_LIBRARY ${PULSEAUDIO_LIBRARIES} CACHE FILEPATH "Path to the PulseAudio library") @@ -40,7 +40,7 @@ if (NOT PULSEAUDIO_LIBRARY) endif (NOT PULSEAUDIO_LIBRARY) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PULSEAUDIO DEFAULT_MSG +find_package_handle_standard_args(PulseAudio DEFAULT_MSG PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY) mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY) diff --git a/CMake/Modules/FindXrandr.cmake b/CMake/Modules/FindXrandr.cmake index c2960911d5..b6e9b7c3d8 100644 --- a/CMake/Modules/FindXrandr.cmake +++ b/CMake/Modules/FindXrandr.cmake @@ -39,6 +39,6 @@ find_library(XRANDR_LIBRARIES NAMES Xrandr ) include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(XRANDR DEFAULT_MSG XRANDR_LIBRARIES XRANDR_INCLUDE_DIRS) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Xrandr DEFAULT_MSG XRANDR_LIBRARIES XRANDR_INCLUDE_DIRS) mark_as_advanced(XRANDR_INCLUDE_DIRS XRANDR_LIBRARIES) diff --git a/CMake/Modules/MacOSXBundleInfo.plist.in b/CMake/Modules/MacOSXBundleInfo.plist.in new file mode 100644 index 0000000000..530c482495 --- /dev/null +++ b/CMake/Modules/MacOSXBundleInfo.plist.in @@ -0,0 +1,35 @@ + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + False + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d367cea37..a7103dbc31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # PROJECT WIDE SETUP project(Etterna - VERSION 0.70.3 + VERSION 0.71.0 HOMEPAGE_URL https://github.com/etternagame/etterna/ LANGUAGES C CXX ASM) @@ -35,15 +35,21 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Enable folders # Project Build Targets add_executable(Etterna) +# Project Compile Options +set(WITH_CRASHPAD TRUE CACHE BOOL "Compile with Crash Handler (Requires depot_tools installed)") + ## Setting Target Properties ### Set a different name for each output binary depending on what build configuration is. ### Usually it is separated by directory, but since we have the same directory for every -### binary, we need to rename the binary -set_target_properties(Etterna PROPERTIES - RUNTIME_OUTPUT_NAME_DEBUG "Etterna-debug" - RUNTIME_OUTPUT_NAME_RELEASE "Etterna" - RUNTIME_OUTPUT_NAME_MINSIZEREL "Etterna-MinSizeRelease" - RUNTIME_OUTPUT_NAME_RELWITHDEBINFO "Etterna-RelWithDebInfo") +### binary, we need to rename the binary. We don't rename if we are compiling on CI, +### as we want the executable to be "Etterna" regardless of which version is deployed. +if(NOT DEFINED ENV{CI}) + set_target_properties(Etterna PROPERTIES + RUNTIME_OUTPUT_NAME_DEBUG "Etterna-debug" + RUNTIME_OUTPUT_NAME_RELEASE "Etterna" + RUNTIME_OUTPUT_NAME_MINSIZEREL "Etterna-MinSizeRelease" + RUNTIME_OUTPUT_NAME_RELWITHDEBINFO "Etterna-RelWithDebInfo") +endif() ### macOS and Linux place binary in root directory if(NOT WIN32) @@ -57,8 +63,11 @@ target_include_directories(Etterna PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) ## Package Includes ### OpenSSL is not used directly by our program, but we have to use OpenSSL first ### in order to statically link. Once find_package is run once, it's results are cached -set(OPENSSL_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) +if (WIN32 OR APPLE) + set(OPENSSL_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) +endif() set(OPENSSL_MSVC_STATIC_RT ON CACHE BOOL "" FORCE) + find_package(OpenSSL REQUIRED) find_package(Threads REQUIRED) target_link_libraries(Etterna PRIVATE Threads::Threads) @@ -82,10 +91,8 @@ target_link_libraries(Etterna PRIVATE SQLiteCpp sqlite3) target_link_libraries(Etterna PRIVATE zlib) target_link_libraries(Etterna PRIVATE rapidjson) target_link_libraries(Etterna PRIVATE websocketpp) -target_link_libraries(Etterna PRIVATE luajit) +target_link_libraries(Etterna PRIVATE libluajit) target_link_libraries(Etterna PRIVATE discord-rpc) -target_link_libraries(Etterna PRIVATE tomcrypt) -target_link_libraries(Etterna PRIVATE libtommath) target_link_libraries(Etterna PRIVATE muFFT) target_link_libraries(Etterna PRIVATE glfw) target_link_libraries(Etterna PRIVATE ogg) @@ -94,7 +101,16 @@ target_link_libraries(Etterna PRIVATE pcre) target_link_libraries(Etterna PRIVATE libmad) target_link_libraries(Etterna PRIVATE stb) target_link_libraries(Etterna PRIVATE libcurl) -target_link_libraries(Etterna PRIVATE picosha2) +target_link_libraries(Etterna PRIVATE fmt::fmt) +target_link_libraries(Etterna PRIVATE plog::plog) +target_link_libraries(Etterna PRIVATE nowide::nowide) +target_link_libraries(Etterna PRIVATE ghc_filesystem) + +# If the user wants crashpad, and the target exists (in-case +# the user wants it, but crashpad couldn't find python) +if(WITH_CRASHPAD AND TARGET crashpad) + target_link_libraries(Etterna PRIVATE crashpad) +endif() # OS Specific Initialization if(WIN32) @@ -111,10 +127,16 @@ add_subdirectory(src/Etterna) add_subdirectory(src/arch) add_subdirectory(src/archutils) add_subdirectory(src/RageUtil) +add_subdirectory(src/Core) ## The source_group line creates the full visual studio filter layout get_target_property(sources Etterna SOURCES) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Etterna" FILES ${sources}) +source_group(TREE ${CMAKE_SOURCE_DIR} PREFIX "Etterna" FILES ${sources}) + +# Compile Definitions +if(DEFINED ENV{CI}) + target_compile_definitions(Etterna PRIVATE ALLOW_CRASH_UPLOAD) +endif() # Static Analysis include(CMake/Helpers/StaticAnalysis.cmake) diff --git a/Data/splash.png b/Data/splash.png index 9f667a6982..165de7fae4 100644 Binary files a/Data/splash.png and b/Data/splash.png differ diff --git a/Docs/Building.md b/Docs/Building.md index 7338f44afe..0b3b36abf0 100644 --- a/Docs/Building.md +++ b/Docs/Building.md @@ -36,7 +36,7 @@ Here are some commands for current developers and contributors to get started. M ```bash cmake -G "Unix Makefiles" .. # Linux -cmake -G "Visual Studio 16 2019" .. # Windows +cmake -G "Visual Studio 16 2019" .. # Windows cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Xcode" .. # macOS ``` @@ -46,21 +46,26 @@ cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Xcode" .. # macOS - [OpenSSL](https://www.openssl.org/) (Version 1.1.1) - Debian: `apt install libssl-dev` - Fedora: `dnf install openssl-devel` + - Arch: `pacman -S openssl` + - Alpine: `apk add openssl-dev` - macOS: `brew install openssl` - - Windows: A CMake compatible version of OpenSSL is available at [Shining Light Productions](https://slproweb.com/products/Win32OpenSSL.html) website. You will need the 32bit and 64bit installers if you plan on building both versions. It's reccomended to uninstall old versions to make sure CMake can find the correct latest version. Direct links: [32bit](https://slproweb.com/download/Win32OpenSSL-1_1_1g.exe), [64bit](https://slproweb.com/download/Win64OpenSSL-1_1_1g.exe) + - Windows: A CMake compatible version of OpenSSL is available at [Shining Light Productions](https://slproweb.com/products/Win32OpenSSL.html) website. You will need the 32bit and 64bit installers if you plan on building both versions. It's reccomended to uninstall old versions to make sure CMake can find the correct latest version. Direct links: [32bit](https://slproweb.com/download/Win32OpenSSL-1_1_1i.exe), [64bit](https://slproweb.com/download/Win64OpenSSL-1_1_1i.exe) +- [depot_tools](https://dev.chromium.org/developers/how-tos/install-depot-tools) - Installation is platform specific. To skip installing this, follow the relevant instructions in [CLI Project Generation](CLI-Project-Generation). ### Linux Dependencies While most dependencies for macOS and Windows are included in the repo, there are some linux libraries which cannot be included in the repo. -- Debian: `apt install libssl-dev libx11-dev libxrandr-dev libcurl4-openssl-dev libglu1-mesa-dev libpulse-dev libogg-dev libasound-dev libjack-dev` -- Fedora: `dnf install libssl-devel libX11-devel libcurl-devel mesa-libGLU-devel libXrandr-devel libogg-devel pulseaudio-libs-devel alsa-lib-devel jack-audio-connection-kit-devel` +- Debian: `apt install build-essential libssl-dev libx11-dev libxrandr-dev libcurl4-openssl-dev libglu1-mesa-dev libpulse-dev libogg-dev libasound-dev libjack-dev` +- Fedora: `dnf install openssl-static libX11-devel libcurl-devel mesa-libGLU-devel libXrandr-devel libogg-devel pulseaudio-libs-devel alsa-lib-devel jack-audio-connection-kit-devel` +- Arch: `pacman -S openssl libx11 libxrandr curl mesa glu libogg pulseaudio jack` +- Alpine: `apk add build-base openssl-dev libx11-dev libxrandr-dev curl-dev mesa-dev glu-dev pulseaudio-dev libogg-dev alsa-lib-dev jack-dev` ### Windows Dependencies - [Visual Studio](https://visualstudio.microsoft.com/downloads/) - Any modern version of Visual Studio should be compatible _(The earliest version we theoretically support is `Visual Studio 9 2008`, though we have only tested on `Visual Studio 15 2017` and after)_ -- [DirectX Runtimes](https://www.microsoft.com/en-us/download/details.aspx?id=8109) (June 2010) -- [DirectX SDK](https://www.microsoft.com/en-us/download/details.aspx?id=6812) +- [DirectX Runtimes](https://web.archive.org/web/20180112171750/http://www.microsoft.com/en-us/download/confirmation.aspx?id=8109) (June 2010) +- [DirectX SDK](https://web.archive.org/web/20180113160705if_/https://www.microsoft.com/en-us/download/confirmation.aspx?id=6812) - [Microsoft Visual C++ Redistributables](http://www.microsoft.com/en-us/download/details.aspx?id=48145) - Both 32bit and 64bit - [Windows 10 Development Kit](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) @@ -88,14 +93,20 @@ mkdir build && cd build Etterna has game resources in the root of the project, so the output binary is either placed in the root of the project *(Unix)* or in the `Program` folder in the project root *(Windows)*. -To generate project files, you will only need to specify the `GENERATOR`. The `ARCHITECTURE` will assume 64bit if left undefined. If any trouble occurs with OpenSSL, the most likely answer will be to define where you have it installed through the `SSL_DIRECTORY` variable. +To generate project files, you will only need to specify the `GENERATOR`. The `ARCHITECTURE` will assume 64bit if left undefined. If any trouble occurs with OpenSSL, the most likely answer will be to define where you have it installed through the `SSL_DIRECTORY` variable. If depot_tools is left uninstalled or misconfigured, you may be able to run `cmake` but the game will not compile. To get around this, build without Crashpad: Specify `-DWITH_CRASHPAD=OFF` in the `cmake` command. +- `SSL_DIRECTORY`: The root directory of your OpenSSL install. It may be required on macOS depending on the OpenSSL version which comes with your system _(thought we recommend getting the latest version from homebrew)_. - `GENERATOR`: The generator you are choosing to use. Supported generators are listed below. - `ARCHITECTURE`: The target architecture. Currently we support `Win32` and `x64`. This parameter is only necessary if using a Visual Studio generator. `x64` will automatically be selected if the variable is left empty. -- `SSL_DIRECTORY`: The root directory of your OpenSSL install. It may be required on macOS depending on the OpenSSL version which comes with your system _(thought we recommend getting the latest version from homebrew)_. ```bash -cmake -G "GENERATOR" -A "ARCHITECTURE" -DOPENSSL_ROOT_DIR="SSL_DIRECTORY" .. +cmake -DOPENSSL_ROOT_DIR="SSL_DIRECTORY" -G "GENERATOR" -A "ARCHITECTURE" .. +``` + +Or to build without Crashpad: + +```bash +cmake -DOPENSSL_ROOT_DIR="SSL_DIRECTORY" -DWITH_CRASHPAD=OFF -G "GENERATOR" -A "ARCHITECTURE" .. ``` We actively support the following CMake generators @@ -110,16 +121,25 @@ For the `OPENSSL_ROOT_DIR` parameter, set the directory for where ever the opens - Linux: This parameter is not necessary on linux. (CMake can find it on it's own) - Windows: CMake writes files to find the version of OpenSSL linked above. If that version is installed, it should not be necessary to specify this variable (unless you have OpenSSL installed in a non-standard location, in which case, you should set OPENSSL_ROOT_DIR to that location) +Users building without Crashpad may choose to add the `-DWITH_CRASHPAD=OFF` option at the beginning of the command. + +Users of Linux be aware that the game builds on the `Debug` target by default. Here are better alternatives: +- `-DCMAKE_BUILD_TYPE=Release` - Builds Release binary with no symbols, normal optimization. +- `-DCMAKE_BUILD_TYPE=RelWithDebInfo` - Builds Release binary with symbols, useful for debugging if any issues arise, almost same as Release otherwise. + #### Sample CMake Commands ```bash -cmake -G "Ninja" .. # Linux Ninja -cmake -G "Unix Makefiles" .. # Linux Makefiles -cmake -G "Visual Studio 16 2019" -A Win32 .. # 32bit Windows -cmake -G "Visual Studio 16 2019" -A x64 .. # 64bit Windows -cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Xcode" .. # macOS Xcode -cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Ninja" .. # macOS Ninja -cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Unix Makefiles" .. # macOS Ninja +cmake -G "Ninja" .. # Linux Ninja +cmake -G "Unix Makefiles" .. # Linux Makefiles +cmake -G "Visual Studio 16 2019" -A Win32 .. # 32bit Windows +cmake -G "Visual Studio 16 2019" -A x64 .. # 64bit Windows +cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Xcode" .. # macOS Xcode +cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Ninja" .. # macOS Ninja +cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" -G "Unix Makefiles" .. # macOS Makefiles +cmake -DWITH_CRASHPAD=OFF -G "Visual Studio 16 2019" -A x64 .. # Without Crashpad - 64bit Windows +cmake -DCMAKE_BUILD_TYPE=Release -G "Ninja" .. # Release Configuration - Linux Ninja +cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -G "Ninja" .. # Release + Debug Symbols - Linux Ninja ``` ##### macOS Xcode Generation Note @@ -154,6 +174,8 @@ To install ninja, use one of the following commands - Debian: `apt install ninja-build` - Fedora: `dnf install ninja-build` +- Arch: `pacman -S ninja` +- Alpine: `apk add samurai #As of 2021-11-10 ninja is not available in alpine main so use samurai instead` - macOS: `brew install ninja` To start compiling, run the cmake command with the Ninja generator, then run `ninja`. @@ -205,8 +227,11 @@ To build a distribution file for the operating system you are using, run `cpack` cppcheck is a cross-platform static analysis tool which CMake supports by adding a target for it in your desired generator. The target named `cppcheck` will only be created if CMake can find the cppcheck command on your system. -- macOS: `brew install cppcheck` - Debian: `apt install cppcheck` +- Fedora: `dnf install cppcheck` +- Arch: `pacman -S cppcheck` +- Alpine: `apk add cppcheck` +- macOS: `brew install cppcheck` - Windows: An installer is available at the [cppcheck website](http://cppcheck.sourceforge.net/). Make sure that `cppcheck` runs when you enter the command in your CLI. If it doesn't, [check your system/user path](https://www.computerhope.com/issues/ch000549.htm) to ensure that the bin folder of where you installed cppcheck is listed there. When cppcheck is run, it will generate a file in the build directory called `cppcheck.txt` which will have the output of the command. The output is saved to a file as the command produces significant output, and can take some time to run. @@ -219,8 +244,11 @@ To run `cppcheck`, run the target. Running the target will be different dependin Etterna uses [doxygen](http://www.doxygen.nl/) to build it's C++ documentation. Documentation is generated in a `doxygen` directory, inside the build directory. CMake is setup to make a target called `doxygen` if the executable found in the path. -- macOS: `brew install doxygen` - Debian: `apt install doxygen` +- Fedora: `dnf install doxygen` +- Arch: `pacman -S doxygen` +- Alpine: `apk add doxygen` +- macOS: `brew install doxygen` - Windows: An installer is available at the [doxygen website](http://www.doxygen.nl/download.html). As with [cppcheck](#cppcheck), make sure the executable binary directory is added to your path. Doxygen within CMake is able to use [graphviz](https://www.graphviz.org/download/) to generate better looking relationship/hierarchy graphs. You can see how to download it for your operating system at the [graphgiz download page](https://www.graphviz.org/download/). diff --git a/Docs/Crashpad.md b/Docs/Crashpad.md new file mode 100644 index 0000000000..725d83cbcb --- /dev/null +++ b/Docs/Crashpad.md @@ -0,0 +1,278 @@ +# Using Google Crashpad with Etterna + +Etterna now uses Google Crashpad for crash handling, which gives developers significantly more +information when it comes to discovering why the program crashed but also affects users when it +comes to submitting crash reports. + +## Table of Contents + +- [Overview](#Overview) +- [What is a Minidump](#What-is-a-Minidump) +- [Preparing System](#Preparing-System) + - [Processing Tools](#Processing-Tools) + - [Compiling Processing Tools](#Compiling-Processing-Tools) + - [Building on Linux](#Building-on-Linux) + - [Building on macOS](#Building-on-macOS) + - [Building on Windows](#Building-on-Windows) +- [Generating Symbols](Generating-Symbols) + - [Windows Symbol Generation](#Windows-Symbol-Generation) + - [macOS Symbol Generation](#macOS-Symbol-Generation) +- [Storing Symbols](#Storing-Symbols) +- [Decoding Minidumps](#Decoding-Minidumps) + +## Overview + +Using Crashpad changes the CI process. For each release, we need to generate symbols for the build. +These symbols contain information about each binary, which lets us decode and find exactly what line +of code and function was being run at the time of the crash. When the game starts, a "crashpad +handler" executable is run in the background and watches the game waiting for it to crash. When it +crashes, it generates a minidump. We can process the minidump with the symbols to see how +the game crashed. + +## What is a Minidump + +According to [Sentry](https://docs.sentry.io/platforms/native/guides/minidumps/), minidumps (.dmp files) are "files containing the most important memory +regions of a crashed process." This includes the runtime stack, CPU register values, CPU +architecture, and operating system. Since it is exactly what is contained within RAM, some personal +information like usernames, passwords, and anything stored in RAM may be stored in the minidump. It +would take a motivated attack to be able to determine those values in the chance that personal +information is stored within the crash dump. If you would rather not send the Etterna team your +minidump file for debugging the crash you experienced, learn how you can decode +the minidump yourself in the [Decoding Minidumps](#Decoding-Minidumps) section. + +## Preparing System + +Users must install specific tools developed by Google before attempting to compile crashpad/breakpad, and +their processing tools. + +### Required Processing Tools + +The following tools are necessary: + +- `dump_syms`: Generates a text file with all the symbols (variables, functions, line numbers) included to allow + for a relationship to be made between the `.dmp` file and the compiled executable. Commands will usually look + like the following: + ```bash + dump_syms Etterna.pdb > Etterna.sym # Windows + dump_syms Etterna.dbg Etterna-debug > Etterna-debug.sym # Linux + dump_syms -g Etterna.dsym Etterna.app/Contents/MacOS/Etterna > Etterna.sym # macOS + ``` + +- `minidump_stackwalk`: Decodes the `.dmp` file and outputs a stack trace of what error caused the + generated `.dmp` file. One of the arguments is a directory to where the symbols get stored. Those symbols + must be organized in a specific manner, which is described below. Command will usually look like the following: + ```bash + minidump_stackwalk generated_crash_file.dmp EtternaSymbols/ > stacktrace.txt + ``` + +### Compiling Processing Tools + +Written in a step-by-step process: + +1. Get [depot_tools](https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up). + This is a collection of tools which google created to help with the compilation of their projects. + It must be installed as written on that page and added to your user/system path accordingly. + After it is added to the path (and restart the terminal session if necessary), you can run `gclient` + to ensure the path has been properly modified. + +2. Get [breakpad](https://chromium.googlesource.com/breakpad/breakpad#getting-started-from-main). While crashpad is the crash-reporting + system that Etterna uses, it is the successor to breakpad, and the decoding tools have remained the same. + It is recommended to create a folder for the breakpad project before downloading it. Once cloned, there will + be a `src` folder with the breakpad source code. Commands are as follows: + ```bash + mkdir breakpad && cd breakpad + fetch breakpad + cd src + ``` + +#### Building on Linux + +In the `src` directory, run the following commands: +```bash +./configure && make +``` +Assuming you have your compiler installed, this will build, and place the tools in the following locations: + +*Note: There is another `src` directory in the `src` you are in when you ran the above command. The locations +are relative to the command you run `make` in.* + +- `dump_syms`: `src/tools/linux/dump_syms/dump_syms` +- `minidump_stackwalk`: `src/processor/minidump_stackwalk` + +#### Building on macOS + +In the `src` directory, run the following commands: +```bash +CXXFLAGS="$CXXFLAGS -std=c++17" ./configure && make # Build minidump_stackwalk +xcodebuild -project src/tools/mac/dump_syms/dump_syms.xcodeproj -target dump_syms CLANG_CXX_LANGUAGE_STANDARD=c++17 # Build dump_syms +``` + +This will build and place the tools in the following locations: + +- `minidump_stackwalk`: `src/processor/minidump_stackwalk` +- `dump_syms`: `src/tools/mac/dump_syms/build/Release` + +##### dump_syms + +In the `src` directory, run the following commands: +```bash +CXXFLAGS="$CXXFLAGS -std=c++17" ./configure && make +``` + +#### Building on Windows + +Currently `minidump_stackwalk` is not available on Windows. The following steps are +for compiling `dump_syms.exe` + +1. Open `src\tools\windows\dump_syms\dump_syms.sln`. It will ask if you want to perform a one-way upgrade. + Click "Ok", and allow it to upgrade. + +2. Select the `Release` build at the upper-left area of the IDE. + +3. The "Solution Explorer" should show on the right side. There is a folder called `(tools)`. Open the + dropdown next to that folder, and do the same for the folder inside called `(dump_syms)`. There will + be a Visual Studio project named `dump_syms`. Right-click it, then click it, then click "Build." + If you get any errors complaining about `std::unique_ptr`, double-click the error, and add + `#include ` at the top of the file. The build should work after the includes. + +The `dump_syms.exe` will be located in the `src\tools\windows\dump_syms\Release\dump_syms.exe`. At this point, +the executable will run, but it will not generate symbol output until the following step is completed. + +4. Open an administrator command prompt, and navigate to the following directory in your Visual Studio + install. + ```bash + cd "C:\Program Files (x86)\Microsoft Visual Studio\2019" + ``` + Depending on what version of Visual Studio you have installed, you may see `Community`, `BuildTools`, `Professional`, etc + in this directory. Select any of them and continue into the following directory: + ```bash + cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\DIA SDK\bin" + ``` + + Run the command + ```bash + regsvr32 msdia140.dll + ``` + This is a DLL which is installed by Visual Studio, but does not get registered to the system, + so we must register it ourselves. Once that line executes in an admin command prompt, `dump_syms.exe` + should properly dump symbols. + +## Generating Symbols + +Symbol files relate instructions in the compiled binary file to the source code which created it. +You don't need the source code; it's all within the symbol file (which could be upwards of 30-40MB). + +### Linux Symbol Generation + +1. Enter these commands in this order, after building Etterna with `Debug` or `RelWithDebInfo`. + ```bash + objcopy --only-keep-debug Etterna Etterna.debug + dump_syms Etterna.debug Etterna > Etterna.sym + ``` + +### Windows Symbol Generation + +1. Using Visual Studio, build `Debug` or `RelWithDebInfo`. A `.pdb` file will be generated alongside + the executable. All that needs to be done is running `dump_syms` stored in + `etterna/extern/crashpad/breakpad/` + + ```powershell + cd etterna/Program + dump_syms Etterna.pdb > Etterna.sym + ``` + +### macOS Symbol Generation + +0. TL;DR - Enter these commands in this order, after building Etterna with `Debug` or `RelWithDebInfo`. +```bash +cd etterna/ +dsymutil -o Etterna.dsym Etterna.app/Contents/MacOS/Etterna +dump_syms -g Etterna.dsym Etterna.app/Contents/MacOS/Etterna > Etterna.sym +``` + +1. Before we can generation symbols, we need to build the game with debug information. That means +setting `CMAKE_BUILD_TYPE` to `Debug` or `RelWithDebInfo` and building the game. The debug +information is stored within the binary, and we'll first want to extract it into a separate file. + + ```bash + cd etterna/ + dsymutil -o Etterna.dsym Etterna.app/Contents/MacOS/Etterna + ``` + + - `-o Etterna.dsym`: A `.dSYM` on macOS is an Xcode debugging symbol folder. This option lets us + choose the output folder. We include this, otherwise, the folder will be generated in the + `Etterna.app/Contents/MacOS/` folder. + + - `Etterna.app/Contents/MacOS/Etterna` is the actual binary of Etterna. macOS `.app` folders + contain more than just the binary, so we have to give the path to the executable itself. + +2. With the generated `Etterna.dsym` file, we want to make it crashpad compatible. Breakpad + comes with a tool called `dump_syms` which lets us turn the `.dSYM` + folder into something that crashpad can understand. The tool can be found in + `etterna/extern/crashpad/breakpad/`. + + ```bash + dump_syms -g Etterna.dsym Etterna.app/Contents/MacOS/Etterna > Etterna.sym + ``` + + - `-g Etterna.dsym`: This option includes the dSYM in the symbol file. + - `Etterna.app/Contents/MacOS/Etterna`: We give the executable file to the `dump_syms` command, + so it knows what binary to relate the `.dsym` symbols with. + - `> Etterna.sym`: Redirect output to a file, as `dump_syms` will just output to the console. + +## Storing Symbols + +### Summary + +1. Open your `Etterna.sym` file, and look at the first line. It should look similar to: +```text +MODULE windows x86_64 7E72B03B469446899BB92B0EB45174FA2f Etterna-RelWithDebInfo.pdb +``` + +2. Take note of two parts of the above string: + +- Build ID: `7E72B03B469446899BB92B0EB45174FA2f` +- Module ID: `Etterna-RelWithDebInfo.pdb` + +With the above parts, we must create a directory structure that looks like the following + +```text +mkdir -p EtternaSymbols/Etterna-RelWithDebInfo.pdb/7E72B03B469446899BB92B0EB45174FA2f/Etterna.sym +``` +Use the `EtternaSymbols` folder when running `minidump_stackwalk`. + +### Explanation + +Now that we have the `Etterna.sym` file, we have everything we need to be able to debug a minidump. +For the decoder to read the symbols, it must be in a specific folder format called +"Breakpad Directory Structure." (I don't know if that is the official name, but that is what I'm +going to refer to it as.) That format is `EtternaSymbols///`. For +Etterna, you can expect symbols to look like `EtternaSymbols///Etterna.sym` +where `build-id` will be the random string `MODULE` line of the `.sym` file, and `module-id` +will be the last part of the `MODULE` line of the `.sym`. That is how the minidump decoder will +find what symbol file should be used for a particular minidump. The `EtternaSymbols` folder name +can be whatever you want, that is just the root folder for Etterna symbols. + +## Decoding Minidumps + +### Linux and macOS + +Crashpad comes with a tool called `minidump_stackwalk` that reads the minidump and symbols, then produces +a stack trace for the developer. Pass in the minidump file, then the symbol folder as parameters, +and you will get a stack trace. + +```bash +minidump_stackwalk minidumpfile.dmp EtternaSymbols/ +``` + +### Windows + +Decoding minidump on Windows is not as easy, but since minidumps have a universal format, you can +decode a Windows minidump on Linux and macOS, as long as you have the corresponding PDB file. You +can find these files on the corresponding releases pages. + +You can open a minidump on Windows using `WinDbg` or Visual Studio. `WinDbg` can be found either on the Windows Store +or within the Windows 10 SDK. Opening the minidump alone will provide information about the exception, +but not much else that is readable to a human. For all of the symbol-related features, you must point your +debugging program of choice to the exact `.exe` and associated `.pdb` which caused the crash. Access to the Etterna +source files will also give additional information about the source lines which caused the crash. diff --git a/Docs/images/cmake-gui-01.png b/Docs/images/cmake-gui-01.png index c4a127df44..3fc7b4ae5e 100644 Binary files a/Docs/images/cmake-gui-01.png and b/Docs/images/cmake-gui-01.png differ diff --git a/Docs/images/cmake-gui-02.png b/Docs/images/cmake-gui-02.png index 76d6666e87..0406d5f351 100644 Binary files a/Docs/images/cmake-gui-02.png and b/Docs/images/cmake-gui-02.png differ diff --git a/Docs/images/cmake-gui-03.png b/Docs/images/cmake-gui-03.png index 8ecde21d27..0b5927bc89 100644 Binary files a/Docs/images/cmake-gui-03.png and b/Docs/images/cmake-gui-03.png differ diff --git a/Docs/images/etterna-welcome.png b/Docs/images/etterna-welcome.png index 75c5559f12..7ef6997ab2 100644 Binary files a/Docs/images/etterna-welcome.png and b/Docs/images/etterna-welcome.png differ diff --git a/Docs/legacy/Luadoc/Lua.xml b/Docs/legacy/Luadoc/Lua.xml index 25a18d2a7c..5fd1e093bd 100644 --- a/Docs/legacy/Luadoc/Lua.xml +++ b/Docs/legacy/Luadoc/Lua.xml @@ -1109,6 +1109,7 @@ + diff --git a/Docs/legacy/Luadoc/bgline.png b/Docs/legacy/Luadoc/bgline.png index 8422416c97..2d8d70f288 100644 Binary files a/Docs/legacy/Luadoc/bgline.png and b/Docs/legacy/Luadoc/bgline.png differ diff --git a/GameTools/CmdRemoval/img/image1.png b/GameTools/CmdRemoval/img/image1.png index ba79c3d9e7..43996d784f 100644 Binary files a/GameTools/CmdRemoval/img/image1.png and b/GameTools/CmdRemoval/img/image1.png differ diff --git a/GameTools/CmdRemoval/img/image2.png b/GameTools/CmdRemoval/img/image2.png index 0573f7c39d..3fa9dbc6e6 100644 Binary files a/GameTools/CmdRemoval/img/image2.png and b/GameTools/CmdRemoval/img/image2.png differ diff --git a/GameTools/CmdRemoval/img/image3.png b/GameTools/CmdRemoval/img/image3.png index 8d19b10f3a..61bdc75cd3 100644 Binary files a/GameTools/CmdRemoval/img/image3.png and b/GameTools/CmdRemoval/img/image3.png differ diff --git a/LICENSE b/LICENSE index 16fa7692a3..a322ae805b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2019 Etterna . +Copyright (c) 2016-2021 Etterna . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NoteSkins/beat/default/Blue Go Receptor.png b/NoteSkins/beat/default/Blue Go Receptor.png index 861faf6a8a..c75ef41dfe 100644 Binary files a/NoteSkins/beat/default/Blue Go Receptor.png and b/NoteSkins/beat/default/Blue Go Receptor.png differ diff --git a/NoteSkins/beat/default/Blue Hold Body active.png b/NoteSkins/beat/default/Blue Hold Body active.png index f89f0a5c64..ad15da6725 100644 Binary files a/NoteSkins/beat/default/Blue Hold Body active.png and b/NoteSkins/beat/default/Blue Hold Body active.png differ diff --git a/NoteSkins/beat/default/Blue Hold Body inactive.png b/NoteSkins/beat/default/Blue Hold Body inactive.png deleted file mode 100644 index c36df45ad7..0000000000 Binary files a/NoteSkins/beat/default/Blue Hold Body inactive.png and /dev/null differ diff --git a/NoteSkins/beat/default/Blue Hold Body inactive.redir b/NoteSkins/beat/default/Blue Hold Body inactive.redir new file mode 100644 index 0000000000..6a8ab2dd89 --- /dev/null +++ b/NoteSkins/beat/default/Blue Hold Body inactive.redir @@ -0,0 +1 @@ +Blue Hold Body active \ No newline at end of file diff --git a/NoteSkins/beat/default/Blue Hold BottomCap active.png b/NoteSkins/beat/default/Blue Hold BottomCap active.png deleted file mode 100644 index c581f30219..0000000000 Binary files a/NoteSkins/beat/default/Blue Hold BottomCap active.png and /dev/null differ diff --git a/NoteSkins/beat/default/Blue Hold BottomCap active.redir b/NoteSkins/beat/default/Blue Hold BottomCap active.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/Blue Hold BottomCap active.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/Blue Hold BottomCap inactive.png b/NoteSkins/beat/default/Blue Hold BottomCap inactive.png deleted file mode 100644 index 9e05876236..0000000000 Binary files a/NoteSkins/beat/default/Blue Hold BottomCap inactive.png and /dev/null differ diff --git a/NoteSkins/beat/default/Blue Hold BottomCap inactive.redir b/NoteSkins/beat/default/Blue Hold BottomCap inactive.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/Blue Hold BottomCap inactive.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/Blue Laser.png b/NoteSkins/beat/default/Blue Laser.png new file mode 100644 index 0000000000..428d0619d8 Binary files /dev/null and b/NoteSkins/beat/default/Blue Laser.png differ diff --git a/NoteSkins/beat/default/Blue Tap Explosion Bright 9x1.png b/NoteSkins/beat/default/Blue Tap Explosion Bright 9x1.png index d2e823b01c..ac2dd5081f 100644 Binary files a/NoteSkins/beat/default/Blue Tap Explosion Bright 9x1.png and b/NoteSkins/beat/default/Blue Tap Explosion Bright 9x1.png differ diff --git a/NoteSkins/beat/default/Blue Tap Explosion Dim 9x1.png b/NoteSkins/beat/default/Blue Tap Explosion Dim 9x1.png deleted file mode 100644 index d2e823b01c..0000000000 Binary files a/NoteSkins/beat/default/Blue Tap Explosion Dim 9x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/Blue Tap Explosion Dim.redir b/NoteSkins/beat/default/Blue Tap Explosion Dim.redir new file mode 100644 index 0000000000..fbc7435b7d --- /dev/null +++ b/NoteSkins/beat/default/Blue Tap Explosion Dim.redir @@ -0,0 +1 @@ +Blue Tap Explosion Bright \ No newline at end of file diff --git a/NoteSkins/beat/default/Blue Tap Mine 1x1.png b/NoteSkins/beat/default/Blue Tap Mine 1x1.png deleted file mode 100644 index e02a88da94..0000000000 Binary files a/NoteSkins/beat/default/Blue Tap Mine 1x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/Blue Tap Mine.redir b/NoteSkins/beat/default/Blue Tap Mine.redir new file mode 100644 index 0000000000..e450c0165f --- /dev/null +++ b/NoteSkins/beat/default/Blue Tap Mine.redir @@ -0,0 +1 @@ +_Mine \ No newline at end of file diff --git a/NoteSkins/beat/default/Blue Tap Note 1x1.png b/NoteSkins/beat/default/Blue Tap Note 1x1.png index b275e5acc3..4c7139e46c 100644 Binary files a/NoteSkins/beat/default/Blue Tap Note 1x1.png and b/NoteSkins/beat/default/Blue Tap Note 1x1.png differ diff --git a/NoteSkins/beat/default/Fallback Hold Explosion 9x1.png b/NoteSkins/beat/default/Fallback Hold Explosion 9x1.png new file mode 100644 index 0000000000..abe05bdcb2 Binary files /dev/null and b/NoteSkins/beat/default/Fallback Hold Explosion 9x1.png differ diff --git a/NoteSkins/beat/default/NoteSkin.lua b/NoteSkins/beat/default/NoteSkin.lua index ae1c5cdc02..c1620c8172 100644 --- a/NoteSkins/beat/default/NoteSkin.lua +++ b/NoteSkins/beat/default/NoteSkin.lua @@ -61,15 +61,22 @@ ret.PartsToRotate = { -- ret.Rotate.UpLeft = 0; -- ret.Rotate.UpRight = 90; -- + +--This table doesn't seem to do anything and I don't know why ret.Blank = { ["Hold Topcap Active"] = true, ["Hold Topcap Inactive"] = true, ["Roll Topcap Active"] = true, ["Roll Topcap Inactive"] = true, + ["Hold BottomCap Active"] = true, + ["Hold BottomCap Inactive"] = true, + ["Roll BottomCap Active"] = true, + ["Roll BottomCap Inactive"] = true, ["Hold Tail Active"] = true, ["Hold Tail Inactive"] = true, ["Roll Tail Active"] = true, ["Roll Tail Inactive"] = true + } return ret diff --git a/NoteSkins/beat/default/Red Go Receptor.png b/NoteSkins/beat/default/Red Go Receptor.png index 004dba71d5..e09c2ed73a 100644 Binary files a/NoteSkins/beat/default/Red Go Receptor.png and b/NoteSkins/beat/default/Red Go Receptor.png differ diff --git a/NoteSkins/beat/default/Red Hold Body active.png b/NoteSkins/beat/default/Red Hold Body active.png new file mode 100644 index 0000000000..4822b10e6e Binary files /dev/null and b/NoteSkins/beat/default/Red Hold Body active.png differ diff --git a/NoteSkins/beat/default/Red Hold Body inactive.redir b/NoteSkins/beat/default/Red Hold Body inactive.redir new file mode 100644 index 0000000000..9fc409bec9 --- /dev/null +++ b/NoteSkins/beat/default/Red Hold Body inactive.redir @@ -0,0 +1 @@ +Red Hold Body active \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Hold BottomCap active.redir b/NoteSkins/beat/default/Red Hold BottomCap active.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/Red Hold BottomCap active.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Hold BottomCap inactive.redir b/NoteSkins/beat/default/Red Hold BottomCap inactive.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/Red Hold BottomCap inactive.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Laser.png b/NoteSkins/beat/default/Red Laser.png new file mode 100644 index 0000000000..cf4c06af5e Binary files /dev/null and b/NoteSkins/beat/default/Red Laser.png differ diff --git a/NoteSkins/beat/default/Red Tap Explosion Bright 9x1.png b/NoteSkins/beat/default/Red Tap Explosion Bright 9x1.png deleted file mode 100644 index d2e823b01c..0000000000 Binary files a/NoteSkins/beat/default/Red Tap Explosion Bright 9x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/Red Tap Explosion Bright.redir b/NoteSkins/beat/default/Red Tap Explosion Bright.redir new file mode 100644 index 0000000000..fbc7435b7d --- /dev/null +++ b/NoteSkins/beat/default/Red Tap Explosion Bright.redir @@ -0,0 +1 @@ +Blue Tap Explosion Bright \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Tap Explosion Dim 9x1.png b/NoteSkins/beat/default/Red Tap Explosion Dim 9x1.png deleted file mode 100644 index d2e823b01c..0000000000 Binary files a/NoteSkins/beat/default/Red Tap Explosion Dim 9x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/Red Tap Explosion Dim.redir b/NoteSkins/beat/default/Red Tap Explosion Dim.redir new file mode 100644 index 0000000000..fbc7435b7d --- /dev/null +++ b/NoteSkins/beat/default/Red Tap Explosion Dim.redir @@ -0,0 +1 @@ +Blue Tap Explosion Bright \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Tap Mine 1x1.png b/NoteSkins/beat/default/Red Tap Mine 1x1.png deleted file mode 100644 index 11c89f57d2..0000000000 Binary files a/NoteSkins/beat/default/Red Tap Mine 1x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/Red Tap Mine.redir b/NoteSkins/beat/default/Red Tap Mine.redir new file mode 100644 index 0000000000..e450c0165f --- /dev/null +++ b/NoteSkins/beat/default/Red Tap Mine.redir @@ -0,0 +1 @@ +_Mine \ No newline at end of file diff --git a/NoteSkins/beat/default/Red Tap Note 1x1.png b/NoteSkins/beat/default/Red Tap Note 1x1.png index 0fa583d42d..3469e697de 100644 Binary files a/NoteSkins/beat/default/Red Tap Note 1x1.png and b/NoteSkins/beat/default/Red Tap Note 1x1.png differ diff --git a/NoteSkins/beat/default/White Go Receptor.png b/NoteSkins/beat/default/White Go Receptor.png index eedb5d7a00..0a785a0b37 100644 Binary files a/NoteSkins/beat/default/White Go Receptor.png and b/NoteSkins/beat/default/White Go Receptor.png differ diff --git a/NoteSkins/beat/default/White Hold Body active.png b/NoteSkins/beat/default/White Hold Body active.png index 71a38630ac..b615d3c764 100644 Binary files a/NoteSkins/beat/default/White Hold Body active.png and b/NoteSkins/beat/default/White Hold Body active.png differ diff --git a/NoteSkins/beat/default/White Hold Body inactive.png b/NoteSkins/beat/default/White Hold Body inactive.png deleted file mode 100644 index 7a22d72278..0000000000 Binary files a/NoteSkins/beat/default/White Hold Body inactive.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Hold Body inactive.redir b/NoteSkins/beat/default/White Hold Body inactive.redir new file mode 100644 index 0000000000..6183b9169d --- /dev/null +++ b/NoteSkins/beat/default/White Hold Body inactive.redir @@ -0,0 +1 @@ +White Hold Body active \ No newline at end of file diff --git a/NoteSkins/beat/default/White Hold BottomCap active.png b/NoteSkins/beat/default/White Hold BottomCap active.png deleted file mode 100644 index 0eda0e0903..0000000000 Binary files a/NoteSkins/beat/default/White Hold BottomCap active.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Hold BottomCap active.redir b/NoteSkins/beat/default/White Hold BottomCap active.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/White Hold BottomCap active.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/White Hold BottomCap inactive.png b/NoteSkins/beat/default/White Hold BottomCap inactive.png deleted file mode 100644 index d08cf4a2c1..0000000000 Binary files a/NoteSkins/beat/default/White Hold BottomCap inactive.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Hold BottomCap inactive.redir b/NoteSkins/beat/default/White Hold BottomCap inactive.redir new file mode 100644 index 0000000000..80251c4433 --- /dev/null +++ b/NoteSkins/beat/default/White Hold BottomCap inactive.redir @@ -0,0 +1 @@ +zBlank \ No newline at end of file diff --git a/NoteSkins/beat/default/White Laser (res 38x240).png b/NoteSkins/beat/default/White Laser (res 38x240).png new file mode 100644 index 0000000000..926fda9cd4 Binary files /dev/null and b/NoteSkins/beat/default/White Laser (res 38x240).png differ diff --git a/NoteSkins/beat/default/White Tap Explosion Bright 9x1.png b/NoteSkins/beat/default/White Tap Explosion Bright 9x1.png deleted file mode 100644 index d2e823b01c..0000000000 Binary files a/NoteSkins/beat/default/White Tap Explosion Bright 9x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Tap Explosion Bright.redir b/NoteSkins/beat/default/White Tap Explosion Bright.redir new file mode 100644 index 0000000000..fbc7435b7d --- /dev/null +++ b/NoteSkins/beat/default/White Tap Explosion Bright.redir @@ -0,0 +1 @@ +Blue Tap Explosion Bright \ No newline at end of file diff --git a/NoteSkins/beat/default/White Tap Explosion Dim 9x1.png b/NoteSkins/beat/default/White Tap Explosion Dim 9x1.png deleted file mode 100644 index d2e823b01c..0000000000 Binary files a/NoteSkins/beat/default/White Tap Explosion Dim 9x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Tap Explosion Dim.redir b/NoteSkins/beat/default/White Tap Explosion Dim.redir new file mode 100644 index 0000000000..fbc7435b7d --- /dev/null +++ b/NoteSkins/beat/default/White Tap Explosion Dim.redir @@ -0,0 +1 @@ +Blue Tap Explosion Bright \ No newline at end of file diff --git a/NoteSkins/beat/default/White Tap Mine 1x1.png b/NoteSkins/beat/default/White Tap Mine 1x1.png deleted file mode 100644 index 7c1a62eab3..0000000000 Binary files a/NoteSkins/beat/default/White Tap Mine 1x1.png and /dev/null differ diff --git a/NoteSkins/beat/default/White Tap Mine.redir b/NoteSkins/beat/default/White Tap Mine.redir new file mode 100644 index 0000000000..e450c0165f --- /dev/null +++ b/NoteSkins/beat/default/White Tap Mine.redir @@ -0,0 +1 @@ +_Mine \ No newline at end of file diff --git a/NoteSkins/beat/default/White Tap Note 1x1.png b/NoteSkins/beat/default/White Tap Note 1x1.png index c1027190ca..a2a532ccfb 100644 Binary files a/NoteSkins/beat/default/White Tap Note 1x1.png and b/NoteSkins/beat/default/White Tap Note 1x1.png differ diff --git a/NoteSkins/beat/default/_Blue Tap Explosion Bright.redir b/NoteSkins/beat/default/_Blue Tap Explosion Bright.redir deleted file mode 100644 index d20fc4c55d..0000000000 --- a/NoteSkins/beat/default/_Blue Tap Explosion Bright.redir +++ /dev/null @@ -1 +0,0 @@ -Blue Tap Explosion Dim \ No newline at end of file diff --git a/NoteSkins/beat/default/_Blue Tap Explosion Dim.png b/NoteSkins/beat/default/_Blue Tap Explosion Dim.png deleted file mode 100644 index 1c2cf7d59a..0000000000 Binary files a/NoteSkins/beat/default/_Blue Tap Explosion Dim.png and /dev/null differ diff --git a/NoteSkins/beat/default/_Mine.png b/NoteSkins/beat/default/_Mine.png new file mode 100644 index 0000000000..c67aebcf5a Binary files /dev/null and b/NoteSkins/beat/default/_Mine.png differ diff --git a/NoteSkins/beat/default/_Red Tap Explosion Bright.redir b/NoteSkins/beat/default/_Red Tap Explosion Bright.redir deleted file mode 100644 index 13f826785b..0000000000 --- a/NoteSkins/beat/default/_Red Tap Explosion Bright.redir +++ /dev/null @@ -1 +0,0 @@ -Red Tap Explosion Dim \ No newline at end of file diff --git a/NoteSkins/beat/default/_Red Tap Explosion Dim.png b/NoteSkins/beat/default/_Red Tap Explosion Dim.png deleted file mode 100644 index 5557fdf4e1..0000000000 Binary files a/NoteSkins/beat/default/_Red Tap Explosion Dim.png and /dev/null differ diff --git a/NoteSkins/beat/default/_Tap Lead-in Receptor.lua b/NoteSkins/beat/default/_Tap Lead-in Receptor.lua index 620240f47f..a5c5142aa9 100644 --- a/NoteSkins/beat/default/_Tap Lead-in Receptor.lua +++ b/NoteSkins/beat/default/_Tap Lead-in Receptor.lua @@ -1,19 +1,5 @@ return Def.ActorFrame { children = { - LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Ready Receptor")) .. - { - Frame0000 = 0, - Delay0000 = 1, - InitCommand = function(self) - self:playcommand("Set") - end, - GameplayLeadInChangedMessageCommand = function(self) - self:playcommand("Set") - end, - SetCommand = function(self) - self:visible(GAMESTATE:GetGameplayLeadIn()) - end - }, LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Go Receptor")) .. { Frame0000 = 0, diff --git a/NoteSkins/beat/default/_Tap Receptor.lua b/NoteSkins/beat/default/_Tap Receptor.lua index 778c66e8e1..0457d7ee65 100644 --- a/NoteSkins/beat/default/_Tap Receptor.lua +++ b/NoteSkins/beat/default/_Tap Receptor.lua @@ -1,15 +1,64 @@ -local t = ... -assert(type(t) == "table") +local Reverse = string.find(GAMESTATE:GetPlayerState(pn):GetPlayerOptionsString("ModsLevel_Preferred"), "Reverse"); +local sButton = Var "Button" -return t .. - { +if sButton == "scratch" + then column = "Red" + +elseif sButton == "Key2" + or sButton == "Key4" + or sButton == "Key6" + then column = "Blue" +else + column = "White" +end + +local t = + Def.ActorFrame { + --Judgeline + Def.Sprite { + Texture = column .. " Go Receptor", + Frames=Sprite.LinearFrames(1,1), InitCommand = NOTESKIN:GetMetricA("ReceptorArrow", "InitCommand"), - MissCommand = NOTESKIN:GetMetricA("ReceptorArrow", "MissCommand"), NoneCommand = NOTESKIN:GetMetricA("ReceptorArrow", "NoneCommand"), - HitMineCommand = NOTESKIN:GetMetricA("ReceptorArrow", "HitMineCommand"), + PressCommand = NOTESKIN:GetMetricA("ReceptorArrow", "PressCommand"), + LiftCommand = NOTESKIN:GetMetricA("ReceptorArrow", "LiftCommand"), W5Command = NOTESKIN:GetMetricA("ReceptorArrow", "W5Command"), W4Command = NOTESKIN:GetMetricA("ReceptorArrow", "W4Command"), W3Command = NOTESKIN:GetMetricA("ReceptorArrow", "W3Command"), W2Command = NOTESKIN:GetMetricA("ReceptorArrow", "W2Command"), W1Command = NOTESKIN:GetMetricA("ReceptorArrow", "W1Command") + }, + --Laser + Def.Sprite { + Texture = column .. " Laser", + Frames=Sprite.LinearFrames(1,1), + InitCommand = function(self) + if not Reverse then + self:addrotationz(180) + self:addy(4) + else + self:addy(-4) + end + + self:valign(1) + self:diffusealpha(0) + self:zoom(1) + self:blend("BlendMode_Add") + end, + NoneCommand = NOTESKIN:GetMetricA("ReceptorArrow", "NoneCommand"), + PressCommand = function(self) + self:finishtweening() + self:diffusealpha(1) + self:zoomy(0.5) + self:zoomx(1) + end, + LiftCommand = function(self) + self:zoomy(0.75) + self:decelerate(0.192) + self:zoomx(0.33) + self:diffusealpha(0) + end } + +} +return t \ No newline at end of file diff --git a/NoteSkins/beat/default/_White Tap Explosion Bright.redir b/NoteSkins/beat/default/_White Tap Explosion Bright.redir deleted file mode 100644 index 5007a73fa8..0000000000 --- a/NoteSkins/beat/default/_White Tap Explosion Bright.redir +++ /dev/null @@ -1 +0,0 @@ -White Tap Explosion Dim \ No newline at end of file diff --git a/NoteSkins/beat/default/_White Tap Explosion Dim.png b/NoteSkins/beat/default/_White Tap Explosion Dim.png deleted file mode 100644 index 1c2cf7d59a..0000000000 Binary files a/NoteSkins/beat/default/_White Tap Explosion Dim.png and /dev/null differ diff --git a/NoteSkins/beat/default/metrics.ini b/NoteSkins/beat/default/metrics.ini index 3095d747a5..68cb8da0ff 100644 --- a/NoteSkins/beat/default/metrics.ini +++ b/NoteSkins/beat/default/metrics.ini @@ -13,7 +13,7 @@ HoldBottomCapAnimationLength=4 // doesn't matter. Only 1 frame anyway. HoldBodyAnimationLength=4 // doesn't matter. Only 1 frame anyway. HoldTailAnimationLength=4 // doesn't matter. Only 1 frame anyway. StartDrawingHoldBodyOffsetFromHead=0 -StopDrawingHoldBodyOffsetFromTail=-32 // top of tail +StopDrawingHoldBodyOffsetFromTail=4 // top of tail HoldLetGoGrayPercent=0.25 ReverseDrawOrder=0000000 HoldHeadIsAboveWavyParts=1 @@ -40,8 +40,8 @@ JudgmentFrames=7 JudgmentSeconds=0.2 MineFrames=1 MineSeconds=0.2 -HoldFrames=1 -HoldSeconds=0.2 +HoldFrames=9 +HoldSeconds=0.3 [GhostArrowBright] W5Command=loop,0;diffusealpha,1;setstate,0;sleep,self:GetAnimationLengthSeconds();diffusealpha,0 @@ -56,7 +56,12 @@ LetGoCommand=sleep,0.06 HeldCommand=diffuse,1.0,1.0,0.3,1;zoom,1;linear,0.1;zoom,1.1;linear,0.1;diffusealpha,0 [ReceptorArrow] +HitMineCommand= +MissCommand= +W5Command= +W4Command= W3Command= W2Command= W1Command= -#NoneCommand= +NoneCommand= +InitCommand= \ No newline at end of file diff --git a/NoteSkins/beat/default/zBlank.png b/NoteSkins/beat/default/zBlank.png new file mode 100644 index 0000000000..d3291020f4 Binary files /dev/null and b/NoteSkins/beat/default/zBlank.png differ diff --git a/NoteSkins/common/common/Fallback Explosion.lua b/NoteSkins/common/common/Fallback Explosion.lua index 4136fc2881..324364eb45 100644 --- a/NoteSkins/common/common/Fallback Explosion.lua +++ b/NoteSkins/common/common/Fallback Explosion.lua @@ -1,99 +1,99 @@ local t = - Def.ActorFrame { - NOTESKIN:LoadActor(Var "Button", "Hold Explosion") .. - { - HoldingOnCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "HoldingOnCommand"), - HoldingOffCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "HoldingOffCommand"), - InitCommand = function(self) - self:playcommand("HoldingOff"):finishtweening() - end - }, - NOTESKIN:LoadActor(Var "Button", "Roll Explosion") .. - { - RollOnCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "RollOnCommand"), - RollOffCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "RollOffCommand"), - InitCommand = function(self) - self:playcommand("RollOff"):finishtweening() - end - }, - NOTESKIN:LoadActor(Var "Button", "Tap Explosion Dim") .. - { - InitCommand = function(self) - self:diffusealpha(0) - end, - W5Command = NOTESKIN:GetMetricA("GhostArrowDim", "W5Command"), - W4Command = NOTESKIN:GetMetricA("GhostArrowDim", "W4Command"), - W3Command = NOTESKIN:GetMetricA("GhostArrowDim", "W3Command"), - W2Command = NOTESKIN:GetMetricA("GhostArrowDim", "W2Command"), - W1Command = NOTESKIN:GetMetricA("GhostArrowDim", "W1Command"), - JudgmentCommand = function(self) - self:finishtweening() - end, - BrightCommand = function(self) - self:visible(false) - end, - DimCommand = function(self) - self:visible(true) - end - }, - NOTESKIN:LoadActor(Var "Button", "Held Explosion Dim") .. - { - InitCommand = function(self) - self:diffusealpha(0) - end, - HeldCommand = NOTESKIN:GetMetricA("GhostArrowDim", "HeldCommand"), - JudgmentCommand = function(self) - self:finishtweening() - end, - BrightCommand = function(self) - self:visible(false) - end, - DimCommand = function(self) - self:visible(true) - end - }, - NOTESKIN:LoadActor(Var "Button", "Tap Explosion Bright") .. - { - InitCommand = function(self) - self:diffusealpha(0) - end, - W5Command = NOTESKIN:GetMetricA("GhostArrowBright", "W5Command"), - W4Command = NOTESKIN:GetMetricA("GhostArrowBright", "W4Command"), - W3Command = NOTESKIN:GetMetricA("GhostArrowBright", "W3Command"), - W2Command = NOTESKIN:GetMetricA("GhostArrowBright", "W2Command"), - W1Command = NOTESKIN:GetMetricA("GhostArrowBright", "W1Command"), - JudgmentCommand = function(self) - self:finishtweening() - end, - BrightCommand = function(self) - self:visible(true) - end, - DimCommand = function(self) - self:visible(false) - end - }, - NOTESKIN:LoadActor(Var "Button", "Held Explosion Bright") .. - { - InitCommand = function(self) - self:diffusealpha(0) - end, - HeldCommand = NOTESKIN:GetMetricA("GhostArrowBright", "HeldCommand"), - JudgmentCommand = function(self) - self:finishtweening() - end, - BrightCommand = function(self) - self:visible(true) - end, - DimCommand = function(self) - self:visible(false) - end - }, - NOTESKIN:LoadActor(Var "Button", "HitMine Explosion") .. - { - InitCommand = function(self) - self:blend("BlendMode_Add"):diffusealpha(0) - end, - HitMineCommand = NOTESKIN:GetMetricA("GhostArrowBright", "HitMineCommand") - } + Def.ActorFrame { + NOTESKIN:LoadActor(Var "Button", "Hold Explosion") .. + { + HoldingOnCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "HoldingOnCommand"), + HoldingOffCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "HoldingOffCommand"), + InitCommand = function(self) + self:playcommand("HoldingOff"):finishtweening() + end + }, + NOTESKIN:LoadActor(Var "Button", "Roll Explosion") .. + { + RollOnCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "RollOnCommand"), + RollOffCommand = NOTESKIN:GetMetricA("HoldGhostArrow", "RollOffCommand"), + InitCommand = function(self) + self:playcommand("RollOff"):finishtweening() + end + }, + NOTESKIN:LoadActor(Var "Button", "Tap Explosion Dim") .. + { + InitCommand = function(self) + self:diffusealpha(0) + end, + W5Command = NOTESKIN:GetMetricA("GhostArrowDim", "W5Command"), + W4Command = NOTESKIN:GetMetricA("GhostArrowDim", "W4Command"), + W3Command = NOTESKIN:GetMetricA("GhostArrowDim", "W3Command"), + W2Command = NOTESKIN:GetMetricA("GhostArrowDim", "W2Command"), + W1Command = NOTESKIN:GetMetricA("GhostArrowDim", "W1Command"), + JudgmentCommand = function(self) + self:finishtweening() + end, + BrightCommand = function(self) + self:visible(false) + end, + DimCommand = function(self) + self:visible(true) + end + }, + NOTESKIN:LoadActor(Var "Button", "Held Explosion Dim") .. + { + InitCommand = function(self) + self:diffusealpha(0) + end, + HeldCommand = NOTESKIN:GetMetricA("GhostArrowDim", "HeldCommand"), + JudgmentCommand = function(self) + self:finishtweening() + end, + BrightCommand = function(self) + self:visible(false) + end, + DimCommand = function(self) + self:visible(true) + end + }, + NOTESKIN:LoadActor(Var "Button", "Tap Explosion Bright") .. + { + InitCommand = function(self) + self:diffusealpha(0) + end, + W5Command = NOTESKIN:GetMetricA("GhostArrowBright", "W5Command"), + W4Command = NOTESKIN:GetMetricA("GhostArrowBright", "W4Command"), + W3Command = NOTESKIN:GetMetricA("GhostArrowBright", "W3Command"), + W2Command = NOTESKIN:GetMetricA("GhostArrowBright", "W2Command"), + W1Command = NOTESKIN:GetMetricA("GhostArrowBright", "W1Command"), + JudgmentCommand = function(self) + self:finishtweening() + end, + BrightCommand = function(self) + self:visible(true) + end, + DimCommand = function(self) + self:visible(false) + end + }, + NOTESKIN:LoadActor(Var "Button", "Held Explosion Bright") .. + { + InitCommand = function(self) + self:diffusealpha(0) + end, + HeldCommand = NOTESKIN:GetMetricA("GhostArrowBright", "HeldCommand"), + JudgmentCommand = function(self) + self:finishtweening() + end, + BrightCommand = function(self) + self:visible(true) + end, + DimCommand = function(self) + self:visible(false) + end + }, + NOTESKIN:LoadActor(Var "Button", "HitMine Explosion") .. + { + InitCommand = function(self) + self:blend("BlendMode_Add"):diffusealpha(0) + end, + HitMineCommand = NOTESKIN:GetMetricA("GhostArrowBright", "HitMineCommand") + } } return t diff --git a/NoteSkins/common/common/NoteSkin.lua b/NoteSkins/common/common/NoteSkin.lua index 658304d73d..c2fdd281e0 100644 --- a/NoteSkins/common/common/NoteSkin.lua +++ b/NoteSkins/common/common/NoteSkin.lua @@ -1,9 +1,9 @@ local ret = ... or {} ret.Redir = function(sButton, sElement) - -- To redirect files for Up to Down: - -- if sButton == "Up" then sButton = "Down"; end - return sButton, sElement + -- To redirect files for Up to Down: + -- if sButton == "Up" then sButton = "Down"; end + return sButton, sElement end ret.Rotate = {} @@ -13,40 +13,40 @@ ret.PartsToRotate = {} ret.Blank = {} local function func() - local sButton = Var "Button" - local sElement = Var "Element" - - if ret.Blank[sElement] then - -- Return a blank element. If SpriteOnly is set, - -- we need to return a sprite; otherwise just return - -- a dummy actor. - local t - if Var "SpriteOnly" then - t = LoadActor("_blank") - else - t = Def.Actor {} - end - return t .. - { - function(self) - self:visible(false) - end - } - end - - local sButtonToLoad, sElementToLoad = ret.Redir(sButton, sElement) - assert(sButtonToLoad) - assert(sElementToLoad) - - local sPath = NOTESKIN:GetPath(sButtonToLoad, sElementToLoad) - - local t = LoadActor(sPath) - - if ret.PartsToRotate[sElement] then - t.BaseRotationZ = ret.Rotate[sButton] - end - - return t + local sButton = Var "Button" + local sElement = Var "Element" + + if ret.Blank[sElement] then + -- Return a blank element. If SpriteOnly is set, + -- we need to return a sprite; otherwise just return + -- a dummy actor. + local t + if Var "SpriteOnly" then + t = LoadActor("_blank") + else + t = Def.Actor {} + end + return t .. + { + function(self) + self:visible(false) + end + } + end + + local sButtonToLoad, sElementToLoad = ret.Redir(sButton, sElement) + assert(sButtonToLoad) + assert(sElementToLoad) + + local sPath = NOTESKIN:GetPath(sButtonToLoad, sElementToLoad) + + local t = LoadActor(sPath) + + if ret.PartsToRotate[sElement] then + t.BaseRotationZ = ret.Rotate[sButton] + end + + return t end -- This is the only required function. diff --git a/NoteSkins/common/common/_Tap Lead-in Receptor.lua b/NoteSkins/common/common/_Tap Lead-in Receptor.lua index 578a6ad1d9..ae63022958 100644 --- a/NoteSkins/common/common/_Tap Lead-in Receptor.lua +++ b/NoteSkins/common/common/_Tap Lead-in Receptor.lua @@ -1,34 +1,34 @@ return Def.ActorFrame { - LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Ready Receptor")) .. - { - Frames = { - {Frame = 2, Delay = 1} - }, - InitCommand = function(self) - self:playcommand("Set") - end, - GameplayLeadInChangedMessageCommand = function(self) - self:playcommand("Set") - end, - SetCommand = function(self) - self:visible(GAMESTATE:GetGameplayLeadIn()) - end - }, - LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Go Receptor")) .. - { - Frames = { - {Frame = 0}, - {Frame = 1, Delay = 0.8}, - {Frame = 2} - }, - InitCommand = function(self) - self:playcommand("Set") - end, - GameplayLeadInChangedMessageCommand = function(self) - self:playcommand("Set") - end, - SetCommand = function(self) - self:visible(not GAMESTATE:GetGameplayLeadIn()) - end - } + LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Ready Receptor")) .. + { + Frames = { + {Frame = 2, Delay = 1} + }, + InitCommand = function(self) + self:playcommand("Set") + end, + GameplayLeadInChangedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + self:visible(GAMESTATE:GetGameplayLeadIn()) + end + }, + LoadActor("_Tap Receptor", NOTESKIN:LoadActor(Var "Button", "Go Receptor")) .. + { + Frames = { + {Frame = 0}, + {Frame = 1, Delay = 0.8}, + {Frame = 2} + }, + InitCommand = function(self) + self:playcommand("Set") + end, + GameplayLeadInChangedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + self:visible(not GAMESTATE:GetGameplayLeadIn()) + end + } } diff --git a/NoteSkins/common/common/_Tap Press.lua b/NoteSkins/common/common/_Tap Press.lua index b7622991bc..b924cec2b2 100644 --- a/NoteSkins/common/common/_Tap Press.lua +++ b/NoteSkins/common/common/_Tap Press.lua @@ -1,12 +1,12 @@ local File = ... return LoadActor(File) .. - { - InitCommand = function(self) - self:playcommand("Lift") - end, - ReverseOnCommand = NOTESKIN:GetMetricA("Press", "ReverseOnCommand"), - ReverseOffCommand = NOTESKIN:GetMetricA("Press", "ReverseOffCommand"), - PressCommand = NOTESKIN:GetMetricA("Press", "PressCommand"), - LiftCommand = NOTESKIN:GetMetricA("Press", "LiftCommand") - } + { + InitCommand = function(self) + self:playcommand("Lift") + end, + ReverseOnCommand = NOTESKIN:GetMetricA("Press", "ReverseOnCommand"), + ReverseOffCommand = NOTESKIN:GetMetricA("Press", "ReverseOffCommand"), + PressCommand = NOTESKIN:GetMetricA("Press", "PressCommand"), + LiftCommand = NOTESKIN:GetMetricA("Press", "LiftCommand") + } diff --git a/NoteSkins/common/common/_Tap Receptor.lua b/NoteSkins/common/common/_Tap Receptor.lua index 778c66e8e1..02a7d68744 100644 --- a/NoteSkins/common/common/_Tap Receptor.lua +++ b/NoteSkins/common/common/_Tap Receptor.lua @@ -2,14 +2,14 @@ local t = ... assert(type(t) == "table") return t .. - { - InitCommand = NOTESKIN:GetMetricA("ReceptorArrow", "InitCommand"), - MissCommand = NOTESKIN:GetMetricA("ReceptorArrow", "MissCommand"), - NoneCommand = NOTESKIN:GetMetricA("ReceptorArrow", "NoneCommand"), - HitMineCommand = NOTESKIN:GetMetricA("ReceptorArrow", "HitMineCommand"), - W5Command = NOTESKIN:GetMetricA("ReceptorArrow", "W5Command"), - W4Command = NOTESKIN:GetMetricA("ReceptorArrow", "W4Command"), - W3Command = NOTESKIN:GetMetricA("ReceptorArrow", "W3Command"), - W2Command = NOTESKIN:GetMetricA("ReceptorArrow", "W2Command"), - W1Command = NOTESKIN:GetMetricA("ReceptorArrow", "W1Command") - } + { + InitCommand = NOTESKIN:GetMetricA("ReceptorArrow", "InitCommand"), + MissCommand = NOTESKIN:GetMetricA("ReceptorArrow", "MissCommand"), + NoneCommand = NOTESKIN:GetMetricA("ReceptorArrow", "NoneCommand"), + HitMineCommand = NOTESKIN:GetMetricA("ReceptorArrow", "HitMineCommand"), + W5Command = NOTESKIN:GetMetricA("ReceptorArrow", "W5Command"), + W4Command = NOTESKIN:GetMetricA("ReceptorArrow", "W4Command"), + W3Command = NOTESKIN:GetMetricA("ReceptorArrow", "W3Command"), + W2Command = NOTESKIN:GetMetricA("ReceptorArrow", "W2Command"), + W1Command = NOTESKIN:GetMetricA("ReceptorArrow", "W1Command") + } diff --git a/NoteSkins/dance/DivideByInf/_Down Hold Active 1x9.png b/NoteSkins/dance/DivideByInf/_Down Hold Active 1x9.png index 052eb22ca0..6e01d96a37 100644 Binary files a/NoteSkins/dance/DivideByInf/_Down Hold Active 1x9.png and b/NoteSkins/dance/DivideByInf/_Down Hold Active 1x9.png differ diff --git a/NoteSkins/dance/DivideByInf/_Down Tap Note 1x9.png b/NoteSkins/dance/DivideByInf/_Down Tap Note 1x9.png index e9bcea178a..428788798c 100644 Binary files a/NoteSkins/dance/DivideByInf/_Down Tap Note 1x9.png and b/NoteSkins/dance/DivideByInf/_Down Tap Note 1x9.png differ diff --git a/NoteSkins/dance/DivideByZero/_Down Go Receptor Go 2x1.png b/NoteSkins/dance/DivideByZero/_Down Go Receptor Go 2x1.png index 4b1056713d..5814067896 100644 Binary files a/NoteSkins/dance/DivideByZero/_Down Go Receptor Go 2x1.png and b/NoteSkins/dance/DivideByZero/_Down Go Receptor Go 2x1.png differ diff --git a/NoteSkins/dance/DivideByZero/_Down Hold Active 1x8.png b/NoteSkins/dance/DivideByZero/_Down Hold Active 1x8.png index 1a0fad6e07..157dbef1b9 100644 Binary files a/NoteSkins/dance/DivideByZero/_Down Hold Active 1x8.png and b/NoteSkins/dance/DivideByZero/_Down Hold Active 1x8.png differ diff --git a/NoteSkins/dance/DivideByZero/_Down Tap Note 1x8.png b/NoteSkins/dance/DivideByZero/_Down Tap Note 1x8.png index 38b0555ad0..b83417058e 100644 Binary files a/NoteSkins/dance/DivideByZero/_Down Tap Note 1x8.png and b/NoteSkins/dance/DivideByZero/_Down Tap Note 1x8.png differ diff --git a/NoteSkins/dance/DivideByZeroHollow/_Down Hold Active 1x8.png b/NoteSkins/dance/DivideByZeroHollow/_Down Hold Active 1x8.png index 17acc19bc4..fbb3d80793 100644 Binary files a/NoteSkins/dance/DivideByZeroHollow/_Down Hold Active 1x8.png and b/NoteSkins/dance/DivideByZeroHollow/_Down Hold Active 1x8.png differ diff --git a/NoteSkins/dance/DivideByZeroHollow/_Down Tap Note 1x8.png b/NoteSkins/dance/DivideByZeroHollow/_Down Tap Note 1x8.png index 17acc19bc4..fbb3d80793 100644 Binary files a/NoteSkins/dance/DivideByZeroHollow/_Down Tap Note 1x8.png and b/NoteSkins/dance/DivideByZeroHollow/_Down Tap Note 1x8.png differ diff --git a/NoteSkins/dance/DivideByZero_halved/_Down Hold Active 1x8.png b/NoteSkins/dance/DivideByZero_halved/_Down Hold Active 1x8.png index 1d60a48890..cf1384a64f 100644 Binary files a/NoteSkins/dance/DivideByZero_halved/_Down Hold Active 1x8.png and b/NoteSkins/dance/DivideByZero_halved/_Down Hold Active 1x8.png differ diff --git a/NoteSkins/dance/DivideByZero_halved/_Down Tap Note 1x8.png b/NoteSkins/dance/DivideByZero_halved/_Down Tap Note 1x8.png index e7b9f8a112..93ceaded34 100644 Binary files a/NoteSkins/dance/DivideByZero_halved/_Down Tap Note 1x8.png and b/NoteSkins/dance/DivideByZero_halved/_Down Tap Note 1x8.png differ diff --git a/NoteSkins/dance/DivideByZero_semihalved/_Down Hold Active 1x8.png b/NoteSkins/dance/DivideByZero_semihalved/_Down Hold Active 1x8.png index 389e9e0e4e..6dd0971007 100644 Binary files a/NoteSkins/dance/DivideByZero_semihalved/_Down Hold Active 1x8.png and b/NoteSkins/dance/DivideByZero_semihalved/_Down Hold Active 1x8.png differ diff --git a/NoteSkins/dance/DivideByZero_semihalved/_Down Tap Note 1x8.png b/NoteSkins/dance/DivideByZero_semihalved/_Down Tap Note 1x8.png index 7be8d0c890..595779cc32 100644 Binary files a/NoteSkins/dance/DivideByZero_semihalved/_Down Tap Note 1x8.png and b/NoteSkins/dance/DivideByZero_semihalved/_Down Tap Note 1x8.png differ diff --git a/NoteSkins/dance/SubtractByZero/_Down Go Receptor Go 2x1.png b/NoteSkins/dance/SubtractByZero/_Down Go Receptor Go 2x1.png index 51a92bbd9d..86a5101032 100644 Binary files a/NoteSkins/dance/SubtractByZero/_Down Go Receptor Go 2x1.png and b/NoteSkins/dance/SubtractByZero/_Down Go Receptor Go 2x1.png differ diff --git a/NoteSkins/dance/SubtractByZero/_Down Hold Active 1x8.png b/NoteSkins/dance/SubtractByZero/_Down Hold Active 1x8.png index c830c2a9da..adbecfea87 100644 Binary files a/NoteSkins/dance/SubtractByZero/_Down Hold Active 1x8.png and b/NoteSkins/dance/SubtractByZero/_Down Hold Active 1x8.png differ diff --git a/NoteSkins/dance/SubtractByZero/_Down Tap Note 1x8.png b/NoteSkins/dance/SubtractByZero/_Down Tap Note 1x8.png index c830c2a9da..adbecfea87 100644 Binary files a/NoteSkins/dance/SubtractByZero/_Down Tap Note 1x8.png and b/NoteSkins/dance/SubtractByZero/_Down Tap Note 1x8.png differ diff --git a/NoteSkins/dance/default/lift.png b/NoteSkins/dance/default/lift.png index a74f2d827b..9b2650acdf 100644 Binary files a/NoteSkins/dance/default/lift.png and b/NoteSkins/dance/default/lift.png differ diff --git a/NoteSkins/kb7/default/Key Go Receptor.png b/NoteSkins/kb7/default/Key Go Receptor.png index 1161fbe8a3..b649c71bc6 100644 Binary files a/NoteSkins/kb7/default/Key Go Receptor.png and b/NoteSkins/kb7/default/Key Go Receptor.png differ diff --git a/NoteSkins/kb7/default/Space Hold Body active.png b/NoteSkins/kb7/default/Space Hold Body active.png index 67ea16e252..6f95ab64c0 100644 Binary files a/NoteSkins/kb7/default/Space Hold Body active.png and b/NoteSkins/kb7/default/Space Hold Body active.png differ diff --git a/NoteSkins/kb7/default/Space Hold Body inactive.png b/NoteSkins/kb7/default/Space Hold Body inactive.png index 23fdb7251d..0c313181c3 100644 Binary files a/NoteSkins/kb7/default/Space Hold Body inactive.png and b/NoteSkins/kb7/default/Space Hold Body inactive.png differ diff --git a/NoteSkins/kb7/default/Space Roll Body active.png b/NoteSkins/kb7/default/Space Roll Body active.png index 9565955df7..0d2312e357 100644 Binary files a/NoteSkins/kb7/default/Space Roll Body active.png and b/NoteSkins/kb7/default/Space Roll Body active.png differ diff --git a/NoteSkins/kb7/default/Space Roll Body inactive.png b/NoteSkins/kb7/default/Space Roll Body inactive.png index 8068351bcc..39c5c83434 100644 Binary files a/NoteSkins/kb7/default/Space Roll Body inactive.png and b/NoteSkins/kb7/default/Space Roll Body inactive.png differ diff --git a/NoteSkins/kb7/orbital/Bar Go Receptor.png b/NoteSkins/kb7/orbital/Bar Go Receptor.png index 1161fbe8a3..b649c71bc6 100644 Binary files a/NoteSkins/kb7/orbital/Bar Go Receptor.png and b/NoteSkins/kb7/orbital/Bar Go Receptor.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/Blue Hold Body active.png b/NoteSkins/kb7/retrobar-iidx/Blue Hold Body active.png index cb0c83ff46..4ed73a675a 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/Blue Hold Body active.png and b/NoteSkins/kb7/retrobar-iidx/Blue Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/Blue Hold Body inactive.png b/NoteSkins/kb7/retrobar-iidx/Blue Hold Body inactive.png index 6a4a2e439f..70b07ad2f0 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/Blue Hold Body inactive.png and b/NoteSkins/kb7/retrobar-iidx/Blue Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap active.png b/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap active.png index 7802c97ba7..3b90590e2f 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap active.png and b/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap inactive.png b/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap inactive.png index f8b405530c..9d8fcf6ce3 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap inactive.png and b/NoteSkins/kb7/retrobar-iidx/Blue Hold BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/Blue tap fake.png b/NoteSkins/kb7/retrobar-iidx/Blue tap fake.png index f6f968123e..9a2833afc2 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/Blue tap fake.png and b/NoteSkins/kb7/retrobar-iidx/Blue tap fake.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_Roll Body active.png b/NoteSkins/kb7/retrobar-iidx/_Roll Body active.png index 23a6d79c68..7338f7757a 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_Roll Body active.png and b/NoteSkins/kb7/retrobar-iidx/_Roll Body active.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_Roll Body inactive.png b/NoteSkins/kb7/retrobar-iidx/_Roll Body inactive.png index e1fa513fff..80802e8063 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_Roll Body inactive.png and b/NoteSkins/kb7/retrobar-iidx/_Roll Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap active.png b/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap active.png index cad2a4cc10..e39e1b6f7b 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap active.png and b/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap inactive.png b/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap inactive.png index 531bd90b93..c5420a90a6 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap inactive.png and b/NoteSkins/kb7/retrobar-iidx/_Roll BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_Tap explosion bright.png b/NoteSkins/kb7/retrobar-iidx/_Tap explosion bright.png index 5f531344eb..afb3fd81db 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_Tap explosion bright.png and b/NoteSkins/kb7/retrobar-iidx/_Tap explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_bar hold explosion bright.png b/NoteSkins/kb7/retrobar-iidx/_bar hold explosion bright.png index 3dce74eefa..542561e214 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_bar hold explosion bright.png and b/NoteSkins/kb7/retrobar-iidx/_bar hold explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_bar hold explosion dim.png b/NoteSkins/kb7/retrobar-iidx/_bar hold explosion dim.png index 2a1bce3589..8f0605bbf4 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_bar hold explosion dim.png and b/NoteSkins/kb7/retrobar-iidx/_bar hold explosion dim.png differ diff --git a/NoteSkins/kb7/retrobar-iidx/_bar mine 4x1.png b/NoteSkins/kb7/retrobar-iidx/_bar mine 4x1.png index 4b32410b2f..cd7fd1b065 100644 Binary files a/NoteSkins/kb7/retrobar-iidx/_bar mine 4x1.png and b/NoteSkins/kb7/retrobar-iidx/_bar mine 4x1.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body active.png b/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body active.png index cb0c83ff46..4ed73a675a 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body active.png and b/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body inactive.png b/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body inactive.png index 6a4a2e439f..70b07ad2f0 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body inactive.png and b/NoteSkins/kb7/retrobar-o2jam/Blue Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap active.png b/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap active.png index 7802c97ba7..3b90590e2f 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap active.png and b/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap inactive.png b/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap inactive.png index f8b405530c..9d8fcf6ce3 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap inactive.png and b/NoteSkins/kb7/retrobar-o2jam/Blue Hold BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Blue tap fake.png b/NoteSkins/kb7/retrobar-o2jam/Blue tap fake.png index f6f968123e..9a2833afc2 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Blue tap fake.png and b/NoteSkins/kb7/retrobar-o2jam/Blue tap fake.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body active.png b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body active.png index fcb4adfb1b..37e166b01c 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body active.png and b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body inactive.png b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body inactive.png index 8ccb4b6958..c3e0f26fd3 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body inactive.png and b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap active.png b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap active.png index ca1aaaaa4c..cc659957d0 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap active.png and b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap inactive.png b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap inactive.png index b52669b8a2..aaa6361589 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap inactive.png and b/NoteSkins/kb7/retrobar-o2jam/Yellow Hold BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/Yellow tap fake.png b/NoteSkins/kb7/retrobar-o2jam/Yellow tap fake.png index 7e7443d760..d83c692b61 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/Yellow tap fake.png and b/NoteSkins/kb7/retrobar-o2jam/Yellow tap fake.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_Roll Body active.png b/NoteSkins/kb7/retrobar-o2jam/_Roll Body active.png index 23a6d79c68..7338f7757a 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_Roll Body active.png and b/NoteSkins/kb7/retrobar-o2jam/_Roll Body active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_Roll Body inactive.png b/NoteSkins/kb7/retrobar-o2jam/_Roll Body inactive.png index e1fa513fff..80802e8063 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_Roll Body inactive.png and b/NoteSkins/kb7/retrobar-o2jam/_Roll Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap active.png b/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap active.png index cad2a4cc10..e39e1b6f7b 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap active.png and b/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap inactive.png b/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap inactive.png index 531bd90b93..c5420a90a6 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap inactive.png and b/NoteSkins/kb7/retrobar-o2jam/_Roll BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_Tap explosion bright.png b/NoteSkins/kb7/retrobar-o2jam/_Tap explosion bright.png index 5f531344eb..afb3fd81db 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_Tap explosion bright.png and b/NoteSkins/kb7/retrobar-o2jam/_Tap explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion bright.png b/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion bright.png index 3dce74eefa..542561e214 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion bright.png and b/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion dim.png b/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion dim.png index 2a1bce3589..8f0605bbf4 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion dim.png and b/NoteSkins/kb7/retrobar-o2jam/_bar hold explosion dim.png differ diff --git a/NoteSkins/kb7/retrobar-o2jam/_bar mine 4x1.png b/NoteSkins/kb7/retrobar-o2jam/_bar mine 4x1.png index 4b32410b2f..cd7fd1b065 100644 Binary files a/NoteSkins/kb7/retrobar-o2jam/_bar mine 4x1.png and b/NoteSkins/kb7/retrobar-o2jam/_bar mine 4x1.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Hold Body active.png b/NoteSkins/kb7/retrobar-razor/Bar Hold Body active.png index cb0c83ff46..4ed73a675a 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Hold Body active.png and b/NoteSkins/kb7/retrobar-razor/Bar Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Hold Body inactive.png b/NoteSkins/kb7/retrobar-razor/Bar Hold Body inactive.png index 6a4a2e439f..70b07ad2f0 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Hold Body inactive.png and b/NoteSkins/kb7/retrobar-razor/Bar Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll Body active.png b/NoteSkins/kb7/retrobar-razor/Bar Roll Body active.png index 23a6d79c68..7338f7757a 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll Body active.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll Body active.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll Body inactive.png b/NoteSkins/kb7/retrobar-razor/Bar Roll Body inactive.png index e1fa513fff..80802e8063 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll Body inactive.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap active.png b/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap active.png index 40407e208c..6a1c6366dc 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap active.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap inactive.png b/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap inactive.png index 25188bc297..3c32dcb766 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap inactive.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll Head active.png b/NoteSkins/kb7/retrobar-razor/Bar Roll Head active.png index 7a06a30da8..2a724ce6be 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll Head active.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll Head active.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Roll Head inactive.png b/NoteSkins/kb7/retrobar-razor/Bar Roll Head inactive.png index f8ae3ad1f0..077aa7b013 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Roll Head inactive.png and b/NoteSkins/kb7/retrobar-razor/Bar Roll Head inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor/Bar Tap Lift.png b/NoteSkins/kb7/retrobar-razor/Bar Tap Lift.png index 4b011b7803..e794b4a3d5 100644 Binary files a/NoteSkins/kb7/retrobar-razor/Bar Tap Lift.png and b/NoteSkins/kb7/retrobar-razor/Bar Tap Lift.png differ diff --git a/NoteSkins/kb7/retrobar-razor/_bar hold explosion bright.png b/NoteSkins/kb7/retrobar-razor/_bar hold explosion bright.png index 3dce74eefa..542561e214 100644 Binary files a/NoteSkins/kb7/retrobar-razor/_bar hold explosion bright.png and b/NoteSkins/kb7/retrobar-razor/_bar hold explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-razor/_bar hold explosion dim.png b/NoteSkins/kb7/retrobar-razor/_bar hold explosion dim.png index 2a1bce3589..8f0605bbf4 100644 Binary files a/NoteSkins/kb7/retrobar-razor/_bar hold explosion dim.png and b/NoteSkins/kb7/retrobar-razor/_bar hold explosion dim.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body active.png b/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body active.png index cb0c83ff46..4ed73a675a 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body active.png and b/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body inactive.png b/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body inactive.png index 6a4a2e439f..70b07ad2f0 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body inactive.png and b/NoteSkins/kb7/retrobar-razor_o2/Blue Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body active.png b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body active.png index fcb4adfb1b..37e166b01c 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body active.png and b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body inactive.png b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body inactive.png index 8ccb4b6958..c3e0f26fd3 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body inactive.png and b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold BottomCap inactive.png b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold BottomCap inactive.png index 9216fc4a5a..18c0273dcf 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold BottomCap inactive.png and b/NoteSkins/kb7/retrobar-razor_o2/Yellow Hold BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_Roll Body active.png b/NoteSkins/kb7/retrobar-razor_o2/_Roll Body active.png index 23a6d79c68..7338f7757a 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_Roll Body active.png and b/NoteSkins/kb7/retrobar-razor_o2/_Roll Body active.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_Roll Body inactive.png b/NoteSkins/kb7/retrobar-razor_o2/_Roll Body inactive.png index e1fa513fff..80802e8063 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_Roll Body inactive.png and b/NoteSkins/kb7/retrobar-razor_o2/_Roll Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap active.png b/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap active.png index 40407e208c..6a1c6366dc 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap active.png and b/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap inactive.png b/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap inactive.png index 25188bc297..3c32dcb766 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap inactive.png and b/NoteSkins/kb7/retrobar-razor_o2/_Roll BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion bright.png b/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion bright.png index 3dce74eefa..542561e214 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion bright.png and b/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion dim.png b/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion dim.png index 2a1bce3589..8f0605bbf4 100644 Binary files a/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion dim.png and b/NoteSkins/kb7/retrobar-razor_o2/_bar hold explosion dim.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold Body active.png b/NoteSkins/kb7/retrobar/Bar Hold Body active.png index cb0c83ff46..4ed73a675a 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold Body active.png and b/NoteSkins/kb7/retrobar/Bar Hold Body active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold Body inactive.png b/NoteSkins/kb7/retrobar/Bar Hold Body inactive.png index 6a4a2e439f..70b07ad2f0 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold Body inactive.png and b/NoteSkins/kb7/retrobar/Bar Hold Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold BottomCap active.png b/NoteSkins/kb7/retrobar/Bar Hold BottomCap active.png index 7802c97ba7..3b90590e2f 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold BottomCap active.png and b/NoteSkins/kb7/retrobar/Bar Hold BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold BottomCap inactive.png b/NoteSkins/kb7/retrobar/Bar Hold BottomCap inactive.png index f8b405530c..9d8fcf6ce3 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold BottomCap inactive.png and b/NoteSkins/kb7/retrobar/Bar Hold BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold Head active.png b/NoteSkins/kb7/retrobar/Bar Hold Head active.png index b8c7572911..dd769b369a 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold Head active.png and b/NoteSkins/kb7/retrobar/Bar Hold Head active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Hold Head inactive.png b/NoteSkins/kb7/retrobar/Bar Hold Head inactive.png index 5bd78b9204..42f4e050c7 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Hold Head inactive.png and b/NoteSkins/kb7/retrobar/Bar Hold Head inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll Body active.png b/NoteSkins/kb7/retrobar/Bar Roll Body active.png index 23a6d79c68..7338f7757a 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll Body active.png and b/NoteSkins/kb7/retrobar/Bar Roll Body active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll Body inactive.png b/NoteSkins/kb7/retrobar/Bar Roll Body inactive.png index e1fa513fff..80802e8063 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll Body inactive.png and b/NoteSkins/kb7/retrobar/Bar Roll Body inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll BottomCap active.png b/NoteSkins/kb7/retrobar/Bar Roll BottomCap active.png index cad2a4cc10..e39e1b6f7b 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll BottomCap active.png and b/NoteSkins/kb7/retrobar/Bar Roll BottomCap active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll BottomCap inactive.png b/NoteSkins/kb7/retrobar/Bar Roll BottomCap inactive.png index 531bd90b93..c5420a90a6 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll BottomCap inactive.png and b/NoteSkins/kb7/retrobar/Bar Roll BottomCap inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll Head active.png b/NoteSkins/kb7/retrobar/Bar Roll Head active.png index 687c091e4a..397027dfcd 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll Head active.png and b/NoteSkins/kb7/retrobar/Bar Roll Head active.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Roll Head inactive.png b/NoteSkins/kb7/retrobar/Bar Roll Head inactive.png index 717749e294..5444952a90 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Roll Head inactive.png and b/NoteSkins/kb7/retrobar/Bar Roll Head inactive.png differ diff --git a/NoteSkins/kb7/retrobar/Bar Tap explosion bright.png b/NoteSkins/kb7/retrobar/Bar Tap explosion bright.png index 5f531344eb..afb3fd81db 100644 Binary files a/NoteSkins/kb7/retrobar/Bar Tap explosion bright.png and b/NoteSkins/kb7/retrobar/Bar Tap explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar/_bar hold explosion bright.png b/NoteSkins/kb7/retrobar/_bar hold explosion bright.png index 3dce74eefa..542561e214 100644 Binary files a/NoteSkins/kb7/retrobar/_bar hold explosion bright.png and b/NoteSkins/kb7/retrobar/_bar hold explosion bright.png differ diff --git a/NoteSkins/kb7/retrobar/_bar hold explosion dim.png b/NoteSkins/kb7/retrobar/_bar hold explosion dim.png index 2a1bce3589..8f0605bbf4 100644 Binary files a/NoteSkins/kb7/retrobar/_bar hold explosion dim.png and b/NoteSkins/kb7/retrobar/_bar hold explosion dim.png differ diff --git a/NoteSkins/kb7/retrobar/_bar mine 4x1.png b/NoteSkins/kb7/retrobar/_bar mine 4x1.png index 4b32410b2f..cd7fd1b065 100644 Binary files a/NoteSkins/kb7/retrobar/_bar mine 4x1.png and b/NoteSkins/kb7/retrobar/_bar mine 4x1.png differ diff --git a/NoteSkins/pump/cmd-routine-p1/Center Hold BottomCap active (doubleres) 6x1.png b/NoteSkins/pump/cmd-routine-p1/Center Hold BottomCap active (doubleres) 6x1.png index 9b0f8bd676..b60ec4889a 100644 Binary files a/NoteSkins/pump/cmd-routine-p1/Center Hold BottomCap active (doubleres) 6x1.png and b/NoteSkins/pump/cmd-routine-p1/Center Hold BottomCap active (doubleres) 6x1.png differ diff --git a/NoteSkins/pump/cmd-routine-p1/Center Tap Note (doubleres) 3x2.png b/NoteSkins/pump/cmd-routine-p1/Center Tap Note (doubleres) 3x2.png index 9d7a097144..830bef30ea 100644 Binary files a/NoteSkins/pump/cmd-routine-p1/Center Tap Note (doubleres) 3x2.png and b/NoteSkins/pump/cmd-routine-p1/Center Tap Note (doubleres) 3x2.png differ diff --git a/NoteSkins/pump/cmd-routine-p2/Center Hold BottomCap active (doubleres) 6x1.png b/NoteSkins/pump/cmd-routine-p2/Center Hold BottomCap active (doubleres) 6x1.png index e582f6cf9e..8bd3ced27f 100644 Binary files a/NoteSkins/pump/cmd-routine-p2/Center Hold BottomCap active (doubleres) 6x1.png and b/NoteSkins/pump/cmd-routine-p2/Center Hold BottomCap active (doubleres) 6x1.png differ diff --git a/NoteSkins/pump/cmd-routine-p2/Center Tap Note (doubleres) 3x2.png b/NoteSkins/pump/cmd-routine-p2/Center Tap Note (doubleres) 3x2.png index d5d2cc7a53..0da49d4422 100644 Binary files a/NoteSkins/pump/cmd-routine-p2/Center Tap Note (doubleres) 3x2.png and b/NoteSkins/pump/cmd-routine-p2/Center Tap Note (doubleres) 3x2.png differ diff --git a/NoteSkins/pump/cmd/Center Hold BottomCap active (doubleres) 6x1.png b/NoteSkins/pump/cmd/Center Hold BottomCap active (doubleres) 6x1.png index 0308639bdc..49dfbbda29 100644 Binary files a/NoteSkins/pump/cmd/Center Hold BottomCap active (doubleres) 6x1.png and b/NoteSkins/pump/cmd/Center Hold BottomCap active (doubleres) 6x1.png differ diff --git a/NoteSkins/pump/default/Center Hold Body Active 6x1.png b/NoteSkins/pump/default/Center Hold Body Active 6x1.png index cf71ed154d..b43348d5bc 100644 Binary files a/NoteSkins/pump/default/Center Hold Body Active 6x1.png and b/NoteSkins/pump/default/Center Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/default/DownLeft Hold Body Active 6x1.png b/NoteSkins/pump/default/DownLeft Hold Body Active 6x1.png index 6403fa1c7c..74f39a4ebd 100644 Binary files a/NoteSkins/pump/default/DownLeft Hold Body Active 6x1.png and b/NoteSkins/pump/default/DownLeft Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/default/DownRight Hold Body Active 6x1.png b/NoteSkins/pump/default/DownRight Hold Body Active 6x1.png index 7a1c5af51c..42efe5270d 100644 Binary files a/NoteSkins/pump/default/DownRight Hold Body Active 6x1.png and b/NoteSkins/pump/default/DownRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/default/NoteSkin.lua b/NoteSkins/pump/default/NoteSkin.lua index 1ca20ee000..1ac6c54e02 100644 --- a/NoteSkins/pump/default/NoteSkin.lua +++ b/NoteSkins/pump/default/NoteSkin.lua @@ -4,8 +4,6 @@ local Noteskin = {} Noteskin.bBlanks = { --["element"] = true|false; ["Hold Tail Active"] = true, - ["Hold Tail Active"] = true, - ["Roll Tail Inactive"] = true, ["Roll Tail Inactive"] = true } Noteskin.PartsToRotate = { diff --git a/NoteSkins/pump/default/UpLeft Hold Body Active 6x1.png b/NoteSkins/pump/default/UpLeft Hold Body Active 6x1.png index 1fe66d03e5..1b5bd054b8 100644 Binary files a/NoteSkins/pump/default/UpLeft Hold Body Active 6x1.png and b/NoteSkins/pump/default/UpLeft Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/default/UpRight Hold Body Active 6x1.png b/NoteSkins/pump/default/UpRight Hold Body Active 6x1.png index 9932324f92..06a547aba5 100644 Binary files a/NoteSkins/pump/default/UpRight Hold Body Active 6x1.png and b/NoteSkins/pump/default/UpRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/default/_flash (doubleres).png b/NoteSkins/pump/default/_flash (doubleres).png index 1936c7e2e4..823561c7ec 100644 Binary files a/NoteSkins/pump/default/_flash (doubleres).png and b/NoteSkins/pump/default/_flash (doubleres).png differ diff --git a/NoteSkins/pump/delta-note/Center Hold Body Active.png b/NoteSkins/pump/delta-note/Center Hold Body Active.png index bb8409011c..f813bf5e1b 100644 Binary files a/NoteSkins/pump/delta-note/Center Hold Body Active.png and b/NoteSkins/pump/delta-note/Center Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-note/Center Hold BottomCap Active.png b/NoteSkins/pump/delta-note/Center Hold BottomCap Active.png index 300b93e579..0584b55d45 100644 Binary files a/NoteSkins/pump/delta-note/Center Hold BottomCap Active.png and b/NoteSkins/pump/delta-note/Center Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-note/DownLeft Hold Body Active.png b/NoteSkins/pump/delta-note/DownLeft Hold Body Active.png index b78d95ee28..15ec990a24 100644 Binary files a/NoteSkins/pump/delta-note/DownLeft Hold Body Active.png and b/NoteSkins/pump/delta-note/DownLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-note/DownLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-note/DownLeft Hold BottomCap Active.png index fe38c0906d..1f77349d88 100644 Binary files a/NoteSkins/pump/delta-note/DownLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-note/DownLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-note/DownRight Hold Body Active.png b/NoteSkins/pump/delta-note/DownRight Hold Body Active.png index 09042338f4..9259573d06 100644 Binary files a/NoteSkins/pump/delta-note/DownRight Hold Body Active.png and b/NoteSkins/pump/delta-note/DownRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-note/DownRight Hold BottomCap Active.png b/NoteSkins/pump/delta-note/DownRight Hold BottomCap Active.png index cc169ba249..1f08f5d32d 100644 Binary files a/NoteSkins/pump/delta-note/DownRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-note/DownRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-note/UpLeft Hold Body Active.png b/NoteSkins/pump/delta-note/UpLeft Hold Body Active.png index 035a2f1eb4..2d888806b5 100644 Binary files a/NoteSkins/pump/delta-note/UpLeft Hold Body Active.png and b/NoteSkins/pump/delta-note/UpLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-note/UpLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-note/UpLeft Hold BottomCap Active.png index f5c6c24dff..4dd6893131 100644 Binary files a/NoteSkins/pump/delta-note/UpLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-note/UpLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-note/UpRight Hold Body Active.png b/NoteSkins/pump/delta-note/UpRight Hold Body Active.png index d7c57b60eb..834f2a47c2 100644 Binary files a/NoteSkins/pump/delta-note/UpRight Hold Body Active.png and b/NoteSkins/pump/delta-note/UpRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-note/UpRight Hold BottomCap Active.png b/NoteSkins/pump/delta-note/UpRight Hold BottomCap Active.png index 5c21b23015..b00ce13854 100644 Binary files a/NoteSkins/pump/delta-note/UpRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-note/UpRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/Center Hold Body Active.png b/NoteSkins/pump/delta-routine-p1/Center Hold Body Active.png index 240663359f..735fe759f7 100644 Binary files a/NoteSkins/pump/delta-routine-p1/Center Hold Body Active.png and b/NoteSkins/pump/delta-routine-p1/Center Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/Center Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p1/Center Hold BottomCap Active.png index f84bf2d2cd..9125a50a37 100644 Binary files a/NoteSkins/pump/delta-routine-p1/Center Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p1/Center Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/DownLeft Hold Body Active.png b/NoteSkins/pump/delta-routine-p1/DownLeft Hold Body Active.png index 8ccfb85da2..f644a7de1c 100644 Binary files a/NoteSkins/pump/delta-routine-p1/DownLeft Hold Body Active.png and b/NoteSkins/pump/delta-routine-p1/DownLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/DownLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p1/DownLeft Hold BottomCap Active.png index 1781a22b96..e2518bf6ce 100644 Binary files a/NoteSkins/pump/delta-routine-p1/DownLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p1/DownLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/DownRight Hold Body Active.png b/NoteSkins/pump/delta-routine-p1/DownRight Hold Body Active.png index fb7746a355..fff27e97d3 100644 Binary files a/NoteSkins/pump/delta-routine-p1/DownRight Hold Body Active.png and b/NoteSkins/pump/delta-routine-p1/DownRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/DownRight Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p1/DownRight Hold BottomCap Active.png index beab79eb50..f8d8aed9ca 100644 Binary files a/NoteSkins/pump/delta-routine-p1/DownRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p1/DownRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/UpLeft Hold Body Active.png b/NoteSkins/pump/delta-routine-p1/UpLeft Hold Body Active.png index 971efaa18f..1dd09bcb1d 100644 Binary files a/NoteSkins/pump/delta-routine-p1/UpLeft Hold Body Active.png and b/NoteSkins/pump/delta-routine-p1/UpLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/UpLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p1/UpLeft Hold BottomCap Active.png index 0728662e42..4c5784eaae 100644 Binary files a/NoteSkins/pump/delta-routine-p1/UpLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p1/UpLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/UpRight Hold Body Active.png b/NoteSkins/pump/delta-routine-p1/UpRight Hold Body Active.png index 17d79fea1b..da97e3664b 100644 Binary files a/NoteSkins/pump/delta-routine-p1/UpRight Hold Body Active.png and b/NoteSkins/pump/delta-routine-p1/UpRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p1/UpRight Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p1/UpRight Hold BottomCap Active.png index 87de47bcbf..3cad61e30f 100644 Binary files a/NoteSkins/pump/delta-routine-p1/UpRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p1/UpRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/Center Hold Body Active.png b/NoteSkins/pump/delta-routine-p2/Center Hold Body Active.png index c6b5200a4d..02a7e08cc6 100644 Binary files a/NoteSkins/pump/delta-routine-p2/Center Hold Body Active.png and b/NoteSkins/pump/delta-routine-p2/Center Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/Center Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p2/Center Hold BottomCap Active.png index c3c91d7a8a..135279fb5e 100644 Binary files a/NoteSkins/pump/delta-routine-p2/Center Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p2/Center Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/DownLeft Hold Body Active.png b/NoteSkins/pump/delta-routine-p2/DownLeft Hold Body Active.png index dfe4915ecf..29dbf5601e 100644 Binary files a/NoteSkins/pump/delta-routine-p2/DownLeft Hold Body Active.png and b/NoteSkins/pump/delta-routine-p2/DownLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/DownLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p2/DownLeft Hold BottomCap Active.png index a30d4ccf3c..a557217b0c 100644 Binary files a/NoteSkins/pump/delta-routine-p2/DownLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p2/DownLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/DownRight Hold Body Active.png b/NoteSkins/pump/delta-routine-p2/DownRight Hold Body Active.png index 5f6778dd18..920485ed03 100644 Binary files a/NoteSkins/pump/delta-routine-p2/DownRight Hold Body Active.png and b/NoteSkins/pump/delta-routine-p2/DownRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/DownRight Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p2/DownRight Hold BottomCap Active.png index 58d7e4304e..e14f0b2bdc 100644 Binary files a/NoteSkins/pump/delta-routine-p2/DownRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p2/DownRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/UpLeft Hold Body Active.png b/NoteSkins/pump/delta-routine-p2/UpLeft Hold Body Active.png index 5a6ebff24a..3ee447e24a 100644 Binary files a/NoteSkins/pump/delta-routine-p2/UpLeft Hold Body Active.png and b/NoteSkins/pump/delta-routine-p2/UpLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/UpLeft Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p2/UpLeft Hold BottomCap Active.png index ecf60d4004..5d91e66b41 100644 Binary files a/NoteSkins/pump/delta-routine-p2/UpLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p2/UpLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/UpRight Hold Body Active.png b/NoteSkins/pump/delta-routine-p2/UpRight Hold Body Active.png index 073edcc7cc..c2fe6d4cd6 100644 Binary files a/NoteSkins/pump/delta-routine-p2/UpRight Hold Body Active.png and b/NoteSkins/pump/delta-routine-p2/UpRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta-routine-p2/UpRight Hold BottomCap Active.png b/NoteSkins/pump/delta-routine-p2/UpRight Hold BottomCap Active.png index 7129ae47d7..a5e8147041 100644 Binary files a/NoteSkins/pump/delta-routine-p2/UpRight Hold BottomCap Active.png and b/NoteSkins/pump/delta-routine-p2/UpRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta/Center Hold Body Active.png b/NoteSkins/pump/delta/Center Hold Body Active.png index ff8fc16a4d..249807a8ff 100644 Binary files a/NoteSkins/pump/delta/Center Hold Body Active.png and b/NoteSkins/pump/delta/Center Hold Body Active.png differ diff --git a/NoteSkins/pump/delta/Center border.png b/NoteSkins/pump/delta/Center border.png index 63abc5240d..f0ec1774e4 100644 Binary files a/NoteSkins/pump/delta/Center border.png and b/NoteSkins/pump/delta/Center border.png differ diff --git a/NoteSkins/pump/delta/Center_blend.png b/NoteSkins/pump/delta/Center_blend.png index 096d05329c..ef1dd1e27a 100644 Binary files a/NoteSkins/pump/delta/Center_blend.png and b/NoteSkins/pump/delta/Center_blend.png differ diff --git a/NoteSkins/pump/delta/DownLeft Hold Body Active.png b/NoteSkins/pump/delta/DownLeft Hold Body Active.png index 6f188b6bfe..f75a6c54f4 100644 Binary files a/NoteSkins/pump/delta/DownLeft Hold Body Active.png and b/NoteSkins/pump/delta/DownLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta/DownLeft Hold BottomCap Active.png b/NoteSkins/pump/delta/DownLeft Hold BottomCap Active.png index e745e1272b..7270d17919 100644 Binary files a/NoteSkins/pump/delta/DownLeft Hold BottomCap Active.png and b/NoteSkins/pump/delta/DownLeft Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta/DownLeft border.png b/NoteSkins/pump/delta/DownLeft border.png index c550a1f41a..83af068c52 100644 Binary files a/NoteSkins/pump/delta/DownLeft border.png and b/NoteSkins/pump/delta/DownLeft border.png differ diff --git a/NoteSkins/pump/delta/DownLeft_fill.png b/NoteSkins/pump/delta/DownLeft_fill.png index b78f58b311..51eb16d918 100644 Binary files a/NoteSkins/pump/delta/DownLeft_fill.png and b/NoteSkins/pump/delta/DownLeft_fill.png differ diff --git a/NoteSkins/pump/delta/DownRight Hold Body Active.png b/NoteSkins/pump/delta/DownRight Hold Body Active.png index 3c374ad8d6..ed37442816 100644 Binary files a/NoteSkins/pump/delta/DownRight Hold Body Active.png and b/NoteSkins/pump/delta/DownRight Hold Body Active.png differ diff --git a/NoteSkins/pump/delta/DownRight Hold BottomCap Active.png b/NoteSkins/pump/delta/DownRight Hold BottomCap Active.png index 932f50ad23..5dcaa3a1f6 100644 Binary files a/NoteSkins/pump/delta/DownRight Hold BottomCap Active.png and b/NoteSkins/pump/delta/DownRight Hold BottomCap Active.png differ diff --git a/NoteSkins/pump/delta/NoteSkin.lua b/NoteSkins/pump/delta/NoteSkin.lua index ac379013f1..30609301e1 100644 --- a/NoteSkins/pump/delta/NoteSkin.lua +++ b/NoteSkins/pump/delta/NoteSkin.lua @@ -4,8 +4,6 @@ local Noteskin = {} Noteskin.bBlanks = { --["element"] = true|false; ["Hold Tail Active"] = true, - ["Hold Tail Active"] = true, - ["Roll Tail Inactive"] = true, ["Roll Tail Inactive"] = true } Noteskin.PartsToRotate = { diff --git a/NoteSkins/pump/delta/UpLeft Hold Body Active.png b/NoteSkins/pump/delta/UpLeft Hold Body Active.png index 4f535a4746..4d08451c02 100644 Binary files a/NoteSkins/pump/delta/UpLeft Hold Body Active.png and b/NoteSkins/pump/delta/UpLeft Hold Body Active.png differ diff --git a/NoteSkins/pump/delta/UpLeft border.png b/NoteSkins/pump/delta/UpLeft border.png index 3cd156cde8..f5499dc980 100644 Binary files a/NoteSkins/pump/delta/UpLeft border.png and b/NoteSkins/pump/delta/UpLeft border.png differ diff --git a/NoteSkins/pump/delta/UpLeft_fill.png b/NoteSkins/pump/delta/UpLeft_fill.png index 0b9d3d178a..d686b05157 100644 Binary files a/NoteSkins/pump/delta/UpLeft_fill.png and b/NoteSkins/pump/delta/UpLeft_fill.png differ diff --git a/NoteSkins/pump/delta/UpRight Hold Body Active.png b/NoteSkins/pump/delta/UpRight Hold Body Active.png index 7119ccbd13..e169f7069d 100644 Binary files a/NoteSkins/pump/delta/UpRight Hold Body Active.png and b/NoteSkins/pump/delta/UpRight Hold Body Active.png differ diff --git a/NoteSkins/pump/frame5p/Center Hold Body active (doubleres).png b/NoteSkins/pump/frame5p/Center Hold Body active (doubleres).png index 8d3d5cb2ed..8e6e5f00d6 100644 Binary files a/NoteSkins/pump/frame5p/Center Hold Body active (doubleres).png and b/NoteSkins/pump/frame5p/Center Hold Body active (doubleres).png differ diff --git a/NoteSkins/pump/frame5p/DownLeft Hold Body active (doubleres).png b/NoteSkins/pump/frame5p/DownLeft Hold Body active (doubleres).png index b74447ada9..1d97993090 100644 Binary files a/NoteSkins/pump/frame5p/DownLeft Hold Body active (doubleres).png and b/NoteSkins/pump/frame5p/DownLeft Hold Body active (doubleres).png differ diff --git a/NoteSkins/pump/frame5p/DownRight Hold Body active (doubleres).png b/NoteSkins/pump/frame5p/DownRight Hold Body active (doubleres).png index 5d7b39b0ee..e0f3f4d4e3 100644 Binary files a/NoteSkins/pump/frame5p/DownRight Hold Body active (doubleres).png and b/NoteSkins/pump/frame5p/DownRight Hold Body active (doubleres).png differ diff --git a/NoteSkins/pump/frame5p/UpLeft Hold Body active (doubleres).png b/NoteSkins/pump/frame5p/UpLeft Hold Body active (doubleres).png index b1d752c862..3852cb0376 100644 Binary files a/NoteSkins/pump/frame5p/UpLeft Hold Body active (doubleres).png and b/NoteSkins/pump/frame5p/UpLeft Hold Body active (doubleres).png differ diff --git a/NoteSkins/pump/frame5p/UpRight Hold Body active (doubleres).png b/NoteSkins/pump/frame5p/UpRight Hold Body active (doubleres).png index b1d752c862..3852cb0376 100644 Binary files a/NoteSkins/pump/frame5p/UpRight Hold Body active (doubleres).png and b/NoteSkins/pump/frame5p/UpRight Hold Body active (doubleres).png differ diff --git a/NoteSkins/pump/newextra/Center Hold Body Active 6x1.png b/NoteSkins/pump/newextra/Center Hold Body Active 6x1.png index 114f8d319b..d0b7b508f9 100644 Binary files a/NoteSkins/pump/newextra/Center Hold Body Active 6x1.png and b/NoteSkins/pump/newextra/Center Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/newextra/Center Ready Receptor 1x3.png b/NoteSkins/pump/newextra/Center Ready Receptor 1x3.png index 470dcecfbe..5aa2c6b306 100644 Binary files a/NoteSkins/pump/newextra/Center Ready Receptor 1x3.png and b/NoteSkins/pump/newextra/Center Ready Receptor 1x3.png differ diff --git a/NoteSkins/pump/newextra/UpLeft Hold Body Active 6x1.png b/NoteSkins/pump/newextra/UpLeft Hold Body Active 6x1.png index 8b2779c312..de1369ef11 100644 Binary files a/NoteSkins/pump/newextra/UpLeft Hold Body Active 6x1.png and b/NoteSkins/pump/newextra/UpLeft Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/pad/Center Hold Body Active 6x1.png b/NoteSkins/pump/pad/Center Hold Body Active 6x1.png index 3406e5d263..47c081de9a 100644 Binary files a/NoteSkins/pump/pad/Center Hold Body Active 6x1.png and b/NoteSkins/pump/pad/Center Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/pad/Center Roll Head Active 3x2.png b/NoteSkins/pump/pad/Center Roll Head Active 3x2.png index b7492eabeb..f7c9a0b4ef 100644 Binary files a/NoteSkins/pump/pad/Center Roll Head Active 3x2.png and b/NoteSkins/pump/pad/Center Roll Head Active 3x2.png differ diff --git a/NoteSkins/pump/pad/DownRight Hold Body Active 6x1.png b/NoteSkins/pump/pad/DownRight Hold Body Active 6x1.png index 87d10cbdbe..7496d73257 100644 Binary files a/NoteSkins/pump/pad/DownRight Hold Body Active 6x1.png and b/NoteSkins/pump/pad/DownRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/pad/UpRight Hold Body Active 6x1.png b/NoteSkins/pump/pad/UpRight Hold Body Active 6x1.png index 5f4df0c07b..85dd5fc048 100644 Binary files a/NoteSkins/pump/pad/UpRight Hold Body Active 6x1.png and b/NoteSkins/pump/pad/UpRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/pad/UpRight Roll Head Active 3x2.png b/NoteSkins/pump/pad/UpRight Roll Head Active 3x2.png index ce39f2b9aa..cc3a500583 100644 Binary files a/NoteSkins/pump/pad/UpRight Roll Head Active 3x2.png and b/NoteSkins/pump/pad/UpRight Roll Head Active 3x2.png differ diff --git a/NoteSkins/pump/rhythm/DownLeft Hold Body Active 6x1.png b/NoteSkins/pump/rhythm/DownLeft Hold Body Active 6x1.png index 2342c45fc3..60ee4e6dad 100644 Binary files a/NoteSkins/pump/rhythm/DownLeft Hold Body Active 6x1.png and b/NoteSkins/pump/rhythm/DownLeft Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/rhythm/DownRight Hold Body Active 6x1.png b/NoteSkins/pump/rhythm/DownRight Hold Body Active 6x1.png index 5e3af4d641..73af8b32d3 100644 Binary files a/NoteSkins/pump/rhythm/DownRight Hold Body Active 6x1.png and b/NoteSkins/pump/rhythm/DownRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/rhythm/UpLeft Hold Body Active 6x1.png b/NoteSkins/pump/rhythm/UpLeft Hold Body Active 6x1.png index f178e92d75..ba8bc47298 100644 Binary files a/NoteSkins/pump/rhythm/UpLeft Hold Body Active 6x1.png and b/NoteSkins/pump/rhythm/UpLeft Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/rhythm/UpRight Hold Body Active 6x1.png b/NoteSkins/pump/rhythm/UpRight Hold Body Active 6x1.png index b720d56e4e..0fb43b8349 100644 Binary files a/NoteSkins/pump/rhythm/UpRight Hold Body Active 6x1.png and b/NoteSkins/pump/rhythm/UpRight Hold Body Active 6x1.png differ diff --git a/NoteSkins/pump/rhythm/_UpLeft Tap Note 6x8.png b/NoteSkins/pump/rhythm/_UpLeft Tap Note 6x8.png index 0511bf3a83..a91a924141 100644 Binary files a/NoteSkins/pump/rhythm/_UpLeft Tap Note 6x8.png and b/NoteSkins/pump/rhythm/_UpLeft Tap Note 6x8.png differ diff --git a/README.md b/README.md index 5310901986..54c7aaae12 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,13 @@ Etterna is a cross-platform rhythm game similar to [Dance Dance Revolution](http ### Windows and macOS -Head to the [Github Releases](https://github.com/etternagame/etterna/releases) page, and download the relevant file for your operating system. For Windows, run the installer, and you should be ready to go. For macOS, mount the DMG and copy the Etterna folder to a location of your choice. Run the executable, and you are ready to go. +Head to the [Github Releases](https://github.com/etternagame/etterna/releases) page, and download the relevant file for your operating system. For Windows, run the installer, and you should be ready to go. For macOS, first follow the below instructions. *After* doing them, mount the DMG and copy the Etterna folder to a location of your choice. Run the executable, and you are ready to go. ### macOS -macOS has protection software called Gatekeeper. It ensures only trusted applications (code-signed apps) can be run on your system. Since we are an open-source project, we don't have the means to code-sign Etterna. If you have any trouble when opening Etterna on your system, please try to control-click the app, choose Open from the menu, and in the dialog that appears, click Open. Enter your admin name and password when prompted, and it should allow you to run the game. +This macOS binary is not signed, so before it can be installed it must be de-quarantined by executing this command in the same directory (likely your downloads folder) as the Etterna dmg. + +`xattr -d com.apple.quarantine ./Etterna*.dmg` ### Linux diff --git a/Themes/Rebirth/BGAnimations/Screen in.redir b/Themes/Rebirth/BGAnimations/Screen in.redir new file mode 100644 index 0000000000..513980546d --- /dev/null +++ b/Themes/Rebirth/BGAnimations/Screen in.redir @@ -0,0 +1 @@ +_fadein \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/Screen out.redir b/Themes/Rebirth/BGAnimations/Screen out.redir new file mode 100644 index 0000000000..357d5528e1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/Screen out.redir @@ -0,0 +1 @@ +_fadefast \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenChatOverlay overlay.lua b/Themes/Rebirth/BGAnimations/ScreenChatOverlay overlay.lua new file mode 100644 index 0000000000..4e036aa8b0 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenChatOverlay overlay.lua @@ -0,0 +1,763 @@ +local width = THEME:GetMetric("ScreenChatOverlay","ChatWidth") +local height = THEME:GetMetric("ScreenChatOverlay","ChatHeight") +local maxlines = 20 +local lineNumber = 20 +local inputLineNumber = 2 +local tabHeight = 1 +local maxTabs = 5 +local x, y = 5, SCREEN_HEIGHT - height * (maxlines + inputLineNumber + tabHeight) +local moveY = 0 +local scale = 0.4 +local minimised = true +local typing = false +local typingText = "" +local transparency = 0.667 +local curmsgh = 0 +local closeTabSize = 10 +local tweentime = 0.25 +local tipshown = false +local getMainColor = getMainColor or function(e) if e == "positive" then return color("#9654FD") else return color("2E2E2E99") end end +local topbaroffset = capWideScale(6,0) +local Colors = { + background = getMainColor("tabs"), + input = color("#888888"), + activeInput = Brightness(getMainColor("positive"),0.2), + chatSent = ColorMultiplier(getMainColor("positive"),1.5), + output = color("#545454"), + bar = color("#666666"), + tab = color("#555555"), + activeTab = color("#999999") +} +local translated_info = { + WindowTitle = THEME:GetString("MultiPlayer", "ChatTitle"), + LobbyTab = THEME:GetString("MultiPlayer", "LobbyTabName"), + ServerTab = THEME:GetString("MultiPlayer", "ServerTabName"), + ToggleTip = THEME:GetString("MultiPlayer", "InsertTip") +} +local chats = {} +chats[0] = {} +chats[1] = {} +chats[2] = {} +chats[0][""] = {} +local tabs = {{0, ""}} +--chats[tabName][tabType] +--tabtype: 0=lobby, 1=room, 2=pm +local messages = chats[0][""] +local currentTabName = "" +local currentTabType = 0 + +function changeTab(tabName, tabType) + currentTabName = tabName + currentTabType = tabType + if not chats[tabType][tabName] then + local i = 1 + local done = false + while not done do + if not tabs[i] then + tabs[i] = {tabType, tabName} + done = true + end + end + chats[tabType][tabName] = {} + end + messages = chats[tabType][tabName] +end +local chat = Def.ActorFrame {} +local currentScreen +local show = true +local online = IsNetSMOnline() and IsSMOnlineLoggedIn(PLAYER_1) and NSMAN:IsETTP() + +chat.MinimiseMessageCommand = function(self) + self:decelerate(tweentime) + moveY = minimised and height * (maxlines + inputLineNumber + tabHeight - 1) or 0 + self:y(moveY) +end +local i = 0 +chat.InitCommand = function(self) + online = IsNetSMOnline() and IsSMOnlineLoggedIn(PLAYER_1) and NSMAN:IsETTP() + self:visible(false) + MESSAGEMAN:Broadcast("Minimise") +end +chat.BeginTextEntryMessageCommand = function(self) + if not minimised then + minimised = not minimised + MESSAGEMAN:Broadcast("Minimise") + end +end +local isGameplay +local isInSinglePlayer +chat.ScreenChangedMessageCommand = function(self) + local s = SCREENMAN:GetTopScreen() + if not s then + return + end + local oldScreen = currentScreen + currentScreen = s:GetName() + + -- prevent the chat from showing in singleplayer because it can be annoying + if + oldScreen ~= currentScreen and + (currentScreen == "ScreenSelectMusic" or currentScreen == "ScreenTitleMenu" or + currentScreen == "ScreenOptionsService" or currentScreen == "ScreenInit" or + currentScreen == "ScreenPackDownloader" or currentScreen == "ScreenBundleSelect") + then + isInSinglePlayer = true + end + if string.sub(currentScreen, 1, 9) == "ScreenNet" and currentScreen ~= "ScreenNetSelectProfile" then + isInSinglePlayer = false + end + + online = IsNetSMOnline() and IsSMOnlineLoggedIn(PLAYER_1) and NSMAN:IsETTP() + isGameplay = (currentScreen:find("Gameplay") ~= nil or currentScreen:find("StageInformation") ~= nil) + + if isGameplay or isInSinglePlayer then + self:visible(false) + show = false + typing = false + s:setTimeout( + function() + self:visible(false) + end, + 0.025 + ) + else + self:visible(online and not isInSinglePlayer) + show = true + end + if currentScreen == "ScreenNetSelectMusic" then + for i = 1, #tabs do + if tabs[i] and tabs[i][2] == NSMAN:GetCurrentRoomName() then + changeTab(tabs[i][2], tabs[i][1]) + end + end + end + MESSAGEMAN:Broadcast("UpdateChatOverlay") +end +chat.MultiplayerDisconnectionMessageCommand = function(self) + online = false + self:visible(false) + typing = false + MESSAGEMAN:Broadcast("UpdateChatOverlay") + chats = {} + chats[0] = {} + chats[1] = {} + chats[2] = {} + chats[0][""] = {} + tabs = {{0, ""}} + changeTab("", 0) + SCREENMAN:set_input_redirected("PlayerNumber_P1", false) +end + +local bg +chat[#chat + 1] = + Def.Quad { + Name = "Background", + InitCommand = function(self) + bg = self + self:diffuse(Colors.background) + self:diffusealpha(transparency) + self:stretchto(x, y + height, width + x, height * (maxlines + inputLineNumber + tabHeight) + y) + end +} +local minbar +chat[#chat + 1] = + Def.Quad { + Name = "Bar", + InitCommand = function(self) + minbar = self + self:diffuse(Colors.bar) + self:diffusealpha(transparency) + self:stretchto(width * 0.425 + x, y, width * 0.575 + x, height + y) + self:addx(topbaroffset) + end, + ChatMessageCommand = function(self, params) + if minimised and params.tab ~= "" + and not (params.msg:find("System:") and not params.msg:find("The room is now") + and not params.msg:find("Can't start") and not params.msg:find("room operator") + and not params.msg:find("You're not in a room") and not params.msg:find("Starting in")) then + self:linear(tweentime) + self:glowshift() + self:effectcolor1(Colors.chatSent) + self:effectcolor2(Colors.bar) + self:effectperiod(1) + end + end, + MinimiseMessageCommand = function(self) + if minimised then + self:linear(tweentime) + self:diffuse(Colors.bar):diffusealpha(transparency) + self:stopeffect() + else + self:linear(tweentime) + self:diffuse(Colors.bar):diffusealpha(0) + end + end, + StopEffectCommand = function(self) + self:stopeffect() + end, +} +chat[#chat + 1] = + LoadFont("Common Normal") .. + { + Name = "BarLabel", + InitCommand = function(self) + self:settext(translated_info["WindowTitle"]) + self:halign(0):valign(0.5) + self:zoom(0.5) + self:diffuse(color("#000000")) + self:visible(true) + self:xy(x + 3 + width * 0.425, y - 0.5 + height * 0.5) + self:addx(topbaroffset) + end, + MinimiseMessageCommand = function(self) + self:accelerate(tweentime):diffusealpha(minimised and 1 or 0) + end + } +chat[#chat + 1] = + LoadFont("Common Normal") .. + { + Name = "BarLabel2", + InitCommand = function(self) + self:settext("-") + self:halign(1):valign(0.5) + self:zoom(0.8) + self:diffuse(color("#000000")) + self:visible(true) + self:xy(x - 3 + width * 0.575, y - 0.5 + height * 0.5) + self:addx(topbaroffset) + end, + MinimiseMessageCommand = function(self) + self:settext(minimised and "+" or "-") + self:y(minimised and y - 1 + height * 0.5 or y - 2.5 + height * 0.5) + self:accelerate(tweentime):diffusealpha(minimised and 1 or 0) + end + } +chat[#chat + 1] = + LoadFont("Common Normal") .. + { + Name = "InsertShortcutTip", + InitCommand = function(self) + self:settext(translated_info["ToggleTip"]) + self:halign(0):valign(0.5) + self:zoom(0.5) + self:xy(x + 3 + width * 0.575, y - 0.5 + height * 0.5) + self:addx(topbaroffset) + self:diffusealpha(0) + self:maxwidth((width * 0.425 - 6 - topbaroffset) / 0.5) + self:shadowlength(1) + end, + MinimiseMessageCommand = function(self) + if not minimised and not tipshown then + tipshown = true + self:diffusealpha(1):sleep(3) + self:linear(0.5):diffusealpha(0) + end + end + } + +local chatWindow = + Def.ActorFrame { + InitCommand = function(self) + self:visible(true) + end, + ChatMessageCommand = function(self, params) + local msgs = chats[params.type][params.tab] + local newTab = false + if not msgs then + chats[params.type][params.tab] = {} + msgs = chats[params.type][params.tab] + tabs[#tabs + 1] = {params.type, params.tab} + newTab = true + end + msgs[#msgs + 1] = os.date("%X") .. params.msg + if msgs == messages or newTab then --if its the current tab + MESSAGEMAN:Broadcast("UpdateChatOverlay") + end + end +} +local chatbg +chatWindow[#chatWindow + 1] = + Def.Quad { + Name = "ChatWindow", + InitCommand = function(self) + chatbg = self + self:diffuse(Colors.output) + self:diffusealpha(transparency) + end, + UpdateChatOverlayMessageCommand = function(self) + self:stretchto(x, height * (1 + tabHeight) + y, width + x, height * (maxlines + tabHeight) + y) + curmsgh = 0 + MESSAGEMAN:Broadcast("UpdateChatOverlayMsgs") + end +} +chatWindow[#chatWindow + 1] = + Def.Quad { --masking quad, hides any text outside chatwindow + InitCommand = function(self) + self:stretchto(x, -SCREEN_HEIGHT, width + x, height * 2 + y) + self:zwrite(true):blend("BlendMode_NoEffect") + end, +} +chatWindow[#chatWindow + 1] = + LoadColorFont("Common Normal") .. + { + Name = "ChatText", + InitCommand = function(self) + self:settext("") + self:halign(0):valign(1) + self:vertspacing(0) + self:zoom(scale) + self:SetMaxLines(maxlines, 1) + self:wrapwidthpixels((width - 8) / scale) + self:xy(x + 4, y + height * (maxlines + tabHeight) - 4) + self:ztest(true) + end, + UpdateChatOverlayMsgsMessageCommand = function(self) + local t = "" + for i = lineNumber - 1, lineNumber - maxlines, -1 do + if messages[#messages - i] then + t = t .. messages[#messages - i] .. "\n" + end + end + self:settext(t) + end + } + +local tabWidth = width / maxTabs +for i = 0, maxTabs - 1 do + chatWindow[#chatWindow + 1] = + Def.ActorFrame { + Name = "Tab" .. i + 1, + UpdateChatOverlayMessageCommand = function(self) + self:visible(not (not tabs[i + 1])) + end, + Def.Quad { + InitCommand = function(self) + self:diffuse(Colors.tab) + self:diffusealpha(transparency) + end, + UpdateChatOverlayMessageCommand = function(self) + self:diffuse( + (tabs[i + 1] and currentTabName == tabs[i + 1][2] and currentTabType == tabs[i + 1][1]) and Colors.activeTab or + Colors.tab + ) + self:stretchto(x + tabWidth * i, y + height, x + tabWidth * (i + 1), y + height * (1 + tabHeight)) + end, + ChatMessageCommand = function(self, params) + if params.tab == self:GetParent():GetChild("TabName"):GetText() and params.tab ~= currentTabName + and not (params.msg:find("System:") and not params.msg:find("The room is now") + and not params.msg:find("Can't start") and not params.msg:find("room operator") + and not params.msg:find("You're not in a room") and not params.msg:find("Starting in")) then + self:decelerate(0.2):diffuse(Colors.chatSent) + end + end, + }, + Def.Quad { + InitCommand = function(self) + self:diffuse(Color.Black) + self:diffusealpha(transparency) + self:halign(0.5) + self:stretchto(x + tabWidth * (i + 1) - 1, y + height,x + tabWidth * (i + 1), y + height * (1 + tabHeight)) + end, + }, + LoadFont("Common Normal") .. + { + Name = "TabName", + InitCommand = function(self) + self:halign(0):valign(0.5) + self:maxwidth((tabWidth - 5) / scale) + self:zoom(scale) + self:diffuse(color("#000000")) + self:xy(x + tabWidth * i + 4 - 1.5, y + height * (1 + (tabHeight / 2.3))) + end, + UpdateChatOverlayMessageCommand = function(self) + if not tabs[i + 1] then + self:settext("") + return + end + if tabs[i + 1][1] == 0 and tabs[i + 1][2] == "" then + self:settext(translated_info["LobbyTab"]) + elseif tabs[i + 1][1] ~= 0 and tabs[i + 1][2] == "" then + self:settext(translated_info["ServerTab"]) + else + self:settext(tabs[i + 1][2] or "") + end + if + tabs[i + 1] and + ((tabs[i + 1][1] == 0 and tabs[i + 1][2] == "") or + (tabs[i + 1][1] == 1 and tabs[i + 1][2] ~= nil and tabs[i + 1][2] == NSMAN:GetCurrentRoomName())) + then + self:maxwidth((tabWidth - 5) / scale) + else + self:maxwidth((tabWidth - 15) / scale) + end + end + }, + Def.Sprite { + Texture = THEME:GetPathG("","X.png"), + InitCommand = function(self) + self:halign(0):valign(0.5) + self:zoom(scale - 0.1) + self:diffuse(Color.Red) + self:xy(x + tabWidth * (i + 1) - closeTabSize, y + height * (1 + (tabHeight / 2.1))) + end, + UpdateChatOverlayMessageCommand = function(self) + if + tabs[i + 1] and + ((tabs[i + 1][1] == 0 and tabs[i + 1][2] == "") or + (tabs[i + 1][1] == 1 and tabs[i + 1][2] ~= nil and tabs[i + 1][2] == NSMAN:GetCurrentRoomName())) + then + self:visible(false) + else + self:visible(true) + end + end + } + } +end + +local inbg +chatWindow[#chatWindow + 1] = + Def.Quad { + Name = "ChatBox", + InitCommand = function(self) + inbg = self + self:diffuse(Colors.input) + self:diffusealpha(transparency) + end, + UpdateChatOverlayMessageCommand = function(self) + self:stretchto(x, height * (maxlines + 1) + y + 4, width + x, height * (maxlines + 1 + inputLineNumber) + y) + self:diffuse(typing and Colors.activeInput or Colors.input):diffusealpha(transparency) + end +} +chatWindow[#chatWindow + 1] = + LoadColorFont("Common Normal") .. + { + Name = "ChatBoxText", + InitCommand = function(self) + self:settext("") + self:halign(0):valign(0) + self:vertspacing(0) + self:zoom(scale) + self:SetMaxLines(maxlines, 1) + self:wrapwidthpixels((width - 8) / scale) + self:diffuse(color("#FFFFFF")) + end, + UpdateChatOverlayMessageCommand = function(self) + self:settext(typingText) + self:xy(x + 4, height * (maxlines + 1) + y + 4 + 4) + end + } + +chat[#chat + 1] = chatWindow + +chat.UpdateChatOverlayMessageCommand = function(self) + -- breaks everything in the theme + --SCREENMAN:set_input_redirected("PlayerNumber_P1", typing) +end + +function shiftTab(fromIndex, toIndex) + -- tabs[index of tab][parameter table....] + -- [1 is type, 2 is tab contents?] + tabs[toIndex] = tabs[fromIndex] + tabs[fromIndex] = nil +end + +function shiftAllTabs(emptyIndex) + for i = emptyIndex + 1, maxTabs - 1 do + shiftTab(i, i - 1) + end +end + +function overTab(mx, my) + for i = 0, maxTabs - 1 do + if tabs[i + 1] then + if + mx >= x + tabWidth * i and my >= y + height + moveY and mx <= x + tabWidth * (i + 1) and + my <= y + height * (1 + tabHeight) + moveY + then + return i + 1, mx >= x + tabWidth * (i + 1) - closeTabSize + end + end + end + return nil, nil +end + +function MPinput(event) + if (not show or not online) or isGameplay then + return false + end + local update = false + if event.DeviceInput.button == "DeviceButton_left mouse button" then + if typing then + update = true + end + typing = false + local mx, my = INPUTFILTER:GetMouseX(), INPUTFILTER:GetMouseY() + if isOver(minbar) then --hard mouse toggle -mina + minimised = not minimised + MESSAGEMAN:Broadcast("Minimise") + update = true + elseif isOver(inbg) and not minimised then + typing = true + update = true + elseif mx >= x and mx <= x + width and my >= y + moveY and my <= y + height + moveY then + update = true + elseif not minimised then + local tabButton, closeTab = overTab(mx, my) + if not tabButton then + if typing then + update = true + end + else + if event.type == "InputEventType_FirstPress" then -- only change tabs on press (to stop repeatedly closing tabs or changing to a tab we close) -poco + if not closeTab then + changeTab(tabs[tabButton][2], tabs[tabButton][1]) + else + local tabT = tabs[tabButton][1] + local tabN = tabs[tabButton][2] + if (tabT == 0 and tabN == "") or (tabT == 1 and tabN ~= nil and tabN == NSMAN:GetCurrentRoomName()) then + return false + end + tabs[tabButton] = nil + if chats[tabT][tabN] == messages then + for i = #tabs, 1, -1 do + if tabs[i] then + changeTab(tabs[i][2], tabs[i][1]) + end + end + end + chats[tabT][tabN] = nil + shiftAllTabs(tabButton) + end + update = true + end + end + end + end + + -- hard kb toggle + if event.type == "InputEventType_FirstPress" and event.DeviceInput.button == "DeviceButton_insert" then + minimised = not minimised + MESSAGEMAN:Broadcast("Minimise") + update = true + end + + if event.type == "InputEventType_FirstPress" and event.DeviceInput.button == "DeviceButton_/" then + shouldOpen = true + end + + if shouldOpen and event.type == "InputEventType_FirstPress" and event.DeviceInput.button ~= "DeviceButton_/" then + shouldOpen = false + end + + if not typing and event.type == "InputEventType_Release" then -- keys for auto turning on chat if not already on -mina + if event.DeviceInput.button == "DeviceButton_/" and shouldOpen then + typing = true + update = true + if minimised then + minimised = not minimised + MESSAGEMAN:Broadcast("Minimise") + end + typingText = "/" + shouldOpen = false + end + end + + if typing then + if event.type == "InputEventType_Release" then + if event.DeviceInput.button == "DeviceButton_enter" then + if typingText:len() > 0 then + NSMAN:SendChatMsg(typingText, currentTabType, currentTabName) + typingText = "" + elseif typingText == "" then + typing = false -- pressing enter when text is empty to deactive chat is expected behavior -mina + end + update = true + end + elseif event.button == "Back" then + typingText = "" + typing = false + update = true + elseif event.DeviceInput.button == "DeviceButton_space" then + typingText = typingText .. " " + update = true + elseif event.DeviceInput.button == "DeviceButton_delete" then -- reset msg with delete (since there's no cursor) + typingText = "" + update = true + elseif (INPUTFILTER:IsBeingPressed("left ctrl") or INPUTFILTER:IsBeingPressed("right ctrl")) and + event.DeviceInput.button == "DeviceButton_v" then + typingText = typingText .. Arch.getClipboard() + update = true + elseif event.DeviceInput.button == "DeviceButton_backspace" then + typingText = typingText:sub(1, -2) + update = true + elseif event.char then + typingText = (tostring(typingText) .. tostring(event.char)):gsub("[^%g%s]", "") + update = true + end + end + + if event.DeviceInput.button == "DeviceButton_mousewheel up" and event.type == "InputEventType_FirstPress" then + if isOver(chatbg) then + if lineNumber < #messages then + lineNumber = lineNumber + 1 + end + update = true + end + end + if event.DeviceInput.button == "DeviceButton_mousewheel down" and event.type == "InputEventType_FirstPress" then + if isOver(chatbg) then + if lineNumber > maxlines then + lineNumber = lineNumber - 1 + end + update = true + end + end + + -- right click over the chat to minimize + if event.DeviceInput.button == "DeviceButton_right mouse button" and isOver(bg) then + if event.type == "InputEventType_FirstPress" then + minimised = not minimised + MESSAGEMAN:Broadcast("Minimise") + end + return true + end + + -- kb activate chat input if not minimized (has to go after the above enter block) + if event.type == "InputEventType_Release" and INPUTFILTER:IsBeingPressed("left ctrl") then + if event.DeviceInput.button == "DeviceButton_enter" and not minimised then + typing = true + update = true + end + end + + if update then + if minimised then -- minimise will be set in the above blocks, disable input and clear text -mina + typing = false + typingText = "" + end + MESSAGEMAN:Broadcast("UpdateChatOverlay") + end + + -- always eat mouse inputs if its within the broader chatbox + if event.DeviceInput.button == "DeviceButton_left mouse button" and isOver(bg) then + return true + end + + returnInput = update or typing + return returnInput +end + +return chat +--[[ + Untested half done prototype stuff for chart request front end (Probably + wanna put this in a special tab located at the rightmost possible position) + +local chartRequestsConfig = { + numChartReqActors = 4, + padding = 10, + spacing = 10, + height = 1, + width = SCREEN_WIDTH +} +chartRequestsConfig.itemHeight = + (chartRequestsConfig.height - chartRequestsConfig.padding * 2) / chartRequestsConfig.numChartReqActors +chartRequestsConfig.itemWidth = chartRequestsConfig.width - chartRequestsConfig.padding * 2 +function chartRequestActor(i) + local song + local steps + local req + req = + Def.ActorFrame { + y = (chartRequestsConfig.itemHeight + chartRequestsConfig.spacing) * (i - 1) + } + req.sprite = Widg.Sprite {} + req.rateFont = Widg.Label {} + req.songNameFont = Widg.Label {} + req.rect = + Widg.Rectangle { + width = chartRequestsConfig.itemWidth, + height = chartRequestsConfig.itemHeight - chartRequestsConfig.spacing, + onClick = function() + if song and steps then + local screen = SCREENMAN:GetTopScreen() + local sName = screen:GetName() + if sName == "ScreenSelectMusic" or sName == "ScreenNetSelectMusic" then + screen:GetMusicWheel():SelectSong(song) + end + end + end + } + req[#req + 1] = req.sprite + req[#req + 1] = req.rateFont + req[#req + 1] = req.songNameFont + req[#req + 1] = req.rect + req.updateWithRequest = function(req) + if not req then + song = nil + steps = nil + req.actor:visible(false) + return + end + req.actor:visible(true) + + local ck = req:GetChartkey() + local requester = req:GetUser() + local rate = req:GetRate() + + song = SONGMAN:GetSongByChartKey(ck) + steps = SONGMAN:GetStepsByChartKey(ck) + + req.sprite.actor:fadeleft(1) + req.sprite.actor:Load(song:GetBannerPath()) + req.sprite.actor:scaletocover(0, 0, chartRequestsConfig.itemWidth, chartRequestsConfig.itemHeight) + req.rateFont.actor:settext(tostring(rate)) + req.songNameFont.actor:settext(song:GetMainTitle()) + end + return req +end +local bg = + Widg.Rectangle { + width = chartRequestsConfig.width - chartRequestsConfig.padding * 2, + height = chartRequestsConfig.height - chartRequestActor.padding * 2 +} +local t = + Def.Container { + x = chartRequestsConfig.padding, + y = chartRequestsConfig.padding, + content = { + bg + } +} +local reqWidgs = {} +for i = 1, chartRequestsConfig.numChartReqActors do + reqWidgs[#reqWidgs + 1] = chartRequestActor(i) + t[#t + 1] = reqWidgs[#reqWidgs] +end +local offset = 0 +t.ChartRequestMessageCommand = function(self) + local reqs = NSMAN:GetChartRequests() + for i = 1, #reqWidgs do + local widg = reqWidgs[i] + widg.updateWithRequest(reqs[i + offsset]) + end +end +t.BeginCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback( + function(event) + if event.type ~= "InputEventType_FirstPress" or not bg:isOver() then + return false + end + if event.DeviceInput.button == "DeviceButton_mousewheel up" then + offset = math.min(offset - 1, 0) + t.ChartRequestMessageCommand() + elseif event.DeviceInput.button == "DeviceButton_mousewheel down" then + local reqs = NSMAN:GetChartRequests() + offset = math.min(offset + 1, #reqs) + t.ChartRequestMessageCommand() + end + end + ) +end + +]] diff --git a/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/bundleDisplay.lua b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/bundleDisplay.lua new file mode 100644 index 0000000000..57a896fa8c --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/bundleDisplay.lua @@ -0,0 +1,387 @@ + +local ratios = { + InfoTopGap = 36 / 1080, -- top edge screen to top edge box + InfoLeftGap = 384 / 1920, -- left edge screen to left edge box + InfoWidth = 1153 / 1920, -- small box width + InfoHeight = 150 / 1080, -- small box height + InfoHorizontalBuffer = 40 / 1920, -- from side of box to side of text + InfoVerticalBuffer = 28 / 1080, -- from top/bottom edge of box to top/bottom edge of text + + MainDisplayTopGap = 216 / 1080, -- top edge screen to top edge box + MainDisplayLeftGap = 225 / 1920, -- left edge screen to left edge box + MainDisplayWidth = 1471 / 1920, -- big box width + MainDisplayHeight = 648 / 1080, -- big box height + + BundleListTopGap = 24 / 1080, -- top edge of box to top of button + BundleListEdgeBuffer = 39 / 1920, -- left edge of box to left edge of button, also from right edge big box to right edge text + BundleListTextBuffer = 10 / 1920, -- from bundle button to text + BundleItemWidth = 404 / 1920, -- button width + BundleItemGap = 29 / 1080, -- gap in between items + + ProgressTextBottomGap = 105 / 1080, -- bottom screen to bottom text + -- progress text is centered screen + ProgressBarBottomGap = 49 / 1080, -- bottom screen to bottom bar + ProgressBarWidth = 1255 / 1920, -- width + ProgressBarHeight = 25 / 1080, -- height + + IconExitWidth = 47 / 1920, + IconExitHeight = 36 / 1080, +} + +local actuals = { + InfoTopGap = ratios.InfoTopGap * SCREEN_HEIGHT, + InfoLeftGap = ratios.InfoLeftGap * SCREEN_WIDTH, + InfoWidth = ratios.InfoWidth * SCREEN_WIDTH, + InfoHeight = ratios.InfoHeight * SCREEN_HEIGHT, + InfoHorizontalBuffer = ratios.InfoHorizontalBuffer * SCREEN_WIDTH, + InfoVerticalBuffer = ratios.InfoVerticalBuffer * SCREEN_HEIGHT, + MainDisplayTopGap = ratios.MainDisplayTopGap * SCREEN_HEIGHT, + MainDisplayLeftGap = ratios.MainDisplayLeftGap * SCREEN_WIDTH, + MainDisplayWidth = ratios.MainDisplayWidth * SCREEN_WIDTH, + MainDisplayHeight = ratios.MainDisplayHeight * SCREEN_HEIGHT, + BundleListTopGap = ratios.BundleListTopGap * SCREEN_HEIGHT, + BundleListEdgeBuffer = ratios.BundleListEdgeBuffer * SCREEN_WIDTH, + BundleListTextBuffer = ratios.BundleListTextBuffer * SCREEN_WIDTH, + BundleItemWidth = ratios.BundleItemWidth * SCREEN_WIDTH, + BundleItemGap = ratios.BundleItemGap * SCREEN_HEIGHT, + ProgressTextBottomGap = ratios.ProgressTextBottomGap * SCREEN_HEIGHT, + ProgressBarBottomGap = ratios.ProgressBarBottomGap * SCREEN_HEIGHT, + ProgressBarWidth = ratios.ProgressBarWidth * SCREEN_WIDTH, + ProgressBarHeight = ratios.ProgressBarHeight * SCREEN_HEIGHT, + IconExitWidth = ratios.IconExitWidth * SCREEN_WIDTH, + IconExitHeight = ratios.IconExitHeight * SCREEN_HEIGHT, +} + +local infoTextSize = 0.37 +local progressTextSize = 0.35 +local bundleNameTextSize = 0.4 +local bundleDescTextSize = 0.4 +local disconnectedTextSize = 0.7 +local textZoomFudge = 5 +local buttonHoverAlpha = 0.6 + +local function bundleList() + local bundles = { + { + Name = "Novice", + Color = COLORS:getColor("downloader", "Bundle1Easiest"), + Description = "A bundle aimed at people who are entirely new to rhythm games.\nMostly single notes throughout the song and very little pattern complexity.", + }, + { + Name = "Beginner", + Color = COLORS:getColor("downloader", "Bundle2Easy"), + Description = "A bundle for those who have formed some muscle memory.\nJumps (2 note chords) are introduced; some technical patterns start to appear.", + }, + { + Name = "Intermediate", + Color = COLORS:getColor("downloader", "Bundle3Medium"), + Description = "A bundle for players who can confidently play complex patterns.\nJumpstream/handstream, very technical patterns, and jacks are common.", + }, + { + Name = "Advanced", + Color = COLORS:getColor("downloader", "Bundle4Hard"), + Description = "A bundle for advanced players.\nDumps are introduced. Very fast patterns in stamina intensive and complex files.", + }, + { + Name = "Expert", + Color = COLORS:getColor("downloader", "Bundle5Hardest"), + Description = "A bundle for veterans.\nSome of the hardest songs the game has to offer. Nothing is off-limits.", + }, + } + + local cursorIndex = 1 + local function moveCursor(n) + local newpos = cursorIndex + n + if newpos > #bundles then newpos = 1 end + if newpos < 1 then newpos = #bundles end + cursorIndex = newpos + MESSAGEMAN:Broadcast("UpdateCursor") + end + + local function bundleItem(i) + local bundle = bundles[i] + local bundleUserdata = DLMAN:GetCoreBundle(bundle.Name:lower()) + local yIncrement = (actuals.MainDisplayHeight - (actuals.BundleListTopGap*2)) / #bundles + return Def.ActorFrame { + Name = "Bundle_"..i, + InitCommand = function(self) + -- center y + self:y(yIncrement * (i-1) + yIncrement / 2) + end, + SelectCurrentCommand = function(self) + if cursorIndex == i then + DLMAN:DownloadCoreBundle(bundle.Name:lower()) + end + end, + + UIElements.QuadButton(1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.BundleItemWidth, yIncrement - (actuals.BundleItemGap)) + self:diffuse(bundle.Color) + end, + UpdateCursorMessageCommand = function(self) + if cursorIndex == i then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + MouseDownCommand = function(self, params) + DLMAN:DownloadCoreBundle(bundle.Name:lower()) + end, + MouseOverCommand = function(self, params) + cursorIndex = i + self:GetParent():GetParent():playcommand("UpdateCursor") + end, + }, + LoadFont("Common Large") .. { + Name = "BundleNameSize", + InitCommand = function(self) + self:x(actuals.BundleItemWidth / 2) + self:zoom(bundleNameTextSize) + self:maxwidth(actuals.BundleItemWidth / bundleNameTextSize - textZoomFudge) + self:playcommand("Set") + end, + SetCommand = function(self) + if bundleUserdata.TotalSize == 0 then + -- odd situation. maybe the api is down? no connection? + self:settextf("%s Bundle", bundle.Name) + else + self:settextf("%s Bundle (%dMB)", bundle.Name, bundleUserdata.TotalSize) + end + end, + CoreBundlesRefreshedMessageCommand = function(self) + bundleUserdata = DLMAN:GetCoreBundle(bundle.Name:lower()) + self:playcommand("Set") + end, + }, + LoadFont("Common Large") .. { + Name = "BundleDescription", + InitCommand = function(self) + self:halign(0) + self:x(actuals.BundleItemWidth + actuals.BundleListTextBuffer) + self:zoom(bundleDescTextSize) + self:maxheight(yIncrement * 0.75 / bundleDescTextSize) + self:wrapwidthpixels((actuals.MainDisplayWidth - actuals.BundleListTextBuffer - actuals.BundleItemWidth - (actuals.BundleListEdgeBuffer*2)) / bundleDescTextSize) + self:settext(bundle.Description) + end, + }, + } + end + + local t = Def.ActorFrame { + Name = "BundleListContainer", + BeginCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type == "InputEventType_Release" then return end + + local gameButton = event.button + local key = event.DeviceInput.button + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local enter = gameButton == "Start" + local back = key == "DeviceButton_escape" + + if up or left then + moveCursor(-1) + elseif down or right then + moveCursor(1) + elseif enter then + self:playcommand("SelectCurrent") + elseif back then + SCREENMAN:GetTopScreen():Cancel() + end + end) + self:playcommand("UpdateCursor") + end, + } + + for i = 1, #bundles do + t[#t+1] = bundleItem(i) + end + return t +end + + +local t = Def.ActorFrame { + Name = "BundleDisplayFile", + + Def.ActorFrame { + Name = "InfoBoxFrame", + InitCommand = function(self) + self:xy(actuals.InfoLeftGap, actuals.InfoTopGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.InfoWidth, actuals.InfoHeight) + self:diffuse(color("0,0,0")) + self:diffusealpha(0.6) + end, + }, + LoadColorFont("Common Large") .. { + Name = "Text", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.InfoHorizontalBuffer, actuals.InfoHeight/2) + self:zoom(infoTextSize) + self:maxheight((actuals.InfoHeight - (actuals.InfoVerticalBuffer*2)) / infoTextSize) + self:wrapwidthpixels((actuals.InfoWidth - (actuals.InfoHorizontalBuffer*2)) / infoTextSize) + self:settext("Welcome to Etterna!\nLet's start by installing some songs. Click the button that corresponds to your skill level and the installation will proceed automatically.") + end, + }, + }, + Def.ActorFrame { + Name = "MainDisplayFrame", + InitCommand = function(self) + self:xy(actuals.MainDisplayLeftGap, actuals.MainDisplayTopGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.MainDisplayWidth, actuals.MainDisplayHeight) + self:diffuse(color("0,0,0")) + self:diffusealpha(0.6) + end, + }, + LoadFont("Common Large") .. { + Name = "DisconnectedAlert", + InitCommand = function(self) + self:visible(false) + self:settext("Server pack listing is empty.\nIs the API down?\nIs your network connection down?\nYou may have to install songs manually.") + self:xy(actuals.MainDisplayWidth/2, actuals.MainDisplayHeight/2) + self:maxheight(actuals.MainDisplayHeight / disconnectedTextSize) + self:maxwidth(actuals.MainDisplayWidth / disconnectedTextSize) + self:zoom(disconnectedTextSize) + end, + BeginCommand = function(self) + if #DLMAN:GetAllPacks() == 0 then + self:visible(true) + self:GetParent():GetChild("BundleListContainer"):visible(false) + end + end, + CoreBundlesRefreshedMessageCommand = function(self) + if #DLMAN:GetAllPacks() == 0 then + self:visible(true) + self:GetParent():GetChild("BundleListContainer"):visible(false) + else + self:visible(false) + self:GetParent():GetChild("BundleListContainer"):visible(true) + end + end, + }, + bundleList() .. { + InitCommand = function(self) + self:xy(actuals.BundleListEdgeBuffer, actuals.BundleListTopGap) + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "exit")) .. { + Name = "Exit", + InitCommand = function(self) + self:valign(0):halign(1) + self:xy(actuals.MainDisplayWidth - actuals.InfoVerticalBuffer/4, actuals.InfoVerticalBuffer/4) + self:zoomto(actuals.IconExitWidth, actuals.IconExitHeight) + end, + MouseDownCommand = function(self, params) + SCREENMAN:GetTopScreen():Cancel() + TOOLTIP:Hide() + end, + MouseOverCommand = function(self, params) + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Exit") + TOOLTIP:Show() + end, + MouseOutCommand = function(self, params) + self:diffusealpha(1) + TOOLTIP:Hide() + end, + }, + }, + Def.ActorFrame { + Name = "ProgressFrame", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_HEIGHT) + self:visible(false) + end, + DLProgressAndQueueUpdateMessageCommand = function(self) + local dls = DLMAN:GetDownloads() + if #dls > 0 then + local progress = dls[1]:GetKBDownloaded() + local size = dls[1]:GetTotalKB() + local kbsec = dls[1]:GetKBPerSecond() + self:playcommand("UpdateProgress", { + progress = progress, -- progress of current dl, not total + size = size, -- size of current dl, not total + kbsec = kbsec, + filesRemaining = #dls-1, -- how many more downloads after the current one + }) + else + self:playcommand("ClearProgress") + end + end, + AllDownloadsCompletedMessageCommand = function(self) + self:playcommand("ClearProgress") + end, + UpdateProgressCommand = function(self) + self:visible(true) + end, + ClearProgressCommand = function(self) + self:visible(false) + end, + + LoadFont("Common Large") .. { + Name = "Text", + InitCommand = function(self) + self:valign(1) + self:y(-actuals.ProgressTextBottomGap) + self:zoom(progressTextSize) + self:maxwidth(actuals.ProgressBarWidth / progressTextSize) + end, + UpdateProgressCommand = function(self, params) + if params.filesRemaining > 0 then + self:settextf("Downloading ... (%5.2f%% %dKB/s %d packs remaining)", params.progress / params.size * 100, params.kbsec, params.filesRemaining) + else + self:settextf("Downloading ... (%5.2f%% %dKB/s)", params.progress / params.size * 100, params.kbsec) + end + end, + }, + Def.ActorFrame { + Name = "BarContainer", + InitCommand = function(self) + self:y(-actuals.ProgressBarBottomGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:valign(1) + self:zoomto(actuals.ProgressBarWidth, actuals.ProgressBarHeight) + self:diffuse(color("0.7,0.7,0.7")) + self:diffusealpha(0.6) + end, + }, + Def.Quad { + Name = "Progress", + InitCommand = function(self) + self:valign(1):halign(0) + self:x(-actuals.ProgressBarWidth/2) + self:zoomto(0, actuals.ProgressBarHeight) + self:diffuse(color("1,1,1")) + end, + UpdateProgressCommand = function(self, params) + self:zoomx(actuals.ProgressBarWidth * (params.progress / params.size)) + end, + } + } + }, +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/default.lua new file mode 100644 index 0000000000..5da44c789f --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect overlay/default.lua @@ -0,0 +1,6 @@ +local t = Def.ActorFrame {Name = "OverlayFile"} + +t[#t+1] = LoadActor("bundleDisplay") +t[#t+1] = LoadActor("../_mouse.lua") + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect underlay.lua b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect underlay.lua new file mode 100644 index 0000000000..43330b4766 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenCoreBundleSelect underlay.lua @@ -0,0 +1,5 @@ +local t = Def.ActorFrame {Name = "UnderlayFile"} + +t[#t+1] = LoadActor(THEME:GetPathG("Title", "BG")) + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/default.lua b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/default.lua new file mode 100644 index 0000000000..3355a288c9 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/default.lua @@ -0,0 +1,5 @@ +local t = Def.ActorFrame {Name = "DecorationsFile"} + +t[#t+1] = LoadActor("mainDisplay") -- the primary score screen assets + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/mainDisplay.lua b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/mainDisplay.lua new file mode 100644 index 0000000000..d77e4e5795 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/mainDisplay.lua @@ -0,0 +1,1246 @@ +local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or GetTimingDifficulty()) +local pss = STATSMAN:GetCurStageStats():GetPlayerStageStats() +-- keep track of the current displayed score so we can refer back to it +local chosenScore +local mostRecentScore = SCOREMAN:GetMostRecentScore() +local screen + +local function evalInput(event) + if event.type == "InputEventType_FirstPress" then + local btn = event.GameButton + if btn ~= nil then + if btn == "EffectUp" then + -- judge window increase + judgeSetting = clamp(judgeSetting + 1, 4, 9) + MESSAGEMAN:Broadcast("JudgeWindowChanged") + elseif btn == "EffectDown" then + -- judge window decrease + judgeSetting = clamp(judgeSetting - 1, 4, 9) + MESSAGEMAN:Broadcast("JudgeWindowChanged") + end + end + end +end + +local t = Def.ActorFrame { + Name = "MainDisplayFile", + BeginCommand = function(self) + screen = SCREENMAN:GetTopScreen() + screen:AddInputCallback(evalInput) + end, + OnCommand = function(self) + local score = SCOREMAN:GetMostRecentScore() + if not score then + score = SCOREMAN:GetTempReplayScore() + end + chosenScore = score + + -- use this to force J4 init for SSRNorm + local forcedScreenEntryJudgeWindow = nil + if PREFSMAN:GetPreference("SortBySSRNormPercent") then + forcedScreenEntryJudgeWindow = 4 + -- update replaysnapshots and pss for current score being rejudged to j4 + screen:SetPlayerStageStatsFromReplayData(pss, ms.JudgeScalers[forcedScreenEntryJudgeWindow], score) + end + + --- propagate set command through children with the song + self:playcommand("Set", { + song = GAMESTATE:GetCurrentSong(), + steps = GAMESTATE:GetCurrentSteps(), + score = score, + judgeSetting = forcedScreenEntryJudgeWindow + }) + end, + UpdateScoreCommand = function(self, params) + -- update the global judge setting if it is provided and just in case it happens to be desynced here + -- (it desyncs if picking scores, this will fix it) + -- otherwise it should already be set by the input event + if params.judgeSetting ~= nil then + judgeSetting = params.judgeSetting + end + + -- we assume the score has a replay + -- recalculate playerstagestats using the replay + screen:SetPlayerStageStatsFromReplayData(pss, ms.JudgeScalers[judgeSetting], params.score) + + chosenScore = params.score + + --- update all relevant information according to the given score + -- should work with offset plot as well as all regular information on this screen + -- this is intended for use only with replays but may partially work without it + self:playcommand("Set", { + song = GAMESTATE:GetCurrentSong(), + steps = GAMESTATE:GetCurrentSteps(), + score = params.score, + judgeSetting = params.judgeSetting, + rejudged = params.rejudged, -- optional param to know if need to reload offset plot + }) + end, + JudgeWindowChangedMessageCommand = function(self) + -- we assume a score is already set + -- so we run it back through again + -- the fact that the param table has judgeSetting in it causes things to recalc according to judge + self:playcommand("UpdateScore", { + score = chosenScore, + judgeSetting = judgeSetting, + rejudged = true, + }) + end, + LoginMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + LogOutMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + LoginFailedMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + OnlineUpdateMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end +} + +local ratios = { + LeftGap = 78 / 1920, + UpperGap = 135 / 1080, -- from top edge of screen to edge of bg + Width = 1765 / 1920, + Height = 863 / 1080, + LipLeftGap = 800 / 1920, -- the lip starts at the end of the banner + LipHeight = 50 / 1080, + LipLength = 965 / 1920, -- runs to the right end of the frame + + GraphLeftGap = 18 / 1920, + GraphWidth = 739 / 1920, -- this must be the same as in metrics [GraphDisplay/ComboGraph] + GraphBannerGap = 9 / 1080, -- from bottom of banner to top of graph + BannerLeftGap = 18 / 1920, + BannerUpperGap = 7 / 1080, + BannerHeight = 228 / 1080, + BannerWidth = 739 / 1920, + LifeGraphHeight = 71 / 1080, -- this must be the same as in metrics [GraphDisplay] + ComboGraphHeight = 16 / 1080, -- this must be the same as in metrics [ComboGraph] + + DividerThickness = 2 / 1080, + LeftDividerLeftGap = 18 / 1920, + LeftDividerLength = 739 / 1920, + + LeftDivider1UpperGap = 338 / 1080, + LeftDivider2UpperGap = 399 / 1080, + + ModTextLeftGap = 19 / 1920, + -- modtext Y pos is half between the 2 dividers. + + JudgmentBarLeftGap = 18 / 1920, -- edge of frame to left of bar + JudgmentBarUpperGap = 408 / 1080, -- top edge of from to top edge of top bar + JudgmentBarHeight = 44 / 1080, + JudgmentBarAllottedSpace = 264 / 1080, -- top of top bar to top of bottom bar (valign 0) + JudgmentBarLength = 739 / 1920, + JudgmentBarSpacing = 7 / 1080, -- the emptiness between judgments + JudgmentNameLeftGap = 25 / 1920, -- from left edge of bar to left edge of text + JudgmentCountRightGap = 95 / 1920, -- from right edge of bar to left edge of percentage, right edge of count + + BottomTextUpperGap = 733 / 1080, -- top edge of frame to top edge of the text at the bottom left of the screen + BottomTextHeight = 16 / 1080, -- fudge + BottomTextSpacing = 10 / 1080, -- mix with the immediately above value + SubTypeTextLeftGap = 18 / 1920, -- edge of frame to left of text + SubTypeNumberCenter = 170 / 1920, -- from left edge of text to center of the / + SubTypeNumberWidth = 145 / 1920, -- approximate width of the numbers including the / + SubTypeAllottedSpace = 105 / 1080, -- top of top text to top of bottom text (valign 0) + + StatTextRightGap = 225 / 1920, -- right edge of stat count text to left edge of name text + StatCountTotalRightGap = 0 / 1920, -- this is a base line, probably 0, here for consistency + StatTextAllottedSpace = 105 / 1080, -- top of top text to top of bottom text (valign 0) + + RightHalfLeftGap = 803 / 1920, -- left edge of frame to left edge of everything on the right side + RightHalfRightAlignLeftGap = 936 / 1920, -- basically the same length as the divider, right end of rightest right text + RightHorizontalDividerLength = 936 / 1920, + RightHorizontalDivider1UpperGap = 244 / 1080, -- top of frame to top of divider + RightHorizontalDivider2UpperGap = 544 / 1080, -- same + + SongTitleLowerGap = 147 / 1080, -- top of divider to bottom of text + SongArtistLowerGap = 103 / 1080, -- same + SongPackLowerGap = 59 / 1080, -- ... + SongRateLowerGap = 15 / 1080, + GradeLowerGap = 141 / 1080, + WifePercentLowerGap = 77 / 1080, + MSDInfoLowerGap = 17 / 1080, + + ScoreBoardHeight = 298 / 1080, -- inner edge of divider to inner edge of divider + + OffsetPlotUpperGap = 559 / 1080, -- from top of frame to top of plot + OffsetPlotHeight = 295 / 1080, + OffsetPlotWidth = 936 / 1920, +} + +local actuals = { + LeftGap = ratios.LeftGap * SCREEN_WIDTH, + UpperGap = ratios.UpperGap * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + LipLeftGap = ratios.LipLeftGap * SCREEN_WIDTH, + LipHeight = ratios.LipHeight * SCREEN_HEIGHT, + LipLength = ratios.LipLength * SCREEN_WIDTH, + GraphLeftGap = ratios.GraphLeftGap * SCREEN_WIDTH, + GraphWidth = ratios.GraphWidth * SCREEN_WIDTH, + GraphBannerGap = ratios.GraphBannerGap * SCREEN_HEIGHT, + BannerLeftGap = ratios.BannerLeftGap * SCREEN_WIDTH, + BannerUpperGap = ratios.BannerUpperGap * SCREEN_HEIGHT, + BannerHeight = ratios.BannerHeight * SCREEN_HEIGHT, + BannerWidth = ratios.BannerWidth * SCREEN_WIDTH, + LifeGraphHeight = ratios.LifeGraphHeight * SCREEN_HEIGHT, + ComboGraphHeight = ratios.ComboGraphHeight * SCREEN_HEIGHT, + DividerThickness = ratios.DividerThickness * SCREEN_HEIGHT, + LeftDividerLeftGap = ratios.LeftDividerLeftGap * SCREEN_WIDTH, + LeftDividerLength = ratios.LeftDividerLength * SCREEN_WIDTH, + LeftDivider1UpperGap = ratios.LeftDivider1UpperGap * SCREEN_HEIGHT, + LeftDivider2UpperGap = ratios.LeftDivider2UpperGap * SCREEN_HEIGHT, + ModTextLeftGap = ratios.ModTextLeftGap * SCREEN_WIDTH, + JudgmentBarLeftGap = ratios.JudgmentBarLeftGap * SCREEN_WIDTH, + JudgmentBarUpperGap = ratios.JudgmentBarUpperGap * SCREEN_HEIGHT, + JudgmentBarHeight = ratios.JudgmentBarHeight * SCREEN_HEIGHT, + JudgmentBarAllottedSpace = ratios.JudgmentBarAllottedSpace * SCREEN_HEIGHT, + JudgmentBarLength = ratios.JudgmentBarLength * SCREEN_WIDTH, + JudgmentBarSpacing = ratios.JudgmentBarSpacing * SCREEN_HEIGHT, + JudgmentNameLeftGap = ratios.JudgmentNameLeftGap * SCREEN_WIDTH, + JudgmentCountRightGap = ratios.JudgmentCountRightGap * SCREEN_WIDTH, + BottomTextUpperGap = ratios.BottomTextUpperGap * SCREEN_HEIGHT, + BottomTextHeight = ratios.BottomTextHeight * SCREEN_HEIGHT, + BottomTextSpacing = ratios.BottomTextSpacing * SCREEN_HEIGHT, + SubTypeTextLeftGap = ratios.SubTypeTextLeftGap * SCREEN_WIDTH, + SubTypeNumberCenter = ratios.SubTypeNumberCenter * SCREEN_WIDTH, + SubTypeNumberWidth = ratios.SubTypeNumberWidth * SCREEN_WIDTH, + SubTypeAllottedSpace = ratios.SubTypeAllottedSpace * SCREEN_HEIGHT, + StatTextRightGap = ratios.StatTextRightGap * SCREEN_WIDTH, + StatCountTotalRightGap = ratios.StatCountTotalRightGap * SCREEN_WIDTH, + StatTextAllottedSpace = ratios.StatTextAllottedSpace * SCREEN_HEIGHT, + RightHalfLeftGap = ratios.RightHalfLeftGap * SCREEN_WIDTH, + RightHalfRightAlignLeftGap = ratios.RightHalfRightAlignLeftGap * SCREEN_WIDTH, + RightHorizontalDividerLength = ratios.RightHorizontalDividerLength * SCREEN_WIDTH, + RightHorizontalDivider1UpperGap = ratios.RightHorizontalDivider1UpperGap * SCREEN_HEIGHT, + RightHorizontalDivider2UpperGap = ratios.RightHorizontalDivider2UpperGap * SCREEN_HEIGHT, + SongTitleLowerGap = ratios.SongTitleLowerGap * SCREEN_HEIGHT, + SongArtistLowerGap = ratios.SongArtistLowerGap * SCREEN_HEIGHT, + SongPackLowerGap = ratios.SongPackLowerGap * SCREEN_HEIGHT, + SongRateLowerGap = ratios.SongRateLowerGap * SCREEN_HEIGHT, + GradeLowerGap = ratios.GradeLowerGap * SCREEN_HEIGHT, + WifePercentLowerGap = ratios.WifePercentLowerGap * SCREEN_HEIGHT, + MSDInfoLowerGap = ratios.MSDInfoLowerGap * SCREEN_HEIGHT, + ScoreBoardHeight = ratios.ScoreBoardHeight * SCREEN_HEIGHT, + OffsetPlotUpperGap = ratios.OffsetPlotUpperGap * SCREEN_HEIGHT, + OffsetPlotHeight = ratios.OffsetPlotHeight * SCREEN_HEIGHT, + OffsetPlotWidth = ratios.OffsetPlotWidth * SCREEN_WIDTH, +} + +-- constant list of judgments for rescoring purposes +-- for the most part this list is the same as the one below but remains separate just "in case" +local tapJudgments = { + "TapNoteScore_W1", + "TapNoteScore_W2", + "TapNoteScore_W3", + "TapNoteScore_W4", + "TapNoteScore_W5", + "TapNoteScore_Miss", +} + +-- list of tap/hold subtypes to display the counts for +-- these are each a part of RadarCategory_x +local subTypesChosen = { + "Holds", + "Mines", + "Rolls", + "Lifts", + "Fakes", +} + +local modTextZoom = 0.6 + +local subTypeTextZoom = 0.7 +local subTypeTextBump = 5 -- a bump in position added to the bottom left numbers for spacing + +local statTextZoom = 0.7 +local statTextSuffixZoom = 0.6 +local accStatZoom = 0.7 + +local titleTextSize = 0.8 +local songInfoTextSize = 0.55 +local scoreInfoTextSize = 0.8 + +local textzoomFudge = 5 + +-- a helper to get the radar value for a score and fall back to playerstagestats if that fails +-- it tends to fail a lot... +local function gatherRadarValue(radar, score) + local n = score:GetRadarValues():GetValue(radar) + if n == -1 then + return pss:GetRadarActual():GetValue(radar) + end + return n +end + +-- construct a table that is passed to the wife rescoring function +local function gatherRescoreTableFromScore(score) + local o = {} + -- tap offsets + o["dvt"] = score:GetOffsetVector() + -- holds + o["totalHolds"] = pss:GetRadarPossible():GetValue("RadarCategory_Holds") + pss:GetRadarPossible():GetValue("RadarCategory_Rolls") + o["holdsHit"] = gatherRadarValue("RadarCategory_Holds", score) + gatherRadarValue("RadarCategory_Rolls", score) + o["holdsMissed"] = o["totalHolds"] - o["holdsHit"] + -- mines + o["minesHit"] = pss:GetRadarPossible():GetValue("RadarCategory_Mines") - gatherRadarValue("RadarCategory_Mines", score) + -- taps + o["totalTaps"] = 0 + for _, j in ipairs(tapJudgments) do + o["totalTaps"] = o["totalTaps"] + score:GetTapNoteScore(j) + end + return o +end + +local function subTypeStats() + local t = Def.ActorFrame {Name = "SubTypeParentFrame"} + local function makeLine(i) + local rdr = subTypesChosen[i] + + return Def.ActorFrame { + Name = "SubTypeLine_"..i, + InitCommand = function(self) + self:y((actuals.SubTypeAllottedSpace / (#subTypesChosen - 1)) * (i-1)) + end, + + LoadFont("Common Normal") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoom(subTypeTextZoom) + self:maxwidth((actuals.SubTypeNumberCenter - subTypeTextBump - actuals.SubTypeNumberWidth / 2) / subTypeTextZoom - textzoomFudge) + self:settext(rdr) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.RollingNumbers { + Name = "Count", + Font = "Common Normal", + InitCommand = function(self) + self:Load("RollingNumbersSlow3Leading") + self:halign(1):valign(0) + self:x(actuals.SubTypeNumberCenter - subTypeTextBump) + self:zoom(subTypeTextZoom) + self:maxwidth((actuals.SubTypeNumberWidth / 2 - subTypeTextBump) / subTypeTextZoom - textzoomFudge) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.score == nil then + self:targetnumber(0) + return + end + local num = gatherRadarValue(rdr, params.score) + self:targetnumber(num) + end + }, + LoadFont("Common Normal") .. { + Name = "Slash", + InitCommand = function(self) + -- when you want to do something in a really particular way and dont trust anything else to get it right + self:valign(0) + self:x(actuals.SubTypeNumberCenter) + self:zoom(subTypeTextZoom) + self:settext("/") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.RollingNumbers { + Name = "Total", + Font = "Common Normal", + InitCommand = function(self) + self:Load("RollingNumbersSlow3Leading") + self:halign(0):valign(0) + self:x(actuals.SubTypeNumberCenter + subTypeTextBump) + self:zoom(subTypeTextZoom) + self:maxwidth((actuals.SubTypeNumberWidth / 2 - subTypeTextBump) / subTypeTextZoom - textzoomFudge) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.score == nil then + self:targetnumber(0) + return + end + local num = pss:GetRadarPossible():GetValue("RadarCategory_"..rdr) + self:targetnumber(num) + end + } + } + end + for i = 1, #subTypesChosen do + t[#t+1] = makeLine(i) + end + + return t +end + +local function accuracyStats() + local statStrings = { + "RA", -- Ridiculous Attack - Ratio of J7 Marvelous to J7 Perfect + "MA", -- Marvelous Attack - Ratio of Marvelous to Perfects + "PA", -- Perfect Attack - Ratio of Perfects to Greats + "Longest MFC", -- Longest streak of Marvelous or better + "Longest PFC", -- Longest streak of Perfect or better + } + local statTypes = { + "EvalRA", + "EvalMA", + "EvalPA", + "EvalLongestMFC", + "EvalLongestPFC", + } + local statData = { + 0, -- Ridiculous divided by Marvelous + 0, -- Marvelous divided by Perfect + 0, -- Perfect divided by Great + 0, -- MFC length + 0, -- PFC length + } + + -- BAD HACK TO FIT LONGEST RFC INTO THE EQUATION + -- I HATE THIS + local isMFC = false -- if true, Longest PFC becomes Longest RFC + local rfcStatIndex = 5 + local rfcStatType = "EvalLongestRFC" + local pfcStatType = "EvalLongestPFC" + -- DONT DO THIS + + -- calculates the statData based on the given score + local function calculateStatData(score) + local offsetTable = score:GetOffsetVector() + local typeTable = score:GetTapNoteTypeVector() + + -- must match statData above + local output = { + 0, -- Ridiculous divided by Marvelous + 0, -- Marvelous and Ridiculous divided by Perfect (because we are used to that) + 0, -- Perfect divided by Great + 0, -- MFC length + 0, -- PFC length + } + + if offsetTable == nil or #offsetTable == 0 or typeTable == nil or #typeTable == 0 then + return output + end + + local ridicThreshold = ms.JudgeScalers[judgeSetting] * 11.25 -- this is the J7 Marvelous window + local marvThreshold = ms.JudgeScalers[judgeSetting] * 22.5 -- J4 Marvelous window + local perfThreshold = ms.JudgeScalers[judgeSetting] * 45 -- J4 Perfect window + local greatThreshold = ms.JudgeScalers[judgeSetting] * 90 -- J4 Great window + local currentRFC = 0 + local currentMFC = 0 + local currentPFC = 0 + local marvsForRA = 0 -- we are counting ridic as marvs normally, so count marvs alone separately to calculate RA + local greatCount = 0 + local longestRFC = 0 + + local validTaps = 0 + + for i, o in ipairs(offsetTable) do + if typeTable[i] ~= nil and (typeTable[i] == "TapNoteType_Tap" or typeTable[i] == "TapNoteType_HoldHead") then + local off = math.abs(o) + validTaps = validTaps + 1 + + -- count judgments + if off <= ridicThreshold then + currentRFC = currentRFC + 1 + currentMFC = currentMFC + 1 + currentPFC = currentPFC + 1 + output[1] = output[1] + 1 + output[2] = output[2] + 1 + elseif off <= marvThreshold then + currentRFC = 0 + currentMFC = currentMFC + 1 + currentPFC = currentPFC + 1 + output[2] = output[2] + 1 + marvsForRA = marvsForRA + 1 + elseif off <= perfThreshold then + currentRFC = 0 + currentMFC = 0 + currentPFC = currentPFC + 1 + output[3] = output[3] + 1 + elseif off <= greatThreshold then + currentRFC = 0 + currentMFC = 0 + currentPFC = 0 + greatCount = greatCount + 1 + else + -- worse than a great + currentMFC = 0 + currentPFC = 0 + end + + -- set new highest MFC/PFC at end of iteration + if currentMFC > output[4] then + output[4] = currentMFC + end + if currentPFC > output[5] then + output[5] = currentPFC + end + if currentRFC > longestRFC then + longestRFC = currentRFC + end + end + end + + if output[4] == validTaps then + output[rfcStatIndex] = longestRFC + statTypes[rfcStatIndex] = rfcStatType + else + statTypes[rfcStatIndex] = pfcStatType + end + + -- prevent division by 0 here + if marvsForRA > 0 then + output[1] = output[1] / marvsForRA + else + output[1] = -1 + end + if output[3] > 0 then + output[2] = output[2] / output[3] + else + output[2] = -1 + end + if greatCount > 0 then + output[3] = output[3] / greatCount + else + output[3] = -1 + end + + return output + end + + local t = Def.ActorFrame { + Name = "AccuracyStatsParentFrame", + SetCommand = function(self, params) + if params.steps ~= nil then + -- this recalculates the stats to display for the following texts + statData = calculateStatData(params.score) + + self:playcommand("UpdateStats", {score = params.score}) + end + end + } + local function makeLine(i) + local statname = statStrings[i] + return Def.ActorFrame { + Name = "Stat_"..i, + InitCommand = function(self) + self:y((actuals.StatTextAllottedSpace / (#statStrings - 1)) * (i-1)) + end, + Def.RollingNumbers { + Name = "Number", + Font = "Common Normal", + InitCommand = function(self) + self:Load("RollingNumbers" .. statTypes[i]) + self:valign(0) + self:zoom(accStatZoom) + self:maxwidth((actuals.JudgmentBarLength - actuals.StatTextRightGap - actuals.SubTypeNumberCenter - subTypeTextBump - actuals.SubTypeTextLeftGap) / 1.25 / accStatZoom - textzoomFudge) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateStatsCommand = function(self, params) + if statData[i] == -1 then + self:Load("RollingNumbers" .. statTypes[i] .. "INF") + self:targetnumber(99999) + else + self:Load("RollingNumbers" .. statTypes[i]) + self:targetnumber(statData[i]) + end + end + } + } + end + + for i = 1, #statStrings do + t[#t+1] = makeLine(i) + end + return t +end + +local function calculatedStats() + -- list of stats + -- do not allow this list to be shorter than 2 in length + local statStrings = { + "Mean", + "Sd", + "Largest", + "Left CBs", + "Middle CBs", -- skip this index for even column types + "Right CBs", + } + + -- RollingNumber types in metrics + -- so we can assign it without so much work + local statTypes = { + "Slow2DecimalNoLeadMilliseconds", + "Slow2DecimalNoLeadMilliseconds", + "Slow2DecimalNoLeadMilliseconds", + "SlowNoLead", + "SlowNoLead", + "SlowNoLead", + } + + local evenColumns = true + local indexToSkip = 5 -- the middle cb index + + -- contains the data corresponding to each of the above stat strings + local statData = { + 0, -- mean + 0, -- sd + 0, -- largest deviation + 0, -- left cb + 0, -- middle cb + 0, -- right cb + } + + local cbInfo = {0,0,0,0} -- per column cb info + + local function calculateStatData(score, numColumns) + local tracks = score:GetTrackVector() + local offsetTable = score:GetOffsetVector() + + local middleColumn = numColumns / 2 + + -- MUST MATCH statData above + local output = { + 0, -- mean + 0, -- sd + 0, -- largest deviation + 0, -- left cb + 0, -- middle cb + 0, -- right cb + } + + local cbInfo = {} + for _ = 1, numColumns + 1 do + cbInfo[#cbInfo+1] = 0 + end + + if offsetTable == nil or #offsetTable == 0 then + return output, cbInfo + end + + local cbThreshold = ms.JudgeScalers[judgeSetting] * 90 + local leftCB = 0 + local middleCB = 0 + local rightCB = 0 + + -- count CBs + for i, o in ipairs(offsetTable) do + -- online replays return 180 instead of "1000" for misses + if o == 180 then + offsetTable[i] = 1000 + o = 1000 + end + if tracks[i] then + if math.abs(o) > cbThreshold then + if tracks[i] < middleColumn then + leftCB = leftCB + 1 + elseif tracks[i] > middleColumn then + rightCB = rightCB + 1 + else + middleCB = middleCB + 1 + end + + cbInfo[tracks[i]+1] = cbInfo[tracks[i]+1] + 1 + end + end + end + + local smallest, largest = wifeRange(offsetTable) + + -- MUST MATCH statData above + output = { + wifeMean(offsetTable), -- mean + wifeSd(offsetTable), -- sd + largest, + leftCB, + middleCB, + rightCB, + } + return output, cbInfo + end + + local t = Def.ActorFrame { + Name = "CalculatedStatsParentFrame", + SetCommand = function(self, params) + if params.steps ~= nil then + if params.steps:GetNumColumns() % 2 ~= 0 then + evenColumns = false + end + -- this recalculates the stats to display for the following texts + -- subtract 1 from the number of columns because we are indexing at 0 in some of the data + -- and it produces the numbers we want + statData, cbInfo = calculateStatData(params.score, params.steps:GetNumColumns() - 1) + + self:playcommand("UpdateStats", {score = params.score}) + end + end + } + local function makeLine(i) + local statname = statStrings[i] + return Def.ActorFrame { + Name = "Stat_"..i, + InitCommand = function(self) + self:y((actuals.StatTextAllottedSpace / (#statStrings - 1)) * (i-1)) + end, + UpdateStatsCommand = function(self, params) + if evenColumns and i == indexToSkip then + self:diffusealpha(0) + else + self:diffusealpha(1) + end + if evenColumns then + if i ~= indexToSkip then + -- this will convert the index to either i or i-1 + -- because when we skip an index we want to place it as if nothing changed + -- and we are using a slightly shorter range than usual anyways + local j = (i < indexToSkip and i or i-1) + self:y((actuals.StatTextAllottedSpace / (#statStrings - 2)) * (j-1)) + end + end + end, + + LoadFont("Common Normal") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoom(statTextZoom) + self:maxwidth(actuals.StatTextRightGap / 2 / statTextZoom - textzoomFudge) + self:settext(statname) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.RollingNumbers { + Name = "Number", + Font = "Common Normal", + InitCommand = function(self) + self:Load("RollingNumbers" .. statTypes[i]) + self:halign(1):valign(0) + -- note to self make this name less confusing + self:x(actuals.StatTextRightGap) + self:zoom(statTextZoom) + -- no fudge + self:maxwidth(actuals.StatTextRightGap / 2 / statTextZoom) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateStatsCommand = function(self, params) + self:targetnumber(statData[i]) + end + } + } + + end + + for i = 1, #statStrings do + t[#t+1] = makeLine(i) + end + + -- displays some extra stats on hover + t[#t+1] = UIElements.QuadButton(1) .. { + Name = "StatMouseHoverBox", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.StatTextRightGap) + self:zoomto(actuals.JudgmentBarLength, actuals.StatTextAllottedSpace / (#statStrings - 1) * #statStrings) + self:diffusealpha(0) + end, + MouseOverCommand = function(self) self:playcommand("RolloverUpdate",{update = "over"}) end, + MouseOutCommand = function(self) self:playcommand("RolloverUpdate",{update = "out"}) end, + RolloverUpdateCommand = function(self, params) + -- hovering + if params.update == "over" then + local cbstr = {"CBs Per Column", "\n"} + for _ = 1, #cbInfo do + cbstr[#cbstr+1] = string.format("Column %d: %d", _, cbInfo[_]) + cbstr[#cbstr+1] = "\n" + end + cbstr[#cbstr] = nil + cbstr = table.concat(cbstr) + + TOOLTIP:SetText(cbstr) + TOOLTIP:Show() + elseif params.update == "out" then + TOOLTIP:Hide() + end + end, + } + + return t +end + +local function wifePercentDisplay() + -- internal value storage defaults + local value = 0 + local decimals = 2 + local stringformat = "%05."..decimals.."f%% [%s]" + + -- """constants""" + local nothoverdecimals = 2 -- when not hovering + local hoverdecimals = 4 -- when hovering + local infostr = "" -- example: W3 J4 + + return UIElements.TextToolTip(1, 1, "Common Large") .. { + Name = "WifePercent", + InitCommand = function(self) + self:halign(1):valign(1) + self:zoom(scoreInfoTextSize) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 2 / scoreInfoTextSize - textzoomFudge) + end, + UpdateParametersCommand = function(self, params) + if params.decimals ~= nil then + decimals = params.decimals + stringformat = "%05."..decimals.."f%% [%s]" + end + if params.infostr ~= nil then + infostr = params.infostr + end + if params.value ~= nil then + value = params.value + end + end, + UpdateTextCommand = function(self) + self:settextf(stringformat, notShit.floor(value, decimals), infostr) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self, params) + if params.score ~= nil then + local ver = params.score:GetWifeVers() + local percent = params.score:GetWifeScore() * 100 + decimals = 2 + if params.judgeSetting ~= nil then + local rescoreTable = gatherRescoreTableFromScore(params.score) + percent = getRescoredWife3Judge(3, params.judgeSetting, rescoreTable) + ver = 3 + end + -- wife version string + local ws = "W"..ver.." J" + ws = ws .. (judgeSetting ~= 9 and judgeSetting or "ustice") + -- scores over 99% should show more decimals + if percent > 99 or isOver(self) then + decimals = 4 + end + local pg = notShit.floor(percent, decimals) + local grade = GetGradeFromPercent(pg / 100) + self:diffuse(colorByGrade(grade)) + self:playcommand("UpdateParameters", {decimals = decimals, infostr = ws, value = percent}) + self:playcommand("UpdateText") + else + self:settext("") + end + end, + MouseOverCommand = function(self) self:playcommand("RolloverUpdate",{update = "over"}) end, + MouseOutCommand = function(self) self:playcommand("RolloverUpdate",{update = "out"}) end, + RolloverUpdateCommand = function(self, params) + -- hovering + if params.update == "over" then + self:playcommand("UpdateParameters", {decimals = hoverdecimals}) + elseif params.update == "out" then + self:playcommand("UpdateParameters", {decimals = nothoverdecimals}) + end + self:playcommand("UpdateText") + end + } +end + +t[#t+1] = Def.ActorFrame { + Name = "OwnerFrame", + InitCommand = function(self) + self:xy(actuals.LeftGap, actuals.UpperGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.75) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end + }, + Def.Quad { + Name = "BGLip", + InitCommand = function(self) + self:valign(0):halign(0) + self:x(actuals.LipLeftGap) + self:zoomto(actuals.LipLength, actuals.LipHeight) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end + }, + Def.Quad { + Name = "LeftUpperDivider", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.LeftDividerLength, actuals.DividerThickness) + self:xy(actuals.LeftDividerLeftGap, actuals.LeftDivider1UpperGap) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + Def.Quad { + Name = "LeftLowerDivider", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.LeftDividerLength, actuals.DividerThickness) + self:xy(actuals.LeftDividerLeftGap, actuals.LeftDivider2UpperGap) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + Def.Quad { + Name = "RightUpperHorizontalDivider", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.RightHorizontalDividerLength, actuals.DividerThickness) + self:xy(actuals.RightHalfLeftGap, actuals.RightHorizontalDivider1UpperGap) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + Def.Quad { + Name = "RightLowerHorizontalDivider", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.RightHorizontalDividerLength, actuals.DividerThickness) + self:xy(actuals.RightHalfLeftGap, actuals.RightHorizontalDivider2UpperGap) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + + Def.Sprite { + Name = "Banner", + InitCommand = function(self) + self:xy(actuals.BannerLeftGap, actuals.BannerUpperGap) + self:valign(0):halign(0) + self:scaletoclipped(actuals.BannerWidth, actuals.BannerHeight) + end, + SetCommand = function(self, params) + self:finishtweening() + self:smooth(0.05) + self:diffusealpha(1) + if params.song then + local bnpath = params.song:GetBannerPath() + if not bnpath then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + self:LoadBackground(bnpath) + else + self:visible(false) + end + end + }, + Def.GraphDisplay { + Name = "LifeGraph", + InitCommand = function(self) + self:valign(0):halign(0) + self:xy(actuals.GraphLeftGap, actuals.BannerUpperGap + actuals.GraphBannerGap + actuals.BannerHeight) + -- due to reasons, the sizing for this is in metrics [GraphDisplay] + -- we override them with the following zoomto + -- so the ones in metrics can be anything.... + -- i don't like that + self:Load("GraphDisplay") + self:zoomto(actuals.GraphWidth, actuals.LifeGraphHeight) + registerActorToColorConfigElement(self, "evaluation", "LifeGraphTint") + + -- hide the max life line and its dots (why does this exist?) + self:GetChild("Line"):diffusealpha(0) + end, + SetCommand = function(self, params) + if params.song ~= nil then + self:SetWithoutStageStats(pss, params.song:GetStepsSeconds() / params.score:GetMusicRate()) + end + end + }, + Def.ComboGraph { + Name = "ComboGraph", + InitCommand = function(self) + self:valign(0):halign(0) + self:xy(actuals.GraphLeftGap, actuals.BannerUpperGap + actuals.GraphBannerGap + actuals.BannerHeight + actuals.LifeGraphHeight) + -- due to reasons, the sizing for this is in metrics [ComboGraph] + -- we dont override them here because the combo text is broken by the zoom + -- self:zoomto(actuals.GraphWidth, actuals.ComboGraphHeight) + registerActorToColorConfigElement(self, "evaluation", "ComboGraphTint") + end, + SetCommand = function(self, params) + self:Clear() + self:Load("ComboGraph") + if params.song ~= nil then + self:SetWithoutStageStats(pss, params.song:GetStepsSeconds() / params.score:GetMusicRate()) + end + end + }, + LoadFont("Common Large") .. { + Name = "ModString", + InitCommand = function(self) + -- should be the upper divider + half the space between (accounting for the width of the top divider) + local yPos = actuals.LeftDivider1UpperGap + (actuals.LeftDivider2UpperGap - actuals.LeftDivider1UpperGap) / 2 + actuals.DividerThickness + self:xy(actuals.ModTextLeftGap, yPos) + self:halign(0) + self:zoom(modTextZoom) + self:maxwidth(actuals.BannerWidth / modTextZoom - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + local mstr = params.score:GetModifiers() + local ss = screen:GetStageStats() + if not ss:GetLivePlay() then + mstr = screen:GetReplayModifiers() + end + self:settext(mstr) + end + }, + LoadActorWithParams("../judgmentBars", { + sizing = { + JudgmentBarHeight = actuals.JudgmentBarHeight, + JudgmentBarLength = actuals.JudgmentBarLength, + JudgmentBarSpacing = actuals.JudgmentBarSpacing, + JudgmentBarAllottedSpace = actuals.JudgmentBarAllottedSpace, + JudgmentNameLeftGap = actuals.JudgmentNameLeftGap, + JudgmentCountRightGap = actuals.JudgmentCountRightGap, + }}) .. { + InitCommand = function(self) + self:xy(actuals.JudgmentBarLeftGap, actuals.JudgmentBarUpperGap) + end + }, + subTypeStats() .. { + InitCommand = function(self) + self:xy(actuals.SubTypeTextLeftGap, actuals.BottomTextUpperGap) + end + }, + accuracyStats() .. { + InitCommand = function(self) + self:xy(actuals.JudgmentBarLeftGap + actuals.JudgmentBarLength / 2, actuals.BottomTextUpperGap) + end + }, + calculatedStats() .. { + InitCommand = function(self) + self:xy(actuals.JudgmentBarLeftGap + actuals.JudgmentBarLength - actuals.StatTextRightGap, actuals.BottomTextUpperGap) + end + }, + + LoadFont("Common Large") .. { + Name = "ScreenTitle", + InitCommand = function(self) + self:xy(actuals.RightHalfLeftGap, actuals.LipHeight / 2) + self:halign(0) + self:zoom(titleTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + if GAMESTATE:GetCurrentSteps() ~= nil then + local st = THEME:GetString("StepsDisplay StepsType", ToEnumShortString(GAMESTATE:GetCurrentSteps():GetStepsType())) + self:settextf("%s Results", st) + else + self:settext("Results") + end + end, + SetCommand = function(self, params) + if params.score then + -- include stepstype in the results string + local stStr = "" + if params.steps then + stStr = THEME:GetString("StepsDisplay StepsType", ToEnumShortString(params.steps:GetStepsType())) .. " " + end + + -- only the most recent score can cause this + -- but view eval/replays also are mostrecentscores, so check the name + -- the name is set for scores set during this session + if mostRecentScore and params.score:GetName() == "#P1#" and params.score:GetScoreKey() == mostRecentScore:GetScoreKey() then + self:settext(stStr.."Results") + else + self:settext(stStr.."Replay Results") + end + end + end + }, + Def.ActorFrame { + Name = "BasicSongInfo", + InitCommand = function(self) + -- children y pos relative to the divider + -- dont get confused + self:xy(actuals.RightHalfLeftGap, actuals.RightHorizontalDivider1UpperGap) + end, + + LoadFont("Common Large") .. { + Name = "SongTitle", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(-actuals.SongTitleLowerGap) + self:zoom(songInfoTextSize) + -- allow 3/4 the width of the area + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 4 * 3 / songInfoTextSize - textzoomFudge) + self:settext(GAMESTATE:GetCurrentSong():GetDisplayMainTitle()) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + LoadFont("Common Large") .. { + Name = "SongArtist", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(-actuals.SongArtistLowerGap) + self:zoom(songInfoTextSize) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 2 / songInfoTextSize - textzoomFudge) + self:settext(GAMESTATE:GetCurrentSong():GetDisplayArtist()) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + LoadFont("Common Large") .. { + Name = "SongPack", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(-actuals.SongPackLowerGap) + self:zoom(songInfoTextSize) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 2 / songInfoTextSize - textzoomFudge) + self:settext(GAMESTATE:GetCurrentSong():GetGroupName()) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + LoadFont("Common Large") .. { + Name = "SongRate", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(-actuals.SongRateLowerGap) + self:zoom(songInfoTextSize) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 2 / songInfoTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.score then + local r = params.score:GetMusicRate() + local rstr = getRateString(r) + self:settext(rstr) + end + end + }, + }, + Def.ActorFrame { + Name = "ScoreSpecificInfo", + InitCommand = function(self) + -- children y pos relative to the divider + -- dont get confused + self:xy(actuals.RightHalfLeftGap + actuals.RightHalfRightAlignLeftGap, actuals.RightHorizontalDivider1UpperGap) + end, + + LoadFont("Common Large") .. { + Name = "Grade", + InitCommand = function(self) + self:halign(1):valign(1) + self:y(-actuals.GradeLowerGap) + self:zoom(scoreInfoTextSize) + -- allow 1/4 of the area (opposite of the title maxwidth) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 4 / scoreInfoTextSize - textzoomFudge) + end, + SetCommand = function(self, params) + if params.score ~= nil then + local percent = params.score:GetWifeScore() * 100 + if params.judgeSetting ~= nil then + local rescoreTable = gatherRescoreTableFromScore(params.score) + percent = getRescoredWife3Judge(3, params.judgeSetting, rescoreTable) + end + local grade = GetGradeFromPercent(percent / 100) + + local gra = THEME:GetString("Grade", ToEnumShortString(grade)) + self:settext(gra) + self:diffuse(colorByGrade(grade)) + else + self:settext("") + end + end + }, + + -- to allow hovering for higher precision, wrap this in a function + -- we use the function to store the numbers in order to change precision on the fly + -- and that way we can store any number from any source and just change precision on it + -- (only because i dont want to make the variables for it global: it just feels cleaner that way) + wifePercentDisplay() .. { + InitCommand = function(self) + self:y(-actuals.WifePercentLowerGap) + end + }, + + LoadFont("Common Large") .. { + Name = "MSDSSRDiff", + InitCommand = function(self) + self:halign(1):valign(1) + self:y(-actuals.MSDInfoLowerGap) + self:zoom(scoreInfoTextSize) + self:maxwidth(actuals.RightHalfRightAlignLeftGap / 2 / scoreInfoTextSize - textzoomFudge) + end, + SetCommand = function(self, params) + if params.steps ~= nil then + local msd = params.steps:GetMSD(params.score:GetMusicRate(), 1) + local msdstr = string.format("%5.2f", msd) + local diff = getShortDifficulty(getDifficulty(params.steps:GetDifficulty())) + local diffcolor = colorByDifficulty(GetCustomDifficulty(params.steps:GetStepsType(), params.steps:GetDifficulty())) + local ssr = params.score:GetSkillsetSSR("Overall") + local ssrstr = string.format("%5.2f", ssr) + self:settextf("%s ~ %s %s", msdstr, ssrstr, diff) + self:ClearAttributes() + self:diffuse(COLORS:getMainColor("PrimaryText")) + self:diffusealpha(1) + self:AddAttribute(0, {Length = #msdstr, Zoom = scoreInfoTextSize, Diffuse = colorByMSD(msd)}) + self:AddAttribute(#msdstr + #" ~ ", {Length = #ssrstr, Zoom = scoreInfoTextSize, Diffuse = colorByMSD(tonumber(ssrstr))}) + self:AddAttribute(#msdstr + #" ~ " + #ssrstr, {Length = -1, Zoom = scoreInfoTextSize, Diffuse = diffcolor}) + else + self:settext("") + end + end + } + }, + LoadActorWithParams("scoreBoard.lua", { + Width = actuals.RightHorizontalDividerLength, + Height = actuals.ScoreBoardHeight, + DividerThickness = actuals.DividerThickness, + ItemCount = 4, + }) .. { + InitCommand = function(self) + self:xy(actuals.RightHalfLeftGap, actuals.RightHorizontalDivider1UpperGap + actuals.DividerThickness) + end + }, + LoadActorWithParams("../offsetplot.lua", {sizing = {Width = actuals.OffsetPlotWidth, Height = actuals.OffsetPlotHeight}, extraFeatures = true}) .. { + InitCommand = function(self) + self:xy(actuals.RightHalfLeftGap, actuals.OffsetPlotUpperGap) + end, + SetCommand = function(self, params) + if params.score ~= nil and params.steps ~= nil then + if params.score:HasReplayData() then + local offsets = params.score:GetOffsetVector() + -- for online offset vectors a 180 offset is a miss + for i, o in ipairs(offsets) do + if o >= 180 then + offsets[i] = 1000 + end + end + local tracks = params.score:GetTrackVector() + local types = params.score:GetTapNoteTypeVector() + local noterows = params.score:GetNoteRowVector() + local holds = params.score:GetHoldNoteVector() + local timingdata = params.steps:GetTimingData() + local lastSecond = params.steps:GetLastSecond() + + self:playcommand("LoadOffsets", { + offsetVector = offsets, + trackVector = tracks, + timingData = timingdata, + noteRowVector = noterows, + typeVector = types, + holdVector = holds, + maxTime = lastSecond, + judgeSetting = params.judgeSetting, + columns = params.steps:GetNumColumns(), + rejudged = params.rejudged, + }) + end + end + end + } +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/scoreBoard.lua b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/scoreBoard.lua new file mode 100644 index 0000000000..5cf8f0175e --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenEvaluation decorations/scoreBoard.lua @@ -0,0 +1,937 @@ +local t = Def.ActorFrame { + Name = "ScoreBoardFrame", +} + +local ratios = { + VerticalDividerLeftGap = 131 / 1920, -- from beginning of frame to left edge of divider + VerticalDividerUpperGap = 29 / 1080, -- from top of frame to top of divider + VerticalDividerLength = 250 / 1080, + LeftButtonLeftGap = 4 / 1920, -- left edge of frame to left edge of text + + LocalUpperGap = 28 / 1080, -- edge of frame to top of text + OnlineUpperGap = 77 / 1080, -- edge of frame to top of text + AllScoresUpperGap = 166 / 1080, -- again + TopScoresUpperGap = 198 / 1080, -- ... + CurrentRateUpperGap = 230 / 1080, + AllRatesUpperGap = 263 / 1080, + + ScoreListUpperGap = 26 / 1080, -- inner edge of divider to inner edge of glow of top item + ScoreListLeftGap = 158 / 1920, -- left edge of frame to inner edge of glow + + ScoreItemWidth = 778 / 1920, -- inner edge of glow to inner edge (updated to end at divider length) + ScoreItemHeight = 43 / 1080, + ScoreItemSpacing = 16 / 1080, -- distance between items + ScoreClearInfoSpace = 109 / 1920, -- about 14% of ScoreItemWidth + ScoreMetaInfoSpace = 560 / 1920, -- about 72% + ScorePlayerRateSpace = 109 / 1920, -- about 14% + + CursorVerticalSpan = 12 / 1080, -- visible cursor glow measured, doubled + CursorHorizontalSpan = 12 / 1920, -- same + + LeftButtonWidth = 128 / 1920, -- guesstimation of max text width + -- didnt measure height because it can be weird +} + +local actuals = { + VerticalDividerLeftGap = ratios.VerticalDividerLeftGap * SCREEN_WIDTH, + VerticalDividerUpperGap = ratios.VerticalDividerUpperGap * SCREEN_HEIGHT, + VerticalDividerLength = ratios.VerticalDividerLength * SCREEN_HEIGHT, + LeftButtonLeftGap = ratios.LeftButtonLeftGap * SCREEN_WIDTH, + LocalUpperGap = ratios.LocalUpperGap * SCREEN_HEIGHT, + OnlineUpperGap = ratios.OnlineUpperGap * SCREEN_HEIGHT, + AllScoresUpperGap = ratios.AllScoresUpperGap * SCREEN_HEIGHT, + TopScoresUpperGap = ratios.TopScoresUpperGap * SCREEN_HEIGHT, + CurrentRateUpperGap = ratios.CurrentRateUpperGap * SCREEN_HEIGHT, + AllRatesUpperGap = ratios.AllRatesUpperGap * SCREEN_HEIGHT, + ScoreListUpperGap = ratios.ScoreListUpperGap * SCREEN_HEIGHT, + ScoreListLeftGap = ratios.ScoreListLeftGap * SCREEN_WIDTH, + ScoreItemWidth = ratios.ScoreItemWidth * SCREEN_WIDTH, + ScoreItemHeight = ratios.ScoreItemHeight * SCREEN_HEIGHT, + ScoreItemSpacing = ratios.ScoreItemSpacing * SCREEN_HEIGHT, + ScoreClearInfoSpace = ratios.ScoreClearInfoSpace * SCREEN_WIDTH, + ScoreMetaInfoSpace = ratios.ScoreMetaInfoSpace * SCREEN_WIDTH, + ScorePlayerRateSpace = ratios.ScorePlayerRateSpace * SCREEN_WIDTH, + CursorVerticalSpan = ratios.CursorVerticalSpan * SCREEN_HEIGHT, + CursorHorizontalSpan = ratios.CursorHorizontalSpan * SCREEN_WIDTH, + LeftButtonWidth = ratios.LeftButtonWidth * SCREEN_WIDTH, +} + +-- we are expecting this file to be loaded with these params precalculated +-- in reference, Height is measured divider edge to divider edge +-- in reference, Width is the length of the horizontal divider +actuals.FrameWidth = Var("Width") +actuals.FrameHeight = Var("Height") +actuals.DividerThickness = Var("DividerThickness") +local itemCount = Var("ItemCount") +if itemCount == nil or itemCount < 0 then itemCount = 1 end + +-- we dont actually use this but we pass it back just to make things work smoothly +local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or GetTimingDifficulty()) + +local topButtonSize = 1 +local bottomButtonSize = 0.7 +local gradeSize = 0.8 +local clearTypeSize = 0.65 +local wifeJudgmentsSize = 0.6 +local dateSSRSize = 0.6 +local playerNameSize = 0.6 +local rateSize = 0.6 +local pageTextSize = 0.8 +local loadingTextSize = 0.8 +local textZoomFudge = 5 + +-- increase the highlight area height of the buttons +-- only for the left buttons, not the scoreitems +local buttonSizingFudge = 8 +local buttonHoverAlpha = 0.8 +local buttonRegularAlpha = 1 +local itemBGHoverAlpha = 0.2 +local itemBGHoverAnimationSeconds = 0.05 + +-- this can be nil if no scores exist in the profile +local mostRecentScore = SCOREMAN:GetMostRecentScore() + +-- when moving the cursor from one place to another +local cursorAnimationSeconds = 0.05 +-- when refreshing the score list +local scoreListAnimationSeconds = 0.05 + +t[#t+1] = Def.Quad { + Name = "VerticalDivider", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.DividerThickness, actuals.VerticalDividerLength) + self:xy(actuals.VerticalDividerLeftGap, actuals.VerticalDividerUpperGap) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +-- online and offline (default is offline) +local isLocal = true +-- current rate or not (default is current rate) +-- this is NOT the variable that controls the online scores +-- if isLocal is false, defer to allRates = not DLMAN:GetCurrentRateFilter() +-- but do not replace the variable +local allRates = false +-- all scores or top scores (online only) +-- dont have to modify this var with direct values, call DLMAN to update it +local allScores = not DLMAN:GetTopScoresOnlyFilter() + +-- indicate whether or not we are currently fetching the leaderboard +local fetchingScores = false + +-- this will distribute a given highscore to the offsetplot and the other eval elements +-- it will only work properly with a replay, so restrict it to replay-only scores +local function distributeScore(callerActor, highscore) + if highscore == nil or not highscore:HasReplayData() then return end + -- this highscore is an online score so must request ReplayData + if highscore:GetScoreKey():find("^Online_") ~= nil then + -- this gets replay data and calls the given function upon completion + DLMAN:RequestOnlineScoreReplayData(highscore, + function() + callerActor:GetParent():GetParent():playcommand("UpdateScore", {score = highscore, judgeSetting = judgeSetting}) + end) + else + -- otherwise we can immediately do the thing + callerActor:GetParent():GetParent():playcommand("UpdateScore", {score = highscore, judgeSetting = judgeSetting}) + end +end + +local function scoreList() + local page = 1 + local maxPage = 1 + local scorelistframe = nil + + local function movePage(n) + if maxPage <= 1 then + return + end + -- the tooltip gets stuck on if it is visible and page changes + TOOLTIP:Hide() + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + scorelistframe:playcommand("MovedPage") + end + + -- yes, we do have a country filter + -- we don't tell anyone + -- the Global country just shows everyone + local dlmanScoreboardCountryFilter = "Global" + + local mostRecentScore = SCOREMAN:GetMostRecentScore() + local selectedScorekey = mostRecentScore:GetScoreKey() + local scores = {} + + -- to prevent bombing the server repeatedly with leaderboard requests + local alreadyRequestedLeaderboard = false + + local t = Def.ActorFrame { + Name = "ScoreListFrame", + BeginCommand = function(self) + scorelistframe = self + end, + InitCommand = function(self) + self:xy(actuals.ScoreListLeftGap, actuals.ScoreListUpperGap) + end, + OnCommand = function(self) + self:playcommand("UpdateScores") + -- set the current page so we immediately see where our score is + if scores ~= nil and #scores > 0 then + local ind = 1 + for i, s in ipairs(scores) do + if s:GetScoreKey() == mostRecentScore:GetScoreKey() then + ind = i + break + end + end + page = 1 + math.floor((ind-1) / itemCount) + end + + self:playcommand("UpdateList") + end, + SetCommand = function(self, params) + -- only used to update the judge setting for the purpose of updating to the correct judge + -- when picking new scores in the list + if params.judgeSetting ~= nil then + judgeSetting = params.judgeSetting + end + end, + UpdateScoresCommand = function(self) + page = 1 + if isLocal then + local scoresByRate = getRateTable(getScoresByKey(PLAYER_1)) + + if allRates then + -- place every single score for the file into a table + scores = {} + for _, scoresAtRate in pairs(scoresByRate) do + for __, s in pairs(scoresAtRate) do + scores[#scores + 1] = s + end + end + -- sort it by Overall SSR + table.sort(scores, + function(a,b) + return a:GetSkillsetSSR("Overall") > b:GetSkillsetSSR("Overall") + end) + else + -- place only the scores for this rate into a table + -- it is already sorted by percent + -- the first half of this returns nil sometimes + -- particularly for scores that dont actually exist in your profile, like online replays + -- in those cases, we put the mostRecentScore into its own fallback table + -- mostRecentScore will never be nil. if it is, the game actually crashes before it gets here. + if scoresByRate == nil then + scores = {} + else + scores = scoresByRate[getRate(mostRecentScore)] or {mostRecentScore} + end + end + else + local steps = GAMESTATE:GetCurrentSteps() + -- operate with dlman scores + -- ... everything here is determined by internal bools set by the toggle buttons + scores = DLMAN:GetChartLeaderBoard(steps:GetChartKey(), dlmanScoreboardCountryFilter) + + -- if scores comes back nil, then the chart is unranked + if scores == nil then + if steps then + fetchingScores = false + end + return + end + + -- this is the initial request for the leaderboard which should only end up running once + -- and when it finishes, it loops back through this command + -- on the second passthrough, the leaderboard is hopefully filled out + if #scores == 0 then + if steps then + if not alreadyRequestedLeaderboard then + alreadyRequestedLeaderboard = true + fetchingScores = true + DLMAN:RequestChartLeaderBoardFromOnline( + steps:GetChartKey(), + function(leaderboard) + fetchingScores = false + self:queuecommand("UpdateScores") + self:queuecommand("UpdateList") + end + ) + end + end + end + end + end, + UpdateListCommand = function(self) + if scores == nil then + maxPage = 1 + else + maxPage = math.ceil(#scores / itemCount) + end + + self:GetChild("Cursor"):diffusealpha(0) + + for i = 1, itemCount do + local index = (page - 1) * itemCount + i + self:GetChild("ScoreItem_"..i):playcommand("SetScore", {scoreIndex = index}) + end + MESSAGEMAN:Broadcast("UpdateButtons") + end, + MovedPageCommand = function(self) + self:playcommand("UpdateList") + end, + ToggleCurrentRateMessageCommand = function(self) + if isLocal then + allRates = not allRates + else + DLMAN:ToggleRateFilter() + end + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end, + ToggleAllScoresMessageCommand = function(self) + DLMAN:ToggleTopScoresOnlyFilter() + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end, + ToggleLocalMessageCommand = function(self) + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end, + UpdateLoginStatusCommand = function(self) + -- handling any online status update + -- reset scores and list if in an impossible state + if not DLMAN:IsLoggedIn() and not isLocal then + isLocal = true + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + else + -- for this state, we just make sure the buttons are correct + -- we should be in local screen and offline/online + MESSAGEMAN:Broadcast("UpdateButtons") + end + end, + } + local function scoreItem(i) + local score = nil + local scoreIndex = i + + return Def.ActorFrame { + Name = "ScoreItem_"..i, + InitCommand = function(self) + self:y((i-1) * actuals.ScoreItemSpacing + (i-1) * actuals.ScoreItemHeight) + end, + ColorConfigUpdatedMessageCommand = function(self) + -- handles most colorings + self:playcommand("SetScore", {scoreIndex = scoreIndex}) + end, + SetScoreCommand = function(self, params) + scoreIndex = params.scoreIndex + -- nil scores table: unranked chart + if scores == nil then + score = nil + else + score = scores[scoreIndex] + end + self:finishtweening() + self:diffusealpha(0) + if score ~= nil then + self:smooth(scoreListAnimationSeconds * i) + self:diffusealpha(1) + if score:GetScoreKey() == selectedScorekey then + self:GetParent():GetChild("Cursor"):playcommand("SetIndex", {index = i}) + end + end + end, + + UIElements.QuadButton(1, 1) .. { + Name = "Button", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.ScoreItemWidth, actuals.ScoreItemHeight) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end, + MouseDownCommand = function(self) + if score ~= nil and score:HasReplayData() then + selectedScorekey = score:GetScoreKey() + self:GetParent():GetParent():GetChild("Cursor"):playcommand("SetIndex", {index = i}) + -- this actor great grandparent is the ScoreBoardFrame (the file level t) + distributeScore(self:GetParent():GetParent():GetParent(), score) + end + end, + MouseOverCommand = function(self) self:playcommand("RolloverUpdate",{update = "over"}) end, + MouseOutCommand = function(self) self:playcommand("RolloverUpdate",{update = "out"}) end, + RolloverUpdateCommand = function(self, params) + -- hovering + if self:GetParent():GetDiffuseAlpha() ~= 0 then + if params.update == "over" then + self:finishtweening() + self:smooth(itemBGHoverAnimationSeconds) + self:diffusealpha(itemBGHoverAlpha) + elseif params.update == "out" then + self:smooth(itemBGHoverAnimationSeconds) + self:diffusealpha(0) + end + end + + -- replay data tooltip + if score ~= nil and not score:HasReplayData() then + if params.update == "over" then + TOOLTIP:Show() + TOOLTIP:SetText("No Replay Data") + elseif params.update == "out" then + TOOLTIP:Hide() + end + end + end, + SetScoreCommand = function(self) + -- hovering + if self:GetParent():GetDiffuseAlpha() ~= 0 then + if isOver(self) then + self:finishtweening() + self:smooth(itemBGHoverAnimationSeconds) + self:diffusealpha(itemBGHoverAlpha) + else + self:diffusealpha(0) + end + end + + -- replay data tooltip + if score ~= nil and isOver(self) and not score:HasReplayData() then + TOOLTIP:Show() + TOOLTIP:SetText("No Replay Data") + elseif score ~= nil and isOver(self) and score:HasReplayData() then + TOOLTIP:Hide() + end + end + }, + LoadFont("Common Normal") .. { + Name = "Grade", + InitCommand = function(self) + self:xy(actuals.ScoreClearInfoSpace / 2, actuals.ScoreItemHeight / 4) + self:zoom(gradeSize) + self:maxwidth(actuals.ScoreClearInfoSpace / gradeSize - textZoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + local gra = THEME:GetString("Grade", ToEnumShortString(score:GetWifeGrade())) + self:settext(gra) + self:diffuse(colorByGrade(score:GetWifeGrade())) + end + end + }, + LoadFont("Common Normal") .. { + Name = "ClearType", + InitCommand = function(self) + self:xy(actuals.ScoreClearInfoSpace / 2, actuals.ScoreItemHeight / 4 * 3) + self:zoom(clearTypeSize) + self:maxwidth(actuals.ScoreClearInfoSpace / clearTypeSize - textZoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = getClearTypeFromScore(score, 0) + local color = getClearTypeFromScore(score, 2) + self:settext(txt) + self:diffuse(color) + end + end + }, + LoadFont("Common Normal") .. { + Name = "PercentAndJudgments", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.ScoreClearInfoSpace, actuals.ScoreItemHeight / 4) + self:zoom(wifeJudgmentsSize) + self:maxwidth(actuals.ScoreMetaInfoSpace / wifeJudgmentsSize - textZoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + -- when you want individual colors for every single judgment in a continuous string....... + local wifeStr = string.format("%05.2f%%", notShit.floor(score:GetWifeScore() * 10000) / 100) + local jgMaStr = tostring(score:GetTapNoteScore("TapNoteScore_W1")) + local jgPStr = tostring(score:GetTapNoteScore("TapNoteScore_W2")) + local jgGrStr = tostring(score:GetTapNoteScore("TapNoteScore_W3")) + local jgGoStr = tostring(score:GetTapNoteScore("TapNoteScore_W4")) + local jgBStr = tostring(score:GetTapNoteScore("TapNoteScore_W5")) + local jgMiStr = tostring(score:GetTapNoteScore("TapNoteScore_Miss")) + self:ClearAttributes() + self:diffuse(COLORS:getMainColor("SecondaryText")) + self:diffusealpha(1) + self:settextf("%s | %s - %s - %s - %s - %s - %s", wifeStr, jgMaStr, jgPStr, jgGrStr, jgGoStr, jgBStr, jgMiStr) + -- could have probably used a loop to do this + self:AddAttribute(#string.format("%s | ", wifeStr), {Length = #jgMaStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_W1")}) + self:AddAttribute(#string.format("%s | %s - ", wifeStr, jgMaStr), {Length = #jgPStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_W2")}) + self:AddAttribute(#string.format("%s | %s - %s - ", wifeStr, jgMaStr, jgPStr), {Length = #jgGrStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_W3")}) + self:AddAttribute(#string.format("%s | %s - %s - %s - ", wifeStr, jgMaStr, jgPStr, jgGrStr), {Length = #jgGoStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_W4")}) + self:AddAttribute(#string.format("%s | %s - %s - %s - %s - ", wifeStr, jgMaStr, jgPStr, jgGrStr, jgGoStr), {Length = #jgBStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_W5")}) + self:AddAttribute(#string.format("%s | %s - %s - %s - %s - %s - ", wifeStr, jgMaStr, jgPStr, jgGrStr, jgGoStr, jgBStr), {Length = #jgMiStr, Zoom = wifeJudgmentsSize, Diffuse = colorByJudgment("TapNoteScore_Miss")}) + end + end + }, + LoadFont("Common Normal") .. { + Name = "DateAndSSR", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.ScoreClearInfoSpace, actuals.ScoreItemHeight / 4 * 3) + self:zoom(dateSSRSize) + self:maxwidth(actuals.ScoreMetaInfoSpace / dateSSRSize - textZoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + local date = score:GetDate() + local m, d, y = expandDateString(date) + local dstr = string.format("%s %s, %s", m, d, y) + local ssr = score:GetSkillsetSSR("Overall") + local ssrStr = string.format("%05.2f", ssr) + self:ClearAttributes() + self:diffuse(COLORS:getMainColor("SecondaryText")) + self:diffusealpha(1) + self:settextf("%s | %s", ssrStr, dstr) + self:AddAttribute(0, {Length = #ssrStr, Zoom = dateSSRSize, Diffuse = colorByMSD(ssr)}) + end + end + }, + LoadFont("Common Normal") .. { + Name = "PlayerName", + InitCommand = function(self) + self:xy(actuals.ScoreItemWidth - actuals.ScorePlayerRateSpace / 2, actuals.ScoreItemHeight / 4) + self:zoom(playerNameSize) + self:maxwidth(actuals.ScorePlayerRateSpace / playerNameSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local n = score:GetName() + if n == "" then + if isLocal then + n = "You" + end + elseif n == "#P1#" then + if score:GetScoreKey() == mostRecentScore:GetScoreKey() then + n = "Last Score" + else + n = "You" + end + end + self:settext(n) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + self:xy(actuals.ScoreItemWidth - actuals.ScorePlayerRateSpace / 2, actuals.ScoreItemHeight / 4 * 3) + self:zoom(rateSize) + self:maxwidth(actuals.ScorePlayerRateSpace / rateSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local rt = score:GetMusicRate() + self:settext(getRateString(rt)) + end + end + } + } + end + t[#t+1] = Def.Sprite { + Name = "Cursor", + Texture = THEME:GetPathG("", "scoreboardGlow"), + InitCommand = function(self) + self:halign(0):valign(0) + self:x(-actuals.CursorHorizontalSpan / 2) + self:zoomto(actuals.ScoreItemWidth + actuals.CursorHorizontalSpan, actuals.ScoreItemHeight + actuals.CursorVerticalSpan) + self:diffusealpha(0) + end, + SetIndexCommand = function(self, params) + local i = params.index + if scores[i] == nil then + self:diffusealpha(0) + else + self:finishtweening() + if self:GetDiffuseAlpha() ~= 0 then + self:linear(cursorAnimationSeconds) + else + self:diffusealpha(1) + end + self:y((i-1) * actuals.ScoreItemSpacing + (i-1) * actuals.ScoreItemHeight - actuals.CursorVerticalSpan / 2) + end + end, + } + + for i = 1, itemCount do + t[#t+1] = scoreItem(i) + end + + t[#t+1] = Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + -- have to offset by the parent position... + self:xy(-actuals.ScoreListLeftGap, -actuals.ScoreListUpperGap) + self:diffusealpha(0) + self:zoomto(actuals.FrameWidth, actuals.FrameHeight) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end + } + + t[#t+1] = LoadFont("Common Normal") .. { + Name = "LoadingText", + InitCommand = function(self) + self:valign(0) + self:x(actuals.ScoreItemWidth / 2) + self:maxwidth(actuals.ScoreItemWidth / loadingTextSize - textZoomFudge) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + self:finishtweening() + self:smooth(scoreListAnimationSeconds) + local steps = GAMESTATE:GetCurrentSteps() + + if isLocal then + if scores ~= nil and #scores == 0 then + self:diffusealpha(1) + self:settext("No local scores recorded") + else + self:diffusealpha(0) + self:settext("") + end + return + end + + if scores == nil then + self:diffusealpha(1) + self:settext("Chart is unranked") + elseif #scores == 0 and steps and fetchingScores == true then + self:diffusealpha(1) + self:settext("Fetching scores...") + elseif #scores == 0 and steps and fetchingScores == false then + self:diffusealpha(1) + self:settext("No online scores recorded") + else + self:diffusealpha(0) + self:settext("") + end + end + } + + t[#t+1] = LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:valign(1) + self:xy(actuals.ScoreItemWidth / 2, actuals.VerticalDividerLength) + self:zoom(pageTextSize) + self:maxwidth(actuals.ScoreItemWidth / pageTextSize - textZoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + -- nil scores = no scores + if scores == nil then + self:settext("0-0/0") + return + end + + local lb = clamp((page-1) * (itemCount) + 1, 0, #scores) + local ub = clamp(page * itemCount, 0, #scores) + self:settextf("Showing %d-%d of %d scores", lb, ub, #scores) + end + } + + return t +end + + + +t[#t+1] = Def.ActorFrame { + Name = "LeftButtons", + InitCommand = function(self) + self:x(actuals.LeftButtonLeftGap) + end, + UpdateButtonsMessageCommand = function(self) + allScores = not DLMAN:GetTopScoresOnlyFilter() + self:playcommand("UpdateToggleStatus") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateToggleStatus") + end, + + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "LocalButton", + InitCommand = function(self) + self:y(actuals.LocalUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(topButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / topButtonSize - textZoomFudge) + txt:settext("Local") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + end, + UpdateToggleStatusCommand = function(self) + -- lit when isLocal is true + if not isLocal then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if params.update == "OnMouseDown" then + isLocal = true + MESSAGEMAN:Broadcast("ToggleLocal") + end + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "OnlineButton", + InitCommand = function(self) + self:y(actuals.OnlineUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(topButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / topButtonSize - textZoomFudge) + txt:settext("Online") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + self:playcommand("UpdateToggleStatus") + end, + UpdateToggleStatusCommand = function(self) + if not DLMAN:IsLoggedIn() then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + + -- lit when isLocal is false + if isLocal then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + isLocal = false + MESSAGEMAN:Broadcast("ToggleLocal") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "AllScoresButton", + InitCommand = function(self) + self:y(actuals.AllScoresUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(bottomButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / bottomButtonSize - textZoomFudge) + txt:settext("All Scores") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + end, + UpdateToggleStatusCommand = function(self) + -- invisible if local + if isLocal then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + -- lit if allScores is true + if not allScores then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + MESSAGEMAN:Broadcast("ToggleAllScores") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "TopScoresButton", + InitCommand = function(self) + self:y(actuals.TopScoresUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(bottomButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / bottomButtonSize - textZoomFudge) + txt:settext("Top Scores") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + end, + UpdateToggleStatusCommand = function(self) + -- invisible if local + if isLocal then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + -- lit if allScores is false + if allScores then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + MESSAGEMAN:Broadcast("ToggleAllScores") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "CurrentRateButton", + InitCommand = function(self) + self:y(actuals.CurrentRateUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(bottomButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / bottomButtonSize - textZoomFudge) + txt:settext("Current Rate") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + end, + UpdateToggleStatusCommand = function(self) + -- lit if allRates is false + if (allRates and isLocal) or (not DLMAN:GetCurrentRateFilter() and not isLocal) then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if params.update == "OnMouseDown" then + MESSAGEMAN:Broadcast("ToggleCurrentRate") + end + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "AllRatesButton", + InitCommand = function(self) + self:y(actuals.AllRatesUpperGap) + local txt = self:GetChild("Text") + txt:valign(0):halign(0) + txt:zoom(bottomButtonSize) + txt:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftButtonLeftGap) / bottomButtonSize - textZoomFudge) + txt:settext("All Rates") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + local bg = self:GetChild("BG") + bg:valign(0):halign(0) + bg:zoomto(actuals.LeftButtonWidth, txt:GetZoomedHeight() + buttonSizingFudge) + bg:y(-buttonSizingFudge / 2) + end, + UpdateToggleStatusCommand = function(self) + -- lit if allRates is true + if (not allRates and isLocal) or (DLMAN:GetCurrentRateFilter() and not isLocal) then + self:GetChild("Text"):strokecolor(color("0,0,0,0")) + else + self:GetChild("Text"):strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.75)) + end + end, + ClickCommand = function(self, params) + if params.update == "OnMouseDown" then + MESSAGEMAN:Broadcast("ToggleCurrentRate") + end + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + +} + +t[#t+1] = scoreList() + + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenEvaluation overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenEvaluation overlay/default.lua new file mode 100644 index 0000000000..9676cc618d --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenEvaluation overlay/default.lua @@ -0,0 +1,14 @@ +local t = Def.ActorFrame {Name = "OverlayFile"} + +t[#t+1] = LoadActor("../_mouse.lua") + +-- header +t[#t+1] = LoadActorWithParams("../playerInfoFrame/main.lua", {screen = "ScreenEvaluationNormal"}) + +-- footer +t[#t+1] = LoadActorWithParams("../footer.lua", { + Width = SCREEN_WIDTH, + Height = 50 / 1080 * SCREEN_HEIGHT, +}) + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenEvaluation underlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenEvaluation underlay/default.lua new file mode 100644 index 0000000000..8648ab226e --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenEvaluation underlay/default.lua @@ -0,0 +1,30 @@ +local t = Def.ActorFrame { + Name = "UnderlayFile", + OnCommand = function(self) + -- go + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong()}) + end +} + +t[#t+1] = Def.Sprite { + Name = "BG", + InitCommand = function(self) + self:valign(0):halign(0) + self:scaletocover(0, 0, SCREEN_WIDTH, SCREEN_BOTTOM) + self:diffusealpha(0) + end, + SetCommand = function(self, params) + self:finishtweening() + if params.song and params.song:GetBackgroundPath() then + self:visible(true) + self:LoadBackground(params.song:GetBackgroundPath()) + self:scaletocover(0, 0, SCREEN_WIDTH, SCREEN_BOTTOM) + self:smooth(0.5) + self:diffusealpha(0.3) + else + self:visible(false) + end + end +} + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay failed.redir b/Themes/Rebirth/BGAnimations/ScreenGameplay failed.redir new file mode 100644 index 0000000000..51f3c31129 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay failed.redir @@ -0,0 +1 @@ +_fadeout \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay out.redir b/Themes/Rebirth/BGAnimations/ScreenGameplay out.redir new file mode 100644 index 0000000000..477d9e6229 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay out.redir @@ -0,0 +1 @@ +_fadelong \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/default.lua new file mode 100644 index 0000000000..e80f6072b4 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/default.lua @@ -0,0 +1,6 @@ +local t = Def.ActorFrame {Name = "GameplayOverlayDefaultFile"} + +t[#t+1] = LoadActor("elements") +t[#t+1] = LoadActor("titlesplash") + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaycustomization.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaycustomization.lua new file mode 100644 index 0000000000..a9de48239f --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaycustomization.lua @@ -0,0 +1,705 @@ +-- gameplay customization +-- assume that customization is active if this file is loaded + +local snm = Var("LoadingScreen") + +local ratios = { + MenuHeight = 500 / 1080, + MenuWidth = 600 / 1920, + MenuDraggerHeight = 75 / 1080, + EdgePadding = 10 / 1920, +} +local actuals = { + MenuHeight = ratios.MenuHeight * SCREEN_HEIGHT, + MenuWidth = ratios.MenuWidth * SCREEN_WIDTH, + MenuDraggerHeight = ratios.MenuDraggerHeight * SCREEN_HEIGHT, + EdgePadding = ratios.EdgePadding * SCREEN_WIDTH, +} + +local uiBGAlpha = 0.6 +local textButtonHeightFudgeScalarMultiplier = 1.4 +local buttonHoverAlpha = 0.6 +local cursorAlpha = 0.5 +local cursorAnimationSeconds = 0.05 + +local elementNameTextSize = 1 +local elementCoordTextSize = 1 +local elementSizeTextSize = 1 +local elementListTextSize = 1 +local uiInstructionTextSize = 0.6 + +local t = Def.ActorFrame { + Name = "GameplayElementsCustomizer", + InitCommand = function(self) + -- in the off chance we end up in customization when in syncmachine, dont turn on autoplay + if snm ~= "ScreenGameplaySyncMachine" then + GAMESTATE:SetAutoplay(true) + else + GAMESTATE:SetAutoplay(false) + end + end, + BeginCommand = function(self) + local screen = SCREENMAN:GetTopScreen() + + local lifebar = screen:GetLifeMeter(PLAYER_1) + local nf = screen:GetChild("PlayerP1"):GetChild("NoteField") + local noteColumns = nf:get_column_actors() + + registerActorToCustomizeGameplayUI({ + actor = lifebar, + coordInc = {5,1}, + rotationInc = {5,1}, + sizeInc = {0.1, 0.05}, + }) + registerActorToCustomizeGameplayUI({ + actor = nf, + coordInc = {5,1}, + sizeInc = {0.1, 0.05}, + spacingInc = {5,1}, + }, 4) + end, + EndCommand = function(self) + -- exiting customize gameplay will turn off autoplay + GAMESTATE:SetAutoplay(false) + end, +} + +local function makeUI() + local itemsPerPage = 8 + local itemListFrame = nil + + -- init moved to OnCommand due to timing reasons + local elements = {} + local page = 1 + local maxPage = 1 + + local cursorPos = 1 + local selectedElement = nil + local selectedElementCoords = {} + local selectedElementSizes = {} + -- valid choices: "Coordinate", "Zoom" (zoomx), "Size" (zoomto), "Spacing", "Rotation" + local selectedElementMovementType = "" + + local allowedSpace = actuals.MenuHeight - actuals.MenuDraggerHeight - (actuals.EdgePadding*2) + local topItemY = -actuals.MenuHeight/2 + actuals.MenuDraggerHeight + actuals.EdgePadding + + -- get the next element movement type + -- provide curr to set a starting point to search + -- will run recursively until it finds a working match + local function getNextElementMovementType(curr, recurses) + if selectedElementMovementType == nil then setSelectedElementMovementType() return selectedElementMovementType end + if curr == nil then curr = selectedElementMovementType end + if recurses == nil then recurses = 1 end + if recurses > 10 then return nil end + if curr == "Coordinate" then + if selectedElementCoords ~= nil and selectedElementCoords.rotation ~= nil then + return "Rotation" + end + return getNextElementMovementType("Rotation", recurses + 1) + end + if curr == "Rotation" then + if selectedElementSizes ~= nil and selectedElementSizes.zoom ~= nil then + return "Zoom" + end + return getNextElementMovementType("Zoom", recurses + 1) + end + if curr == "Zoom" then + if selectedElementSizes ~= nil and selectedElementSizes.width ~= nil or selectedElementSizes.height ~= nil then + return "Size" + end + return getNextElementMovementType("Size", recurses + 1) + end + if curr == "Size" then + if selectedElementSizes ~= nil and selectedElementSizes.spacing ~= nil then + return "Spacing" + end + return getNextElementMovementType("Spacing", recurses + 1) + end + if curr == "Spacing" then + if selectedElementCoords ~= nil and selectedElementCoords.x ~= nil or selectedElementCoords.y ~= nil then + return "Coordinate" + end + return getNextElementMovementType("Coordinate", recurses + 1) + end + end + + -- set the selected element movement type + -- if no param is given, set the default (first available) + -- if param is given, find the next one available (i know, unintuitive, dont care) + local function setSelectedElementMovementType(movementType) + if movementType == nil then + if selectedElementCoords ~= nil then + if selectedElementCoords.x ~= nil or selectedElementCoords.y ~= nil then + selectedElementMovementType = "Coordinate" + return + end + if selectedElementCoords.rotation ~= nil then + selectedElementMovementType = "Rotation" + return + end + end + if selectedElementSizes ~= nil then + if selectedElementSizes.zoom ~= nil then + selectedElementMovementType = "Zoom" + return + end + if selectedElementSizes.width ~= nil or selectedElementSizes.height ~= nil then + selectedElementMovementType = "Size" + return + end + if selectedElementSizes.spacing ~= nil then + selectedElementMovementType = "Spacing" + return + end + end + -- impossible?? + ms.ok("Selected element movement type could not be determined. Report to developer") + else + selectedElementMovementType = getNextElementMovementType(movementType) + end + end + + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + local currentPageLowerBound = (page-1) * itemsPerPage + 1 + local currentPageUpperBound = page * itemsPerPage + if cursorPos < currentPageLowerBound then + cursorPos = currentPageLowerBound + if itemListFrame ~= nil then + itemListFrame:queuecommand("UpdateCursor") + end + elseif cursorPos > currentPageUpperBound then + cursorPos = currentPageUpperBound + if itemListFrame ~= nil then + itemListFrame:queuecommand("UpdateCursor") + end + end + + if itemListFrame ~= nil then + itemListFrame:playcommand("UpdateItemList") + end + end + + local function moveCursor(n) + cursorPos = cursorPos + n + if cursorPos > #elements then + cursorPos = 1 + page = 1 + if maxPage ~= 1 and itemListFrame ~= nil then + itemListFrame:playcommand("UpdateItemList") + end + elseif cursorPos < 1 then + cursorPos = #elements + page = maxPage + if maxPage ~= 1 and itemListFrame ~= nil then + itemListFrame:playcommand("UpdateItemList") + end + end + + local currentPageLowerBound = (page-1) * itemsPerPage + 1 + local currentPageUpperBound = page * itemsPerPage + if cursorPos < currentPageLowerBound then + -- lazy: move only 1 page but its possible to move many pages + page = page-1 + if itemListFrame ~= nil then + itemListFrame:playcommand("UpdateItemList") + end + elseif cursorPos > currentPageUpperBound then + -- lazy: move only 1 page but its possible to move many pages + page = page+1 + if itemListFrame ~= nil then + itemListFrame:playcommand("UpdateItemList") + end + end + + if itemListFrame ~= nil then + itemListFrame:playcommand("UpdateCursor") + end + end + + local function visibilityBySelectedElement(self, reverse) + if reverse then + self:visible(selectedElement ~= nil) + else + self:visible(selectedElement == nil) + end + end + + local function updateSelectedElementValues() + if selectedElement == nil then return end + selectedElementCoords = getCoordinatesForElementName(selectedElement) + selectedElementSizes = getSizesForElementName(selectedElement) + end + + local function selectCurrent() + selectedElement = elements[cursorPos]:GetName() + updateSelectedElementValues() + setSelectedElementMovementType() + if selectedElement == "Screen" then + setSelectedCustomizeGameplayElementActor(SCREENMAN:GetTopScreen(), "Screen") + else + setSelectedCustomizeGameplayElementActorByName(selectedElement) + end + setStoredStateForUndoAction(selectedElement) + itemListFrame:playcommand("UpdateItemList") + end + + local function item(i) + local index = i + local element = elements[index] + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Item_"..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + txt:zoom(elementListTextSize) + txt:maxwidth((actuals.MenuWidth-(actuals.EdgePadding*2)) / elementListTextSize) + bg:halign(0) + self:x(-actuals.MenuWidth + actuals.EdgePadding) + self:y(topItemY + (allowedSpace / itemsPerPage) * (i-1) + (allowedSpace / itemsPerPage / 2)) + + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + local visiblemultiplier = self:IsInvisible() and 0 or 1 + self:diffusealpha(1 * hovermultiplier * visiblemultiplier) + end + end, + SetItemCommand = function(self) + visibilityBySelectedElement(self) + + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + index = (page - 1) * itemsPerPage + i + element = elements[index] + if element ~= nil then + self:diffusealpha(1) + txt:settext(element:GetName()) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + else + self:diffusealpha(0) + end + end, + UpdateCursorCommand = function(self) + if index == cursorPos then + local cursor = self:GetParent():GetChild("Cursor") + cursor:finishtweening() + cursor:smooth(cursorAnimationSeconds) + cursor:xy(self:GetX(), self:GetY()) + local bg = self:GetChild("BG") + cursor:zoomto(bg:GetZoomedWidth(), bg:GetZoomedHeight()) + cursor:diffusealpha(cursorAlpha) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if selectedElement == nil then + cursorPos = index + self:playcommand("UpdateCursor") + end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + cursorPos = index + selectCurrent() + self:playcommand("UpdateCursor") + self:alphaDeterminingFunction() + end + end, + } + end + + local t = Def.ActorFrame { + Name = "ItemListContainer", + InitCommand = function(self) + itemListFrame = self + -- make container above all other button elements + -- not guaranteed but this works for now + self:z(10) + end, + EndCommand = function(self) + -- triggered immediately before screen deletion + SCREENMAN:set_input_redirected(PLAYER_1, false) + end, + OnCommand = function(self) + -- these are initialized here because most elements either need BeginCommand or InitCommand to run for them to be registered + -- Order of execution: Init -> Begin -> On + elements = getCustomizeGameplayElements() + -- hacky addition of the Screen element, which affects the entire screen zoom and X/Y + elements[#elements+1] = { + GetName = function(self) return "Screen" end, + } + table.sort( + elements, + function(a, b) + return a:GetName():lower() < b:GetName():lower() + end + ) + maxPage = math.ceil(#elements / itemsPerPage) + + -- lock input events to only lua, no c++ + SCREENMAN:set_input_redirected(PLAYER_1, true) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type ~= "InputEventType_Release" then -- allow Repeat and FirstPress + local gameButton = event.button + local key = event.DeviceInput.button + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local ctrl = INPUTFILTER:IsBeingPressed("left ctrl") or INPUTFILTER:IsBeingPressed("right ctrl") + local shift = INPUTFILTER:IsBeingPressed("left shift") or INPUTFILTER:IsBeingPressed("right shift") + local space = key == "DeviceButton_space" + + -- these inputs shouldnt repeat just to prevent being annoying + local enter = (gameButton == "Start") + and event.type == "InputEventType_FirstPress" + local back = (gameButton == "Back") + and event.type == "InputEventType_FirstPress" + local undo = (key == "DeviceButton_delete" or gameButton == "RestartGameplay" or key == "DeviceButton_backspace") + and event.type == "InputEventType_FirstPress" + + -- handled in a really special way to prevent holding left mouse and right clicking + -- stop trying to break things + local rightclick = (key == "DeviceButton_right mouse button") + and event.type == "InputEventType_FirstPress" and not INPUTFILTER:IsBeingPressed("left mouse button", "Mouse") + + -- exit + if back then + -- (why did we make a specific function for this instead of :Cancel() ?) + SCREENMAN:GetTopScreen():begin_backing_out() + return true + end + + if selectedElement ~= nil then + if up or down or left or right then + local info = getInfoForSelectedGameplayElement() + -- mega hack (sign of bad design) + -- if the screen is selected, fake an info object + if info == nil then + info = { + coordInc = {5,1}, + zoomInc = {0.05,0.01}, + } + end + + local bigIncrement = true + local reverse = false + if shift then + -- small increment arrow key usage + bigIncrement = false + else + -- regular arrow key usage + end + + if down or left then reverse = true end + local tname = selectedElement + + if selectedElementMovementType == "Coordinate" then + if selectedElementCoords ~= nil then + local increment = getCoordinc(info, bigIncrement, reverse) + if left or right then tname = tname .. "X" end + if up or down then tname = tname .. "Y" increment = increment * -1 end + updateGameplayCoordinate(tname, increment) + end + elseif selectedElementMovementType == "Rotation" then + if selectedElementCoords ~= nil then + local increment = getRotationinc(info, bigIncrement, reverse) + tname = tname .. "Rotation" + updateGameplayCoordinate(tname, increment) + end + elseif selectedElementMovementType == "Zoom" then + if selectedElementSizes ~= nil then + local increment = getZoominc(info, bigIncrement, reverse) + tname = tname .. "Zoom" + updateGameplaySize(tname, increment) + end + elseif selectedElementMovementType == "Size" then + if selectedElementSizes ~= nil then + local increment = getZoominc(info, bigIncrement, reverse) + if left or right then tname = tname .. "Width" end + if up or down then tname = tname .. "Height" end + updateGameplaySize(tname, increment) + end + elseif selectedElementMovementType == "Spacing" then + if selectedElementSizes ~= nil then + local increment = getSpacinginc(info, bigIncrement, reverse) + tname = tname .. "Spacing" + updateGameplaySize(tname, increment) + end + end + + elseif space then + -- go to next element movement type + setSelectedElementMovementType(selectedElementMovementType) + -- set a checkpoint for undo + setStoredStateForUndoAction(selectedElement) + self:playcommand("UpdateItemList") + elseif undo then + if ctrl then + -- reset to default + resetElementToDefault() + setStoredStateForUndoAction(selectedElement) + else + -- undo changes and return + resetElementUsingStoredState() + selectedElement = nil + self:playcommand("UpdateItemList") + end + elseif enter or rightclick then + -- save position and return + -- the reality is all positions are saved always haha + -- just go back in this case + -- save to disk will occur at screen exit + selectedElement = nil + self:playcommand("UpdateItemList") + end + else + if up or left then + -- up + moveCursor(-1) + elseif down or right then + -- down + moveCursor(1) + elseif enter then + -- select element + selectCurrent() + end + end + + -- let all mouse inputs through + if event.DeviceInput.button:find("mouse") ~= nil then + return false + end + -- eat all other inputs to not let duplicates get through + return true + end + + -- let all mouse inputs through + if event.DeviceInput.button:find("mouse") ~= nil then + return false + end + return true + end) + self:playcommand("UpdateItemList") + self:playcommand("UpdateCursor") + self:finishtweening() + end, + UpdateItemListCommand = function(self) + self:playcommand("SetItem") + end, + CustomizeGameplayElementSelectedMessageCommand = function(self, params) + if params == nil or params.name == nil then return end + + local name = params.name + selectedElement = name + updateSelectedElementValues() + setSelectedElementMovementType() + setStoredStateForUndoAction(name) + self:playcommand("UpdateItemList") + end, + CustomizeGameplayElementMovedMessageCommand = function(self, params) + if params == nil or params.name == nil then return end + + local name = params.name + updateSelectedElementValues() + self:playcommand("UpdateItemInfo") + end, + CustomizeGameplayElementUndoMessageCommand = function(self, params) + if params == nil or params.name == nil then return end + + updateSelectedElementValues() + self:playcommand("UpdateItemInfo") + end, + CustomizeGameplayElementDefaultedMessageCommand = function(self, params) + self:playcommand("CustomizeGameplayElementUndo", params) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(1) + self:zoomto(actuals.MenuWidth, actuals.MenuHeight) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + self:diffusealpha(uiBGAlpha) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + self:GetParent():playcommand("UpdateItemList") + end + end + }, + UIElements.QuadButton(1) .. { + Name = "DraggableLip", + InitCommand = function(self) + self:halign(1):valign(0) + self:y(-actuals.MenuHeight / 2) + self:zoomto(actuals.MenuWidth, actuals.MenuDraggerHeight) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + self:diffusealpha(uiBGAlpha) + end, + MouseDragCommand = function(self, params) + local newx = params.MouseX - (self.initialClickX or 0) + local newy = params.MouseY - (self.initialClickY or 0) + self:GetParent():addx(newx):addy(newy) + end, + MouseDownCommand = function(self, params) + self.initialClickX = params.MouseX + self.initialClickY = params.MouseY + end, + }, + Def.ActorFrame { + Name = "SelectedElementPage", + InitCommand = function(self) + self:y(topItemY + (allowedSpace / itemsPerPage / 2)) + self:x(-actuals.MenuWidth/2) + end, + UpdateItemListCommand = function(self) + visibilityBySelectedElement(self, true) + if selectedElement ~= nil then + self:playcommand("UpdateItemInfo") + end + end, + UpdateItemInfoCommand = function(self) + -- line management + self.cl = 0 + self.sl = 0 + if selectedElementCoords["x"] then self.cl = self.cl + 1 end + if selectedElementCoords["y"] then self.cl = self.cl + 1 end + if selectedElementCoords["rotation"] then self.cl = self.cl + 1 end + if selectedElementSizes["zoom"] then self.sl = self.sl + 1 end + if selectedElementSizes["width"] then self.sl = self.sl + 1 end + if selectedElementSizes["height"] then self.sl = self.sl + 1 end + if selectedElementSizes["spacing"] then self.sl = self.sl + 1 end + end, + + LoadFont("Common Normal") .. { + Name = "ElementName", + InitCommand = function(self) + local line = 1 + self:valign(0) + self:y((allowedSpace / itemsPerPage) * (line - 1) - (allowedSpace / itemsPerPage)/2) + self:settext(" ") + self:maxwidth(actuals.MenuWidth / elementNameTextSize) + end, + UpdateItemInfoCommand = function(self) + self:settextf("%s", selectedElement) + end, + }, + LoadFont("Common Normal") .. { + Name = "CurrentCoordinates", + InitCommand = function(self) + local line = 2 + self:valign(0) + self:y((allowedSpace / itemsPerPage) * (line - 1) - (allowedSpace / itemsPerPage)/2) + self:settext(" ") + self:maxwidth(actuals.MenuWidth / elementCoordTextSize) + end, + UpdateItemInfoCommand = function(self, params) + local outstr = {} + if selectedElementCoords["x"] ~= nil then + local fstr = selectedElementMovementType == "Coordinate" and "[X: %5.2f]" or "X: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementCoords["x"]) + end + if selectedElementCoords["y"] ~= nil then + local fstr = selectedElementMovementType == "Coordinate" and "[Y: %5.2f]" or "Y: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementCoords["y"]) + end + if selectedElementCoords["rotation"] ~= nil then + local fstr = selectedElementMovementType == "Rotation" and "[Rotation: %5.2f]" or "Rotation: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementCoords["rotation"]) + end + self:settextf(table.concat(outstr, "\n")) + end, + }, + LoadFont("Common Normal") .. { + Name = "CurrentSizing", + InitCommand = function(self) + self:valign(0) + self:settext(" ") + self:maxwidth(actuals.MenuWidth / elementSizeTextSize) + end, + UpdateItemInfoCommand = function(self, params) + local outstr = {} + local coordLines = self:GetParent().cl + local coordactor = self:GetParent():GetChild("CurrentCoordinates") + self:y(coordactor:GetY() + coordactor:GetZoomedHeight() + (allowedSpace / itemsPerPage)/2) + + if selectedElementSizes["zoom"] ~= nil then + local fstr = selectedElementMovementType == "Zoom" and "[Zoom: %5.2f]" or "Zoom: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementSizes["zoom"]) + end + if selectedElementSizes["width"] ~= nil then + local fstr = selectedElementMovementType == "Size" and "[Width: %5.2f]" or "Width: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementSizes["width"]) + end + if selectedElementSizes["height"] ~= nil then + local fstr = selectedElementMovementType == "Size" and "[Height: %5.2f]" or "Height: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementSizes["height"]) + end + if selectedElementSizes["spacing"] ~= nil then + local fstr = selectedElementMovementType == "Spacing" and "[Spacing: %5.2f]" or "Spacing: %5.2f" + outstr[#outstr+1] = string.format(fstr, selectedElementSizes["spacing"]) + end + self:settextf(table.concat(outstr, "\n")) + end + }, + LoadFont("Common Normal") .. { + Name = "Instruction", + InitCommand = function(self) + self:valign(1) + self:y(topItemY + (allowedSpace / itemsPerPage / 2)) + self:addy(actuals.MenuHeight - actuals.EdgePadding) + self:zoom(uiInstructionTextSize) + self:maxwidth(actuals.MenuWidth / uiInstructionTextSize) + self:settext("Space to scroll movement types") + end, + } + } + } + + for i = 1, itemsPerPage do + t[#t+1] = item(i) + end + + t[#t+1] = Def.Quad { + Name = "Cursor", + InitCommand = function(self) + self:halign(0) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + self:diffusealpha(cursorAlpha) + end, + UpdateItemListCommand = function(self) + visibilityBySelectedElement(self) + end, + } + return t +end + +t[#t+1] = Def.ActorFrame { + Name = "UIContainer", + InitCommand = function(self) + self:xy(SCREEN_WIDTH, SCREEN_CENTER_Y) + end, + makeUI(), +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayelements.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayelements.lua new file mode 100644 index 0000000000..345d33d23d --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayelements.lua @@ -0,0 +1,173 @@ +-- gameplay elements + +setMovableKeymode(getCurrentKeyMode()) + +local targetTrackerMode = playerConfig:get_data().TargetTrackerMode + +local t = Def.ActorFrame { + Name = "GameplayElementsController", + + BeginCommand = function(self) + updateDiscordStatusForGameplay() + updateNowPlaying() + + -- queue so it doesnt reach the children + self:queuecommand("SetUpMovableValues") + end, + EndCommand = function(self) + -- exiting the screen saves customization changes + playerConfig:get_data().CurrentWidth = SCREEN_WIDTH + playerConfig:get_data().CurrentHeight = SCREEN_HEIGHT + playerConfig:save() + end, + SetUpMovableValuesMessageCommand = function(self) + local screen = SCREENMAN:GetTopScreen() + local usingReverse = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() + + -- screen scale + local screenscale = MovableValues.ScreenZoom + screen:zoom(screenscale) + screen:xy(SCREEN_WIDTH * (1 - screenscale) / 2, SCREEN_HEIGHT * (1 - screenscale) / 2) + screen:addx(MovableValues.ScreenX) + screen:addy(MovableValues.ScreenY) + + -- lifebar movement + local lifebar = screen:GetLifeMeter(PLAYER_1) + if lifebar ~= nil then + lifebar:zoomtowidth(MovableValues.LifeP1Width) + lifebar:zoomtoheight(MovableValues.LifeP1Height) + lifebar:xy(MovableValues.LifeP1X, MovableValues.LifeP1Y) + lifebar:rotationz(MovableValues.LifeP1Rotation) + end + + -- notefield movement + -- notefield column movement + local nf = screen:GetChild("PlayerP1"):GetChild("NoteField") + if nf then + local noteColumns = nf:get_column_actors() + nf:y(0) + nf:addy(MovableValues.NoteFieldY * (usingReverse and -1 or 1)) + nf:x(0) + nf:addx(MovableValues.NoteFieldX) + + -- notefield column sizing + for i, actor in ipairs(noteColumns) do + actor:zoomtowidth(MovableValues.NoteFieldWidth) + actor:zoomtoheight(MovableValues.NoteFieldHeight) + end + -- notefield column movement + local inc = MovableValues.NoteFieldSpacing + if inc == nil then inc = 0 end + local hCols = math.floor(#noteColumns/2) + for i, col in ipairs(noteColumns) do + col:x(0) + col:addx((i-hCols-1) * inc) + end + end + end, + DoneLoadingNextSongMessageCommand = function(self) + local screen = SCREENMAN:GetTopScreen() + + -- playlists reset notefield positioning ?? + if screen ~= nil and screen:GetChild("PlayerP1") ~= nil then + NoteField = screen:GetChild("PlayerP1"):GetChild("NoteField") + NoteField:addy(MovableValues.NoteFieldY * (usingReverse and 1 or -1)) + end + -- update all stats in gameplay (as if it was a reset) when loading a new song + -- particularly for playlists + self:playcommand("PracticeModeReset") + end, + JudgmentMessageCommand = function(self, msg) + -- for each judgment, every tap and hold judge + local targetDiff = msg.WifeDifferential + local wifePercent = notShit.floor(msg.WifePercent * 100) / 100 + local judgeCount = msg.Val + local dvCur = nil + if msg.Offset ~= nil then + dvCur = msg.Offset + end + local pbTarget = nil + if msg.WifePBGoal ~= nil and targetTrackerMode ~= 0 then + pbTarget = msg.WifePBGoal + targetDiff = msg.WifePBDifferential + end + local jdgCur = msg.Judgment + + self:playcommand("SpottedOffset", { + targetDiff = targetDiff, -- wifepoints difference from target goal + pbTarget = pbTarget, -- goal target equivalent to current rate pb + wifePercent = wifePercent, -- visual wifepercent converted from internal wifepercent value + judgeCount = judgeCount, -- current count of the given judgment that sent the JudgmentMessage + judgeOffset = dvCur, -- offset assigned to judged tap; nil if is a hold judgment + judgeCurrent = jdgCur, -- the judgment that triggered this JudgmentMessage + }) + end, + PracticeModeResetMessageCommand = function(self) + -- reset stats for practice mode reverts mostly + self:playcommand("SpottedOffset", { + targetDiff = 0, + pbTarget = 0, + wifePercent = 0, + judgeCount = 0, + judgeOffset = nil, + judgeCurrent = nil, + }) + end +} + +if playerConfig:get_data().BPMDisplay then + t[#t+1] = LoadActor("bpmdisplay") +end + +if playerConfig:get_data().DisplayPercent then + t[#t+1] = LoadActor("displaypercent") +end + +if playerConfig:get_data().ErrorBar ~= 0 then + t[#t+1] = LoadActor("errorbar") +end + +if playerConfig:get_data().FullProgressBar then + t[#t+1] = LoadActor("fullprogressbar") +end + +if playerConfig:get_data().JudgeCounter then + t[#t+1] = LoadActor("judgecounter") +end + +-- lane cover is in Graphics/NoteField cover.lua + +if (NSMAN:IsETTP() and Var("LoadingScreen"):find("Net") ~= nil) or +(playerConfig:get_data().leaderboardEnabled and DLMAN:IsLoggedIn()) then + t[#t+1] = LoadActor("leaderboard") +end + +if playerConfig:get_data().DisplayMean then + t[#t+1] = LoadActor("meandisplay") +end + +if playerConfig:get_data().MeasureCounter then + t[#t+1] = LoadActor("measurecounter") +end + +if playerConfig:get_data().MiniProgressBar then + t[#t+1] = LoadActor("miniprogressbar") +end + +if playerConfig:get_data().NPSDisplay or playerConfig:get_data().NPSGraph then + t[#t+1] = LoadActor("npsdisplay") +end + +if playerConfig:get_data().PlayerInfo then + t[#t+1] = LoadActor("playerinfo") +end + +if playerConfig:get_data().RateDisplay then + t[#t+1] = LoadActor("ratedisplay") +end + +if playerConfig:get_data().TargetTracker then + t[#t+1] = LoadActor("targettracker") +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaypractice.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaypractice.lua new file mode 100644 index 0000000000..a5a2e13463 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplaypractice.lua @@ -0,0 +1,246 @@ +-- this file is responsible for practice-mode only behavior +-- mostly it handles the additional inputs and functionality surrounding the chord density graph +-- right clicking or pressing InsertCoin to set bookmarks, etc + +local musicratio = 1 +local width = 280 +local height = 53 / 555 * SCREEN_HEIGHT +local cd +local loopStartPos +local loopEndPos + +local bookmarkWidth = 1 / 1080 * SCREEN_WIDTH +local bookmarkAlpha = 1 +local regionAlpha = 0.5 + +local bookmarkColor = COLORS:getGameplayColor("PracticeBookmark") +local regionColor = COLORS:getGameplayColor("PracticeRegion") + +local function handleRegionSetting(positionGiven) + -- don't allow a negative region + -- internally it is limited to -2 + -- the start delay is 2 seconds, so limit this to 0 + if positionGiven < 0 then return end + + -- first time starting a region + if not loopStartPos and not loopEndPos then + loopStartPos = positionGiven + MESSAGEMAN:Broadcast("RegionSet") + return + end + + -- reset region to bookmark only if double right click + if positionGiven == loopStartPos or positionGiven == loopEndPos then + loopEndPos = nil + loopStartPos = positionGiven + MESSAGEMAN:Broadcast("RegionSet") + SCREENMAN:GetTopScreen():ResetLoopRegion() + return + end + + -- measure the difference of the new pos from each end + local startDiff = math.abs(positionGiven - loopStartPos) + local endDiff = startDiff + 0.1 + if loopEndPos then + endDiff = math.abs(positionGiven - loopEndPos) + end + + -- use the diff to figure out which end to move + + -- if there is no end, then you place the end + if not loopEndPos then + if loopStartPos < positionGiven then + loopEndPos = positionGiven + elseif loopStartPos > positionGiven then + loopEndPos = loopStartPos + loopStartPos = positionGiven + else + -- this should never happen + -- but if it does, reset to bookmark + loopEndPos = nil + loopStartPos = positionGiven + MESSAGEMAN:Broadcast("RegionSet") + SCREENMAN:GetTopScreen():ResetLoopRegion() + return + end + else + -- closer to the start, move the start + if startDiff < endDiff then + loopStartPos = positionGiven + else + loopEndPos = positionGiven + end + end + SCREENMAN:GetTopScreen():SetLoopRegion(loopStartPos, loopEndPos) + MESSAGEMAN:Broadcast("RegionSet", {loopLength = loopEndPos-loopStartPos}) +end + +local t = Def.ActorFrame { + Name = "GameplayPracticeController", + InitCommand = function(self) + end, + BeginCommand = function(self) + musicratio = GAMESTATE:GetCurrentSteps():GetLastSecond() / width + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type == "InputEventType_Release" then + if event.DeviceInput.button == "DeviceButton_left mouse button" then + MESSAGEMAN:Broadcast("MouseLeftClick") + elseif event.DeviceInput.button == "DeviceButton_right mouse button" then + MESSAGEMAN:Broadcast("MouseRightClick") + end + elseif event.type == "InputEventType_FirstPress" then + if event.DeviceInput.button == "DeviceButton_backspace" then + if loopStartPos ~= nil then + SCREENMAN:GetTopScreen():SetSongPositionAndUnpause(loopStartPos, 1, true) + else + SCREENMAN:GetTopScreen():SetSongPositionAndUnpause(0, 1, true) + end + elseif event.button == "EffectUp" then + SCREENMAN:GetTopScreen():AddToRate(0.05) + elseif event.button == "EffectDown" then + SCREENMAN:GetTopScreen():AddToRate(-0.05) + elseif event.button == "Coin" then + handleRegionSetting(SCREENMAN:GetTopScreen():GetSongPosition()) + elseif event.DeviceInput.button == "DeviceButton_mousewheel up" then + if GAMESTATE:IsPaused() then + local pos = SCREENMAN:GetTopScreen():GetSongPosition() + local dir = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() and 1 or -1 + local nextpos = pos + dir * 0.05 + if loopEndPos ~= nil and nextpos >= loopEndPos then + handleRegionSetting(nextpos + 1) + end + SCREENMAN:GetTopScreen():SetSongPosition(nextpos, 0, false) + end + elseif event.DeviceInput.button == "DeviceButton_mousewheel down" then + if GAMESTATE:IsPaused() then + local pos = SCREENMAN:GetTopScreen():GetSongPosition() + local dir = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() and 1 or -1 + local nextpos = pos - dir * 0.05 + if loopEndPos ~= nil and nextpos >= loopEndPos then + handleRegionSetting(nextpos + 1) + end + SCREENMAN:GetTopScreen():SetSongPosition(nextpos, 0, false) + end + end + end + return false + end) + end, + PracticeModeReloadMessageCommand = function(self) + musicratio = GAMESTATE:GetCurrentSteps():GetLastSecond() / width + end, + + -- invisible button covering the entire screen + -- right clicking anywhere that hits it (anywhere but the graph) pauses music + UIElements.QuadButton(1, 1) .. { + Name = "PauseArea", + InitCommand = function(self) + self:halign(0):valign(0) + self:z(1) + self:diffusealpha(0) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_right mouse button" then + local top = SCREENMAN:GetTopScreen() + if top then + top:TogglePause() + end + end + end, + } +} + +-- Load the CDGraph with a forced width parameter. +t[#t + 1] = LoadActorWithParams("../../chorddensitygraph.lua", {sizing = { + Width = width, + Height = height, + NPSThickness = 1.5, + TextSize = 0.45, +}}) .. { + Name = "PracticeCDGraph", + BeginCommand = function(self) + self:playcommand("SetUpMovableValues") + self:playcommand("LoadDensityGraph", {steps = GAMESTATE:GetCurrentSteps(), song = GAMESTATE:GetCurrentSong()}) + -- doing this in a really awkward way to inject the desired behavior into the existing SeekBar + local seekbar = self:GetChild("SeekBar") + local bg = self:GetChild("BG") + if seekbar and bg then + bg:addcommand("HandleRegionSetting", function(self, params) + local positionGiven = params.positionGiven + handleRegionSetting(positionGiven) + end) + end + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + sizeInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.PracticeCDGraphX, MovableValues.PracticeCDGraphY) + local wb4 = width + local hb4 = height + width = MovableValues.PracticeCDGraphWidth * 280 + height = MovableValues.PracticeCDGraphHeight * (53 / 555 * SCREEN_HEIGHT) + self:playcommand("UpdateSizing", {sizing = { + Width = width, + Height = height, + }}) + if width ~= wb4 or height ~= hb4 then + self:playcommand("LoadDensityGraph", {steps = GAMESTATE:GetCurrentSteps(), song = GAMESTATE:GetCurrentSong()}) + end + musicratio = GAMESTATE:GetCurrentSteps():GetLastSecond() / width + self:finishtweening() + end, + PracticeModeReloadMessageCommand = function(self) + self:playcommand("LoadDensityGraph", {steps = GAMESTATE:GetCurrentSteps(), song = GAMESTATE:GetCurrentSong()}) + end, +} +-- extra quad for bookmark position and region +t[#t+1] = Def.Quad { + Name = "BookmarkPos", + InitCommand = function(self) + -- trickery + self:SetFakeParent(self:GetParent():GetChild("PracticeCDGraph")) + + self:valign(0) + self:zoomto(bookmarkWidth, height) + self:diffuse(bookmarkColor) + self:diffusealpha(bookmarkAlpha) + self:draworder(1100) + self:visible(false) + end, + SetCommand = function(self) + self:visible(true) + self:zoomto(bookmarkWidth, height) + self:diffuse(bookmarkColor) + self:diffusealpha(bookmarkAlpha) + self:x(loopStartPos / musicratio) + end, + RegionSetMessageCommand = function(self, params) + if not params or not params.loopLength then + self:playcommand("Set") + else + self:visible(true) + self:halign(0) + self:x(loopStartPos / musicratio) + self:zoomto(params.loopLength / musicratio, height) + self:diffuse(regionColor) + self:diffusealpha(regionAlpha) + end + end, + CurrentRateChangedMessageCommand = function(self) + if not loopEndPos and loopStartPos then + self:playcommand("Set") + elseif loopEndPos and loopStartPos then + self:playcommand("RegionSet", {loopLength = (loopEndPos - loopStartPos)}) + end + end, + PracticeModeReloadMessageCommand = function(self) + self:playcommand("CurrentRateChanged") + end +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayreplay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayreplay.lua new file mode 100644 index 0000000000..ed463e62b8 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/_gameplayreplay.lua @@ -0,0 +1,245 @@ +local buttons = {} +local bobo +local function spaceButtons(value) + for i, b in ipairs(buttons) do + b:addy((i-1) * value) + end + bobo:playcommand("ChangeHeight", {val = buttons[#buttons]:GetChild("Label"):GetY() + buttons[#buttons]:GetChild("Label"):GetHeight()/2}) +end + +local modifierPressed = false +local forward = true +local position = 0 + +local scroller -- just an alias for the actor that runs the commands + +local function input(event) + +end + +local function getNewSongPos() + local currentpos = SCREENMAN:GetTopScreen():GetSongPosition() + local newpos = currentpos + (modifierPressed and 0.1 or 5) * (forward and 1 or -1) + --SCREENMAN:SystemMessage(string.format("%f to %f", currentpos, newpos)) + return newpos +end + +local function getNewRate() + local currentrate = GAMESTATE:GetSongOptionsObject("ModsLevel_Preferred"):MusicRate() + local newrate = currentrate + (modifierPressed and 0.05 or 0.1) * (forward and 1 or -1) + --SCREENMAN:SystemMessage(string.format("%f to %f", currentrate, newrate)) + return newrate +end + +scroller = Def.ActorFrame { + Name = "ReplayButtons", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + end, + OnCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + --SCREENMAN:SystemMessage(event.DeviceInput.button) + if event.DeviceInput.button == "DeviceButton_right ctrl" or event.DeviceInput.button == "DeviceButton_left ctrl" then + modifierPressed = not (event.type == "InputEventType_Release") + end + if event.DeviceInput.button == "DeviceButton_right shift" or event.DeviceInput.button == "DeviceButton_left shift" then + ratePressed = not (event.type == "InputEventType_Release") + end + if event.DeviceInput.button == "DeviceButton_right alt" or event.DeviceInput.button == "DeviceButton_left alt" then + bookmarkPressed = not (event.type == "InputEventType_Release") + end + if event.type ~= "InputEventType_Release" then + if event.GameButton == "EffectUp" then + if bookmarkPressed then + self:queuecommand("ReplayBookmarkSet") + return false + end + forward = true + if ratePressed then + self:queuecommand("ReplayRate") + else + self:queuecommand("ReplayScroll") + end + elseif event.GameButton == "EffectDown" then + if bookmarkPressed then + self:queuecommand("ReplayBookmarkGoto") + return false + end + forward = false + if ratePressed then + self:queuecommand("ReplayRate") + else + self:queuecommand("ReplayScroll") + end + elseif event.GameButton == "Coin" then + self:queuecommand("ReplayPauseToggle") + end + end + end) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.ReplayButtonsX, MovableValues.ReplayButtonsY) + end, + ReplayScrollCommand = function(self) + local newpos = getNewSongPos() + SCREENMAN:GetTopScreen():SetSongPosition(newpos) + end, + ReplayRateCommand = function(self) + local newrate = getNewRate() + local givenrate = SCREENMAN:GetTopScreen():SetRate(newrate) + if givenrate ~= nil then + local realnewrate = notShit.round(givenrate, 3) + --SCREENMAN:SystemMessage(string.format("Set rate to %f", realnewrate)) + end + end, + ReplayPauseToggleCommand = function(self) + SCREENMAN:GetTopScreen():TogglePause() + end, + ReplayBookmarkSetCommand = function(self) + position = SCREENMAN:GetTopScreen():GetSongPosition() + SCREENMAN:GetTopScreen():SetBookmark(position) + end, + ReplayBookmarkGotoCommand = function(self) + SCREENMAN:GetTopScreen():JumpToBookmark() + end, +} +local span = 50 +local x = -1 * span +local textSize = 0.8 +local hoverAlpha = 0.6 +local width = 60 +local height = 30 +local uiBGAlpha = 0.6 + +local translated_info = { + Pause = THEME:GetString("ScreenGameplay", "ButtonPause"), + FastForward = THEME:GetString("ScreenGameplay", "ButtonFastForward"), + Rewind = THEME:GetString("ScreenGameplay", "ButtonRewind"), + Play = THEME:GetString("ScreenGameplay", "ButtonPlay"), + Results = "Results", + Exit = "Exit", +} + +local function button(i, txt, click, mustBePaused) + return Def.ActorFrame { + InitCommand = function(self) + self:y(x + span*i) -- wow this is bad + end, + + Def.Quad { + Name = "Border", + InitCommand = function(self) + self:halign(1) + self:zoomto(width, height) + self:diffuse(COLORS:getColor("replay", "ButtonBorder")) + self:diffusealpha(1) + end, + }, + UIElements.QuadButton(1, 1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(1) + self:x(-1) + self:zoomto(width - 2, height - 2) + self:diffuse(COLORS:getColor("replay", "ButtonBG")) + self:diffusealpha(1) + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + if mustBePaused then + if not GAMESTATE:IsPaused() then + TOOLTIP:SetText("Must be paused") + TOOLTIP:Show() + end + end + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + TOOLTIP:Hide() + end, + MouseDownCommand = function(self, params) + if params and params.event == "DeviceButton_left mouse button" then + click(self) + end + end, + }, + LoadFont("Common Normal") .. { + Name = "Text", + InitCommand = function(self) + self:x(-width / 2) + self:zoom(textSize) + self:maxwidth(width / textSize) + self:settext(txt) + self:diffuse(COLORS:getMainColor("PrimaryText")) + self:diffusealpha(1) + end, + } + } +end + +scroller[#scroller + 1] = Def.ActorFrame { + Name = "ReplayButtons", + InitCommand = function(self) + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + spacingInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + UIElements.QuadButton(1) .. { + Name = "DraggableBox", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoomto(width * 1.5, height * 3.5 + span * 4) + self:xy(width/4, -span) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + self:diffusealpha(uiBGAlpha) + end, + MouseDragCommand = function(self, params) + local newx = params.MouseX - (self.initialClickX or 0) + local newy = params.MouseY - (self.initialClickY or 0) + self:GetParent():addx(newx):addy(newy) + end, + MouseDownCommand = function(self, params) + self.initialClickX = params.MouseX + self.initialClickY = params.MouseY + end, + }, + + button(1, + translated_info["Pause"], + function(self) + SCREENMAN:GetTopScreen():TogglePause() + local paused = GAMESTATE:IsPaused() + self:GetParent():GetChild("Text"):settext(paused and translated_info["Play"] or translated_info["Pause"]) + end + ), + button(2, + translated_info["FastForward"], + function(self) + SCREENMAN:GetTopScreen():SetSongPosition(SCREENMAN:GetTopScreen():GetSongPosition() + 5) + end, + true + ), + button(3, + translated_info["Rewind"], + function(self) + SCREENMAN:GetTopScreen():SetSongPosition(SCREENMAN:GetTopScreen():GetSongPosition() - 5) + end, + true + ), + button(4, + translated_info["Results"], + function(self) + SCREENMAN:GetTopScreen():PostScreenMessage("SM_NotesEnded", 0) + end + ), + button(5, + translated_info["Exit"], + function(self) + SCREENMAN:GetTopScreen():Cancel() + end + ), +} +return scroller diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/bpmdisplay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/bpmdisplay.lua new file mode 100644 index 0000000000..a6fe9c09d8 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/bpmdisplay.lua @@ -0,0 +1,58 @@ +-- the bpm display. it displays the bpm + +-- reset the update function and stuff +-- optimization: dont update for files with 1 bpm because the bpm doesnt change +local function initbpm(self) + local r = GAMESTATE:GetSongOptionsObject("ModsLevel_Current"):MusicRate() * 60 + local a = GAMESTATE:GetPlayerState():GetSongPosition() + local GetBPS = SongPosition.GetCurBPS + if #GAMESTATE:GetCurrentSong():GetTimingData():GetBPMs() > 1 then + self:SetUpdateFunction(function(self) + local bpm = GetBPS(a) * r + self:GetChild("BPM"):settext(notShit.round(bpm, 2)) + end) + self:SetUpdateRate(0.5) + else + self:SetUpdateFunction(nil) + self:GetChild("BPM"):settextf("%5.2f", GetBPS(a) * r) + end +end +------- + +local bpmTextSize = GAMEPLAY:getItemHeight("bpmDisplayText") + +return Def.ActorFrame { + Name = "BPMText", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + self:queuecommand("Set") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.BPMTextX, MovableValues.BPMTextY) + self:zoom(MovableValues.BPMTextZoom) + end, + SetCommand = function(self) + initbpm(self) + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("Set") + end, + PracticeModeReloadMessageCommand = function(self) + self:playcommand("Set") + end, + DoneLoadingNextSongMessageCommand = function(self) + self:playcommand("Set") + end, + + LoadFont("Common Normal") .. { + Name = "BPM", + InitCommand = function(self) + self:zoom(bpmTextSize) + end, + }, +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/default.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/default.lua new file mode 100644 index 0000000000..111b960355 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/default.lua @@ -0,0 +1,38 @@ +-- this delegates the existence and further control and customization of all gameplay elements +-- decided to put this into its own folder for organization related reasons +local customizationEnabled = playerConfig:get_data().CustomizeGameplay == true +local practiceEnabled = GAMESTATE:IsPracticeMode() +local replayEnabled = GAMESTATE:GetGameplayMode() == "GameplayMode_Replay" + +if not replayEnabled and not customizationEnabled and not practiceEnabled then + Arch.setCursorVisible(false) + TOOLTIP:HidePointer() +end + + +local t = Def.ActorFrame {Name = "CustomGameplayElementLoader"} + + +t[#t+1] = LoadActor("_gameplayelements") + +if practiceEnabled then + t[#t+1] = LoadActor("_gameplaypractice") +end + +if replayEnabled then + t[#t+1] = LoadActor("_gameplayreplay") +end + +if customizationEnabled then + t[#t+1] = LoadActor("_gameplaycustomization") +end + +if practiceEnabled or replayEnabled or customizationEnabled then + t[#t+1] = LoadActor("../../_mouse.lua") +else + -- re entering gameplay should empty the mouse overlay stuff + local screenName = Var("LoadingScreen") or ... + BUTTON:ResetButtonTable(screenName) +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/displaypercent.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/displaypercent.lua new file mode 100644 index 0000000000..62eb994c83 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/displaypercent.lua @@ -0,0 +1,61 @@ +-- the wifepercent display. it displays the wifepercent + +-- i dunno less copy paste whatever bro +local formatstr = "%05.2f%%" +local wifepercentTextSize = GAMEPLAY:getItemHeight("wifeDisplayText") +local bgMargin = 4 +local bgalpha = 0.4 + +return Def.ActorFrame { + Name = "DisplayPercent", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.DisplayPercentX, MovableValues.DisplayPercentY) + self:zoom(MovableValues.DisplayPercentZoom) + end, + SpottedOffsetCommand = function(self, params) + local bg = self:GetChild("PercentBacking") + local perc = self:GetChild("DisplayPercent") + + -- kind of putting all the logic in one place here + -- modify text then update bg for it + if params ~= nil and params.wifePercent ~= nil then + if perc then + perc:settextf(formatstr, params.wifePercent) + end + else + if perc then + perc:settextf(formatstr, 0) + end + end + if bg and perc then + bg:zoomto(perc:GetZoomedWidth() + bgMargin, perc:GetZoomedHeight() + bgMargin) + end + end, + + Def.Quad { + Name = "PercentBacking", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(bgMargin/2,-bgMargin/2) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + self:diffusealpha(bgalpha) + end + }, + LoadFont("Common Large") .. { + Name = "DisplayPercent", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoom(wifepercentTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + self:diffusealpha(1) + end, + } +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/errorbar.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/errorbar.lua new file mode 100644 index 0000000000..1b32bed5e4 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/errorbar.lua @@ -0,0 +1,176 @@ +-- Number of bars. Older bars will refresh if judgments/barDuration exceeds this value. +local barcount = playerConfig:get_data().ErrorBarCount +-- Width of the bars. +local barWidth = GAMEPLAY:getItemWidth("errorBarBarWidth") +-- Time duration in seconds before the ticks fade out. Doesn't need to be higher than 1. Maybe if you have 300 bars I guess. +local barDuration = 0.75 + +if barcount > 50 then barDuration = barcount / 50 end -- just procedurally set the duration if we pass 50 bars + +-- regular bars +local currentbar = 1 -- so we know which error bar we need to update +local ingots = {} -- references to the error bars + +-- ewma vars +local alpha = 0.07 -- this is not opacity. this is a math number thing +local avg +local lastAvg + +-- to relate an offset to position within the bar +-- max offset is 180ms +local wscale = MovableValues.ErrorBarWidth / 180 + +local earlylateTextSize = GAMEPLAY:getItemHeight("errorBarText") + +-- the error bar can either load as Regular or Exponential Weighted Moving Average +-- Regular loads a certain number of bars and places them continuously +-- EWMA loads 1 bar and places it according to the EWMA of the previous n taps +local errorbarType = playerConfig:get_data().ErrorBar == 1 and "Regular" or "EWMA" + +local translated_info = { + ErrorLate = "Late", + ErrorEarly = "Early", +} + +-- procedurally generated error bars +local function smeltErrorBar(index) + return Def.Quad { + Name = index, + InitCommand = function(self) + self:zoomto(barWidth, MovableValues.ErrorBarHeight) + self:diffusealpha(0) + end, + UpdateErrorBarCommand = function(self, params) + if not params or params.judgeCurrent == nil or params.judgeOffset == nil then return end + self:finishtweening() + self:diffusealpha(1) + -- now make it the color for this new judgment + self:diffuse(colorByJudgment(params.judgeCurrent)) + -- and set up the position + if MovableValues and MovableValues.ErrorBarX then + self:x(params.judgeOffset * wscale) + self:zoomtoheight(MovableValues.ErrorBarHeight) + end + self:linear(barDuration) + self:diffusealpha(0) + end, + PracticeModeResetMessageCommand = function(self) + self:diffusealpha(0) + end + } +end + +local t = Def.ActorFrame { + Name = "ErrorBar", + InitCommand = function(self) + if errorbarType == "Regular" then + for i = 1, barcount do + ingots[#ingots + 1] = self:GetChild(i) + end + else + avg = 0 + lastAvg = 0 + end + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.ErrorBarX, MovableValues.ErrorBarY) + wscale = MovableValues.ErrorBarWidth / 180 + end, + SpottedOffsetCommand = function(self, params) + if errorbarType == "Regular" then + if params and params.judgeOffset ~= nil then + currentbar = ((currentbar) % barcount) + 1 + ingots[currentbar]:playcommand("UpdateErrorBar", params) -- Update the next bar in the queue + end + end + end, + DootCommand = function(self) + self:RemoveChild("DestroyMe") + self:RemoveChild("DestroyMe2") + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + --registerActorToColorConfigElement(self, "main", "PrimaryBackground") + self:diffusealpha(0) + end, + SetUpMovableValuesMessageCommand = function(self) + self:zoomto(MovableValues.ErrorBarWidth, MovableValues.ErrorBarHeight) + end, + }, + Def.Quad { + Name = "Center", + InitCommand = function(self) + self:diffuse(COLORS:getGameplayColor("ErrorBarCenter")) + end, + SetUpMovableValuesMessageCommand = function(self) + self:zoomto(2, MovableValues.ErrorBarHeight) + end, + }, + + -- Indicates which side is which (early/late) These should be destroyed after the song starts. + LoadFont("Common Normal") .. { + Name = "DestroyMe", + InitCommand = function(self) + self:zoom(earlylateTextSize) + end, + BeginCommand = function(self) + self:settext(translated_info["ErrorLate"]) + self:diffusealpha(0):smooth(0.5):diffusealpha(0.5):sleep(1.5):smooth(0.5):diffusealpha(0) + end, + SetUpMovableValuesMessageCommand = function(self) + self:x(MovableValues.ErrorBarWidth / 4) + end, + }, + LoadFont("Common Normal") .. { + Name = "DestroyMe2", + InitCommand = function(self) + self:zoom(earlylateTextSize) + end, + BeginCommand = function(self) + self:settext(translated_info["ErrorEarly"]) + self:diffusealpha(0):smooth(0.5):diffusealpha(0.5):sleep(1.5):smooth(0.5):diffusealpha(0):queuecommand("Doot") + end, + SetUpMovableValuesMessageCommand = function(self) + self:x(-MovableValues.ErrorBarWidth / 4) + end, + DootCommand = function(self) + self:GetParent():queuecommand("Doot") + end + }, +} + +if errorbarType == "EWMA" then + t[#t+1] = Def.Quad { + Name = "WeightedBar", + InitCommand = function(self) + self:diffuse(COLORS:getGameplayColor("ErrorBarEWMABar")) + self:diffusealpha(1) + end, + SetUpMovableValuesMessageCommand = function(self) + self:zoomto(barWidth, MovableValues.ErrorBarHeight) + end, + SpottedOffsetCommand = function(self, params) + if params and params.judgeOffset ~= nil then + avg = alpha * params.judgeOffset + (1 - alpha) * lastAvg + lastAvg = avg + self:x(avg * wscale) + end + end + } +end + +if errorbarType == "Regular" then + for i = 1, barcount do + t[#t+1] = smeltErrorBar(i) + end +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/fullprogressbar.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/fullprogressbar.lua new file mode 100644 index 0000000000..4d80154d46 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/fullprogressbar.lua @@ -0,0 +1,128 @@ +-- progress bar that goes across the screen + +local width = SCREEN_WIDTH / 2 - GAMEPLAY:getItemWidth("fullProgressBarWidthBeforeHalf") +local height = SCREEN_HEIGHT / 50 +local alpha = 0.7 +local isReplay = GAMESTATE:GetPlayerState():GetPlayerController() == "PlayerController_Replay" and not allowedCustomization + +local progressbarTextSize = GAMEPLAY:getItemHeight("fullProgressBarText") + +local function bounds() + local stps = GAMESTATE:GetCurrentSteps() + return stps:GetFirstSecond(), stps:GetLastSecond() +end + +-- ternary logic +-- will become either nothing or a slider +-- used to seek in replays +local replaySlider = isReplay and + UIElements.QuadButton(1, 1) .. { + Name = "SliderButtonArea", + InitCommand = function(self) + self:diffusealpha(0.3) + self:zoomto(width, height) + end, + MouseHoldCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if not GAMESTATE:IsPaused() then + TOOLTIP:SetText("Music must be paused") + TOOLTIP:Show() + end + + local localX = clamp(params.MouseX - self:GetTrueX() + width/2, 0, width) + local localY = clamp(params.MouseY, 0, height) + + local lb, ub = bounds() + local percentX = localX / width + + local posx = clamp(lb + (percentX * (ub - lb)), lb, ub) + SCREENMAN:GetTopScreen():SetSongPosition(posx) + end, + MouseReleaseCommand = function(self) + TOOLTIP:Hide() + end, + MouseUpCommand = function(self) + TOOLTIP:Hide() + end, + } or + Def.Actor {Name = "Nothing"} + +return Def.ActorFrame { + Name = "FullProgressBar", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.FullProgressBarX, MovableValues.FullProgressBarY) + self:zoomto(MovableValues.FullProgressBarWidth, MovableValues.FullProgressBarHeight) + end, + + replaySlider, + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:zoomto(width, height) + self:diffuse(COLORS:getGameplayColor("FullProgressBarBG")) + self:diffusealpha(alpha) + end + }, + Def.SongMeterDisplay { + Name = "Progress", + InitCommand = function(self) + self:SetUpdateRate(0.5) + end, + StreamWidth = width, + Stream = Def.Quad { + InitCommand = function(self) + self:zoomy(height) + self:diffuse(COLORS:getGameplayColor("FullProgressBar")) + self:diffusealpha(alpha) + end + } + }, + LoadFont("Common Normal") .. { + Name = "Title", + InitCommand = function(self) + self:zoom(progressbarTextSize) + self:maxwidth((width * 0.8) / progressbarTextSize) + end, + BeginCommand = function(self) + self:settext(GAMESTATE:GetCurrentSong():GetDisplayMainTitle()) + end, + DoneLoadingNextSongMessageCommand = function(self) + self:playcommand("Begin") + end, + PracticeModeReloadMessageCommand = function(self) + self:playcommand("Begin") + end + }, + LoadFont("Common Normal") .. { + Name = "SongLength", + InitCommand = function(self) + self:x(width / 2) + self:halign(1) + self:zoom(progressbarTextSize) + self:maxwidth((width * 0.2) / progressbarTextSize) + end, + BeginCommand = function(self) + local ttime = GetPlayableTime() + self:settext(SecondsToMMSS(ttime)) + self:diffuse(colorByMusicLength(ttime)) + end, + DoneLoadingNextSongMessageCommand = function(self) + self:playcommand("Begin") + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("Begin") + end, + PracticeModeReloadMessageCommand = function(self) + self:playcommand("Begin") + end + }, +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/judgecounter.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/judgecounter.lua new file mode 100644 index 0000000000..d38dbacc84 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/judgecounter.lua @@ -0,0 +1,91 @@ +-- the judge counter. counts judges + +-- judgments to display and the order for them +local jdgT = { + "TapNoteScore_W1", + "TapNoteScore_W2", + "TapNoteScore_W3", + "TapNoteScore_W4", + "TapNoteScore_W5", + "TapNoteScore_Miss", + "HoldNoteScore_Held", + "HoldNoteScore_LetGo", +} + +local spacing = GAMEPLAY:getItemHeight("judgeDisplayVerticalSpacing") -- Spacing between the judgetypes +local frameWidth = GAMEPLAY:getItemWidth("judgeDisplay") -- Width of the Frame +local frameHeight = ((#jdgT + 1) * spacing) -- Height of the Frame +local judgeFontSize = GAMEPLAY:getItemHeight("judgeDisplayJudgeText") +local countFontSize = GAMEPLAY:getItemHeight("judgeDisplayCountText") + +-- the text actors for each judge count +local judgeCounts = {} + +local t = Def.ActorFrame { + Name = "JudgeCounter", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + }) + end, + BeginCommand = function(self) + for _, j in ipairs(jdgT) do + judgeCounts[j] = self:GetChild(j .. "count") + end + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.JudgeCounterX, MovableValues.JudgeCounterY) + end, + SpottedOffsetCommand = function(self, params) + if params == nil then return end + local cur = params.judgeCurrent + if cur and judgeCounts[cur] ~= nil then + judgeCounts[cur]:settext(params.judgeCount) + end + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:zoomto(frameWidth, frameHeight) + self:diffuse(color("0,0,0,0.4")) + end, + }, +} + +local function makeJudgeText(judge, index) + return LoadFont("Common normal") .. { + Name = judge .. "text", + InitCommand = function(self) + self:xy(-frameWidth / 2 + 5, -frameHeight / 2 + (index * spacing)) + self:halign(0) + self:zoom(judgeFontSize) + self:settext(getShortJudgeStrings(judge)) + self:diffuse(COLORS:colorByJudgment(judge)) + end, + } +end + +local function makeJudgeCount(judge, index) + return LoadFont("Common Normal") .. { + Name = judge .. "count", + InitCommand = function(self) + self:halign(1) + self:xy(frameWidth / 2 - 5, -frameHeight / 2 + (index * spacing)) + self:zoom(countFontSize) + self:settext(0) + end, + PracticeModeResetMessageCommand = function(self) + self:settext(0) + end, + } +end + +for i, j in ipairs(jdgT) do + t[#t+1] = makeJudgeText(j, i) + t[#t+1] = makeJudgeCount(j, i) +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/leaderboard.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/leaderboard.lua new file mode 100644 index 0000000000..0bdb3be0c1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/leaderboard.lua @@ -0,0 +1,356 @@ +local leaderboardEnabled = + (NSMAN:IsETTP() and SCREENMAN:GetTopScreen() and SCREENMAN:GetTopScreen():GetName() == "ScreenNetStageInformation") or + (playerConfig:get_data().leaderboardEnabled and DLMAN:IsLoggedIn()) +if not leaderboardEnabled then + return Def.ActorFrame {} +end +local isMulti = NSMAN:IsETTP() and SCREENMAN:GetTopScreen() and SCREENMAN:GetTopScreen():GetName():find("Net") ~= nil or false + +-- bad idea +if not DLMAN:GetCurrentRateFilter() then + DLMAN:ToggleRateFilter() +end + +local jdgs = { + -- Table of judgments for the judgecounters + "TapNoteScore_W1", + "TapNoteScore_W2", + "TapNoteScore_W3", + "TapNoteScore_W4", + "TapNoteScore_W5", + "TapNoteScore_Miss", +} + +local textSize = 0.6 + +local entryActors = {} +local scoreboard = {} +local onlineScores = {} +local multiScores = {} +local curScore = { + GetDisplayName = function() + return DLMAN:GetUsername() + end, + GetWifeGrade = function(self) + return GetGradeFromPercent(self.curWifeScore) + end, + GetWifeScore = function(self) + return self.curWifeScore + end, + GetSkillsetSSR = function() + return -1 + end, + GetJudgmentString = function(self) + local str = "" + for i, v in ipairs(jdgs) do + str = str .. self.jdgVals[v] .. " | " + end + return str .. "x" .. self.combo + end, + combo = 0, + curWifeScore = 0, + curGrade = "Grade_Tier02", + jdgVals = {}, +} +for i,v in ipairs(jdgs) do + curScore.jdgVals[v] = 0; +end + +-- scores in the leaderboard are sorted by this +local CRITERIA = "GetWifeScore" +local NUM_ENTRIES = 32 +local VISIBLE_ENTRIES = 5 +local ENTRY_HEIGHT = (IsUsingWideScreen() and 35 / 480 * SCREEN_HEIGHT or 20 / 480 * SCREEN_HEIGHT) +local WIDTH = SCREEN_WIDTH * (IsUsingWideScreen() and 0.3 or 0.275) + +for i = 1, NUM_ENTRIES do + entryActors[i] = {} +end + +local t = Def.ActorFrame { + Name = "Leaderboard", + InitCommand = function(self) + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + spacingInc = {5,1}, + }) + end, + OnCommand = function(self) + self:playcommand("SetUpMovableValues") + for i, entry in ipairs(entryActors) do + for name, label in pairs(entry) do + if scoreboard[i] ~= nil then + label:visible(not (not scoreboard[i]:GetDisplayName()) and i <= VISIBLE_ENTRIES) + end + end + end + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.LeaderboardX, MovableValues.LeaderboardY) + for i, entry in ipairs(entryActors) do + entry.container:xy(0, (i-1) * ENTRY_HEIGHT * 1.3) + entry.container:addy((i - 1) * MovableValues.LeaderboardSpacing) + end + self:zoomtowidth(MovableValues.LeaderboardWidth) + self:zoomtoheight(MovableValues.LeaderboardHeight) + end, + Def.Quad { + Name = "Background", + InitCommand = function(self) + self:visible(false) + self:valign(0):halign(0) + self:x(WIDTH/5) + end, + SetUpMovableValuesMessageCommand = function(self) + if not allowedCustomization then return end + self:zoomto(WIDTH, ENTRY_HEIGHT * 1.3 * (VISIBLE_ENTRIES) + ENTRY_HEIGHT + (MovableValues.LeaderboardSpacing * VISIBLE_ENTRIES) ) + end, + } +} + +local function leaderboardSortingFunction(h1, h2) + return h1[CRITERIA](h1) > h2[CRITERIA](h2) +end + +local function scoreUsingMultiScore(idx) + return { + GetDisplayName = function() + return multiScores[idx] and multiScores[idx].user or nil + end, + GetWifeGrade = function() + return multiScores[idx] and GetGradeFromPercent(multiScores[idx].wife) or "Grade_Tier01" + end, + GetWifeScore = function() + return multiScores[idx] and multiScores[idx].wife or -5000000 + end, + GetSkillsetSSR = function() + return -1 + end, + GetJudgmentString = function() + return multiScores[idx] and multiScores[idx].jdgstr or "" + end, + } +end + +local function setUpOnlineScores() + if isMulti then + multiScores = NSMAN:GetMPLeaderboard() + for i = 1, NUM_ENTRIES do + onlineScores[i] = scoreUsingMultiScore(i) + end + else + onlineScores = DLMAN:GetChartLeaderBoard(GAMESTATE:GetCurrentSteps():GetChartKey()) + end + + -- hard limiting + if #onlineScores > NUM_ENTRIES then + for i = NUM_ENTRIES, #onlineScores do + onlineScores[i] = nil + end + end + + table.sort(onlineScores, leaderboardSortingFunction) +end + +local function findCurscoreInScoreboard() + for i, s in ipairs(scoreboard) do + if s == curScore then + return i + end + end + return 1 -- how +end + +local function emplaceCurscore() + scoreboard = onlineScores + local done = false + local ind = #scoreboard + + if not isMulti then + local inserted = false + for i = 1, #scoreboard do + if not inserted and leaderboardSortingFunction(scoreboard[i], curScore) then + table.insert(scoreboard, i, curScore) + inserted = true + ind = i + break + end + end + if not inserted then + table.insert(scoreboard, curScore) + ind = #scoreboard + end + end + table.sort(scoreboard, leaderboardSortingFunction) + return ind +end + +local function scoreEntry(i) + local entry = Def.ActorFrame { + Name = "Entry_"..i, + InitCommand = function(self) + entryActors[i]["container"] = self + self.update = function(self, hs) + self:visible(not (not hs) and i <= VISIBLE_ENTRIES) + end + self:update(scoreboard[i]) + end, + } + + local labelContainer = Def.ActorFrame { + Name = "Label", + InitCommand = function(self) + self:x(WIDTH / 5) + end, + Def.Quad { + Name = "Background", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(WIDTH, ENTRY_HEIGHT) + self:diffuse(getLeaderboardColor("Background")) + self:diffusealpha(1) + end, + } + } + + local y = 0 + local function addLabel(name, onUpdate, x, y) + y = (y or 0) - (IsUsingWideScreen() and 0 or ENTRY_HEIGHT / 3.2) + labelContainer[#labelContainer+1] = LoadFont("Common Normal") .. { + Name = name, + InitCommand = function(self) + entryActors[i][name] = self + self:halign(0) + self:zoom(textSize) + self:xy(x, 10 + y) + self:settext("") + self:diffuse(getLeaderboardColor("Text")) + self:diffusealpha(1) + + self.update = function(self, hs, rank) + if hs then + self:visible(true) + onUpdate(self, hs, rank) + else + self:visible(false) + end + end + self:update(scoreboard[i]) + end, + } + end + + addLabel( + "rank", + function(self, hs, rank) + if rank ~= nil then + if rank >= NUM_ENTRIES then + self:settext(tostring(rank) .. "+") + else + self:settext(tostring(rank)) + end + else + self:settext(tostring(i)) + end + end, + 5, + ENTRY_HEIGHT / 4 + ) + addLabel( + "ssr", + function(self, hs) + local ssr = hs:GetSkillsetSSR("Overall") + if ssr < 0 then + self:settext("") + else + self:settextf("%.2f", ssr):diffuse(colorByMSD(ssr)) + end + end, + WIDTH / 15, + ENTRY_HEIGHT/4 + ) + addLabel( + "name", + function(self, hs) + local n = hs:GetDisplayName() + self:settext(n or "") + if entryActor then + entryActor:visible(not (not n)) + end + end, + WIDTH / 5 + ) + addLabel( + "wife", + function(self, hs) + self:settextf("%05.2f%%", hs:GetWifeScore() * 100):diffuse(colorByGrade(hs:GetWifeGrade())) + end, + WIDTH / 1.3 + ) + addLabel( + "grade", + function(self, hs) + self:settext(getGradeStrings(hs:GetWifeGrade())) + self:diffuse(colorByGrade(hs:GetWifeGrade())) + self:halign(0.5) + end, + WIDTH / 1.2, + ENTRY_HEIGHT / 2 + ) + addLabel( + "judges", + function(self, hs) + self:settext(hs:GetJudgmentString():gsub("I", "|")) + end, + WIDTH / 5, + ENTRY_HEIGHT / 2 + ) + entry[#entry+1] = labelContainer + return entry +end +for i = 1, NUM_ENTRIES do + t[#t + 1] = scoreEntry(i) +end + +setUpOnlineScores() +emplaceCurscore() + +t.ComboChangedMessageCommand = function(self, params) + curScore.combo = params.PlayerStageStats and params.PlayerStageStats:GetCurrentCombo() or params.OldCombo +end +t.JudgmentMessageCommand = function(self, params) + if curScore.jdgVals[params.Judgment] then + curScore.jdgVals[params.Judgment] = params.Val + end + -- params.curWifeScore retrieves the Judgment Message curWifeScore which is a raw number for calculations; very large + -- the online highscore curWifeScore is the wife percent... + -- params.WifePercent is our current calculated wife percent. + local old = curScore.curWifeScore + curScore.curWifeScore = notShit.floor(params.WifePercent * 10000) / 1000000 + if isMulti then + multiScores = NSMAN:GetMPLeaderboard() + end + if old ~= curScore.curWifeScore then + table.sort(scoreboard, leaderboardSortingFunction) + + local myscoreIndex = findCurscoreInScoreboard() + + for i, entry in ipairs(entryActors) do + for name, label in pairs(entry) do + label:update(scoreboard[i]) + end + end + + if myscoreIndex > VISIBLE_ENTRIES then + for name, label in pairs(entryActors[VISIBLE_ENTRIES + 1]) do + label:update(curScore, myscoreIndex) + label:visible(true) + end + end + + end +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/meandisplay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/meandisplay.lua new file mode 100644 index 0000000000..1aade14c11 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/meandisplay.lua @@ -0,0 +1,73 @@ +-- the mean display. it displays the mean + +-- i dunno less copy paste whatever bro +local formatstr = "%5.2fms" +local meanTextSize = GAMEPLAY:getItemHeight("meanDisplayText") +local bgMargin = 4 +local bgalpha = 0.4 + +local curMeanSum = 0 +local curMeanCount = 0 + +return Def.ActorFrame { + Name = "DisplayMean", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.DisplayMeanX, MovableValues.DisplayMeanY) + self:zoom(MovableValues.DisplayMeanZoom) + end, + JudgmentMessageCommand = function(self, params) + -- should work fine only for judged taps, not misses or holds + if not params.HoldNoteScore and params.Offset ~= nil and params.Offset < 1000 then + curMeanSum = curMeanSum + params.Offset + curMeanCount = curMeanCount + 1 + self:playcommand("UpdateMeanText") + end + end, + PracticeModeResetMessageCommand = function(self) + curMeanCount = 0 + curMeanSum = 0 + self:playcommand("UpdateMeanText") + end, + UpdateMeanTextCommand = function(self) + local bg = self:GetChild("MeanBacking") + local perc = self:GetChild("DisplayMean") + + if perc then + if curMeanCount == 0 then + perc:settextf(formatstr, 0) + else + perc:settextf(formatstr, curMeanSum / curMeanCount) + end + end + if bg and perc then + bg:zoomto(perc:GetZoomedWidth() + bgMargin, perc:GetZoomedHeight() + bgMargin) + end + end, + + Def.Quad { + Name = "MeanBacking", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(bgMargin/2, -bgMargin/2) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + self:diffusealpha(bgalpha) + end + }, + LoadFont("Common Large") .. { + Name = "DisplayMean", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoom(meanTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + self:diffusealpha(1) + end, + } +} \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/measurecounter.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/measurecounter.lua new file mode 100644 index 0000000000..37be338f2a --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/measurecounter.lua @@ -0,0 +1,153 @@ +-- a counter which measures measures +-- displays the length and current progress of runs of the highest nps sections in the file + +local measures = {} + +-- current real beat within the current measure +local beatcounter = 0 + +-- current real measure index +local measure = 1 + +-- last active measure counter display run index +local thingy = 1 + +-- is the display visible +local active = false + +-- percentage of the nps peak of a file a run can reach before being forced to end +-- (a burst stops the current run) +local peakNPSThreshold = 0.625 + +-- amount of current measure NPS to be different from current run NPS, causing an end to the current run +-- (a break stops the current run) +local runNPSLossThreshold = 2 + +local totalmeasures = 0 + +local t = Def.ActorFrame { + Name = "MeasureCounter", + InitCommand = function(self) + + local steps = GAMESTATE:GetCurrentSteps() + local loot = steps:GetNPSPerMeasure(1) + + -- determine peak + local peak = 0 + for i = 1, #loot do + if loot[i] > peak then + peak = loot[i] + end + end + + local m_len = 0 + local m_spd = 0 + local m_start = 0 + for i = 1, #loot do + -- run init + -- set speed and start measure + if m_len == 0 then + m_spd = loot[i] + m_start = i + end + + if math.abs(m_spd - loot[i]) < runNPSLossThreshold then + -- if the past measure is not too much different from the current, continue the run + m_len = m_len + 1 + m_spd = (m_spd + loot[i]) / 2 + elseif m_len > 1 and m_spd > peak * peakNPSThreshold then + -- if this measure bursts above the nps threshold, end the run + measures[#measures + 1] = { m_start, m_len, m_spd } + m_len = 0 + else + m_len = 0 + end + end + + totalmeasures = #measures + + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + OnCommand = function(self) + self:visible(false) + + if totalmeasures > 0 then + -- if the start measure is a measure to display, go + if measure == measures[thingy][1] then + self:playcommand("Dootz") + end + end + + if allowedCustomization then + self:visible(true) + self:GetChild("Text"):settext("99 / 99") + end + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.MeasureCounterX, MovableValues.MeasureCounterY) + self:zoom(MovableValues.MeasureCounterZoom) + end, + DootzCommand = function(self) + active = true + self:visible(true) + + local txt = self:GetChild("Text") + local bg = self:GetChild("Background") + + txt:settextf("%d / %d", measure - measures[thingy][1], measures[thingy][2]) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight()) + end, + UnDootzCommand = function(self) + active = false + self:visible(false) + end, + BeatCrossedMessageCommand = function(self) + if totalmeasures > 0 and thingy <= totalmeasures then + beatcounter = beatcounter + 1 + if beatcounter == 4 then + measure = measure + 1 + beatcounter = 0 + + if measure == measures[thingy][1] then + self:playcommand("Dootz") + end + + if measure > measures[thingy][1] + measures[thingy][2] then + self:playcommand("UnDootz") + thingy = thingy + 1 + end + + if active then + self:playcommand("MeasureCrossed") + end + end + end + end, + MeasureCrossedCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("Background") + + if totalmeasures > 0 then + txt:settextf("%d / %d", measure - measures[thingy][1], measures[thingy][2]) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight()) + end + end, + + Def.Quad { + Name = "Background", + InitCommand = function(self) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end, + }, + LoadFont("Common Normal") .. { + Name = "Text", + } +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/miniprogressbar.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/miniprogressbar.lua new file mode 100644 index 0000000000..c045ddf4e0 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/miniprogressbar.lua @@ -0,0 +1,51 @@ +-- mini progress bar. song progress but small + +local width = GAMEPLAY:getItemWidth("miniProgressBar") +local height = GAMEPLAY:getItemHeight("miniProgressBar") +local alpha = 0.3 + +return Def.ActorFrame { + Name = "MiniProgressBar", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.MiniProgressBarX, MovableValues.MiniProgressBarY) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:zoomto(width, height) + self:diffuse(COLORS:getGameplayColor("MiniProgressBarBG")) + self:diffusealpha(alpha) + end, + }, + Def.Quad { + Name = "SongEnd", + InitCommand = function(self) + self:x(1 + width / 2) + self:zoomto(1, height) + self:diffuse(COLORS:getGameplayColor("MiniProgressBarEnd")) + self:diffusealpha(1) + end, + }, + Def.SongMeterDisplay { + Name = "Progress", + InitCommand = function(self) + self:SetUpdateRate(0.5) + end, + StreamWidth = width, + Stream = Def.Quad { + InitCommand = function(self) + self:zoomy(height) + self:diffuse(COLORS:getGameplayColor("MiniProgressBar")) + self:diffusealpha(1) + end + } + } +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/npsdisplay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/npsdisplay.lua new file mode 100644 index 0000000000..12396991b0 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/npsdisplay.lua @@ -0,0 +1,285 @@ +-- A moving average NPS calculator + +local enabledNPSDisplay = playerConfig:get_data().NPSDisplay +local enabledNPSGraph = playerConfig:get_data().NPSGraph + +local countNotesSeparately = GAMESTATE:CountNotesSeparately() +-- Generally, a smaller window will adapt faster, but a larger window will have a more stable value. +local maxWindow = 1 -- this will be the maximum size of the "window" in seconds. +local minWindow = 1 -- this will be the minimum size of the "window" in seconds. Unused for now. + +--Graph related stuff +local initialPeak = 10 -- Initial height of the NPS graph. +local graphWidth = GAMEPLAY:getItemWidth("npsGraph") +local graphHeight = GAMEPLAY:getItemHeight("npsGraph") +local npsDisplayTextSize = GAMEPLAY:getItemHeight("npsDisplayText") + +local maxVerts = 100 -- Higher numbers allows for more detailed graph that spans for a longer duration. But may lead to performance issues +local graphFreq = 0.2 -- The frequency in which the graph updates in seconds. +-------------------- + +local graphColor = COLORS:getGameplayColor("NPSGraph") +local npsWindow = maxWindow + +-- This table holds the timestamp of each judgment. +-- being considered for the moving average and the size of the chord. +-- (let's just call this notes for simplicity) +local noteTable = {} +local lastJudgment = "TapNoteScore_None" + +-- Total sum of notes inside the moving average window. +-- The values are added/subtracted whenever we add/remove a note from the noteTable. +-- This allows us to get the total sum of notes that were hit without +-- iterating through the entire noteTable to get the sum. +local noteSum = 0 +local peakNPS = 0 + +local translated_info = { + Peak = "Peak", + NPS = "NPS", +} + +--------------- +-- Functions -- +--------------- + +-- This function will take the player, the timestamp, +-- and the size of the chord and append it to noteTable. +-- The function will also add the size of the chord to noteSum +-- This function is called whenever a JudgmentMessageCommand for regular tap note occurs. +-- (simply put, whenever you hit/miss a note) +local function addNote(time, size) + if countNotesSeparately == true then + size = 1 + end + + noteTable[#noteTable + 1] = {time, size} + noteSum = noteSum + size +end + +-- This function is called every frame to check if there are notes that +-- are old enough to remove from the table. +-- Every time it is called, the function will loop and remove all old notes +-- from noteTable and subtract the corresponding chord size from noteSum. +local function removeNote() + local exit = false + while not exit do + if #noteTable >= 1 then + if noteTable[1][1] + npsWindow < GetTimeSinceStart() then + noteSum = noteSum - noteTable[1][2] + table.remove(noteTable, 1) + else + exit = true + end + else + exit = true + end + end +end + +-- The function simply Calculates the moving average NPS +-- Generally this is just nps = noteSum/window. +local function getCurNPS() + return noteSum / clamp(GAMESTATE:GetSongPosition():GetMusicSeconds(), minWindow, npsWindow) +end + +-- This is an update function that is being called every frame while this is loaded. +local function Update(self) + self.InitCommand = function(self) + self:SetUpdateFunction(Update) + end + + if enabledNPSDisplay or enabledNPSGraph then + -- We want to constantly check for old notes to remove and update the NPS counter text. + removeNote() + + curNPS = getCurNPS() + + -- Update peak nps. Only start updating after enough time has passed. + if GAMESTATE:GetSongPosition():GetMusicSeconds() > npsWindow then + peakNPS = math.max(peakNPS, curNPS) + end + -- the actor which called this update function passes itself down as "self". + -- we then have "self" look for the child named "Text" which you can see down below. + -- Then the settext function is called (or settextf for formatted ones) to set the text of the child "Text" + -- every time this function is called. + -- We don't display the decimal values due to lack of precision from having a relatively small time window. + if enabledNPSDisplay then + self:GetChild("NPSDisplay"):GetChild("Text"):settextf("%0.0f %s (%s %0.0f)", curNPS, translated_info["NPS"], translated_info["Peak"], peakNPS) + end + end +end + +local function npsDisplay() + local t = Def.ActorFrame { + Name = "NPSDisplay", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.NPSDisplayX, MovableValues.NPSDisplayY) + self:zoom(MovableValues.NPSDisplayZoom) + end, + -- Whenever a MessageCommand is broadcasted, + -- a table contanining parameters can also be passed along. + JudgmentMessageCommand = function(self, params) + local notes = params.Notes -- this is just one of those parameters. + + local chordsize = 0 + + if params.Type == "Tap" then + -- The notes parameter contains a table where the table indices + -- correspond to the columns in game. + -- The items in the table either contains a TapNote object (if there is a note) + -- or be simply nil (if there are no notes) + + -- Since we only want to count the number of notes in a chord, + -- we just iterate over the table and count the ones that aren't nil. + -- Set chordsize to 1 if notes are counted separately. + if GAMESTATE:GetCurrentGame():CountNotesSeparately() then + chordsize = 1 + else + for i = 1, GAMESTATE:GetCurrentStyle():ColumnsPerPlayer() do + if notes ~= nil and notes[i] ~= nil then + chordsize = chordsize + 1 + end + end + end + + -- add the note to noteTable + addNote(GetTimeSinceStart(), chordsize) + lastJudgment = params.TapNoteScore + end + end, + } + -- the text that will be updated by the update function. + if enabledNPSDisplay then + t[#t + 1] = LoadFont("Common Normal") .. { + Name = "Text", + InitCommand = function(self) + self:halign(0):valign(0) + self:settext("0 NPS (Peak 0.0)") + self:zoom(npsDisplayTextSize) + end, + } + end + + return t +end + +local function npsGraph() + local t = Def.ActorFrame { + Name = "NPSGraph", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.NPSGraphX, MovableValues.NPSGraphY) + self:zoomto(MovableValues.NPSGraphWidth, MovableValues.NPSGraphHeight) + end, + } + local verts = { + {{0, 0, 0}, graphColor} + } + local total = 1 + local peakNPS = initialPeak + local curNPS = 0 + t[#t + 1] = Def.Quad { + Name = "BG", + InitCommand = function(self) + self:zoomto(graphWidth, graphHeight) + self:xy(0, graphHeight) + self:diffuse(color("#333333")):diffusealpha(0.8) + self:horizalign(0):vertalign(2) + self:fadetop(1) + end, + } + + t[#t + 1] = Def.Quad { + Name = "Floor", + InitCommand = function(self) + self:zoomto(graphWidth, 1) + self:xy(0, graphHeight) + self:diffusealpha(0.5) + self:horizalign(0) + end, + } + + t[#t + 1] = Def.Quad { + Name = "Ceiling", + InitCommand = function(self) + self:zoomto(graphWidth, 1) + self:xy(0, 0) + self:diffusealpha(0.2) + self:horizalign(0) + end, + } + + t[#t + 1] = Def.ActorMultiVertex { + Name = "AMV_QuadStrip", + InitCommand = function(self) + self:visible(true) + self:xy(graphWidth, graphHeight) + self:SetDrawState {Mode = "DrawMode_LineStrip"} + end, + BeginCommand = function(self) + self:SetDrawState {First = 1, Num = -1} + self:SetVertices(verts) + self:queuecommand("GraphUpdate") + end, + GraphUpdateCommand = function(self) + total = total + 1 + curNPS = getCurNPS() + curJudgment = lastJudgment + + if curNPS > peakNPS then -- update height if there's a new peak NPS value + for i = 1, #verts do + verts[i][1][2] = verts[i][1][2] * (peakNPS / curNPS) + end + peakNPS = curNPS + end + + verts[#verts + 1] = {{total * (graphWidth / maxVerts), -curNPS / peakNPS * graphHeight, 0}, graphColor} + if #verts > maxVerts + 2 then -- Start removing unused verts. Otherwise RIP lag + table.remove(verts, 1) + end + self:SetVertices(verts) + self:addx(-graphWidth / maxVerts) + self:SetDrawState {First = math.max(1, #verts - maxVerts), Num = math.min(maxVerts, #verts)} + self:sleep(graphFreq) + self:queuecommand("GraphUpdate") + end, + } + return t +end + +local t = Def.ActorFrame { + Name = "NPSContainer", + BeginCommand = function(self) + if enabledNPSDisplay or enabledNPSGraph then + self:SetUpdateFunction(Update) + end + end, +} + +if enabledNPSDisplay then + t[#t + 1] = npsDisplay() +end +if enabledNPSGraph then + if not enabledNPSDisplay then + t[#t + 1] = npsDisplay() + end + t[#t + 1] = npsGraph() +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/playerinfo.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/playerinfo.lua new file mode 100644 index 0000000000..d3e74bf936 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/playerinfo.lua @@ -0,0 +1,139 @@ +-- Various player and stage info +local profileP1 = GetPlayerOrMachineProfile(PLAYER_1) + +local translated_info = { + Judge = "Judge", + Scoring = "Scoring", +} + +local modstringTextSize = GAMEPLAY:getItemHeight("playerInfoModsText") +local judgeDiffTextSize = GAMEPLAY:getItemHeight("playerInfoJudgeText") +local difficultyTextSize = GAMEPLAY:getItemHeight("playerInfoMeterText") +local msdTextSize = GAMEPLAY:getItemHeight("playerInfoMSDText") +local scoringTextSize = GAMEPLAY:getItemHeight("playerInfoScoreTypeText") + +local avatarSize = GAMEPLAY:getItemHeight("playerInfoAvatar") +local diffwidth = GAMEPLAY:getItemWidth("playerInfoMeter") +local diffXOffset = GAMEPLAY:getItemX("playerInfoMeterX") +local msdXOffset = GAMEPLAY:getItemX("playerInfoMSDX") +local msdwidth = diffXOffset - msdXOffset +local modsXOffset = GAMEPLAY:getItemX("playerInfoModsX") +local judgeXOffset = GAMEPLAY:getItemX("playerInfoJudgeX") +local scoreTypeXOffset = GAMEPLAY:getItemX("playerInfoScoreTypeX") + +return Def.ActorFrame { + Name = "PlayerInfo", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.PlayerInfoX, MovableValues.PlayerInfoY) + self:zoom(MovableValues.PlayerInfoZoom) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(modsXOffset + SCREEN_WIDTH/5, avatarSize) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + self:diffusealpha(0.1) + end, + }, + Def.Sprite { + Name = "Avatar", + InitCommand = function(self) + self:halign(0):valign(0) + end, + BeginCommand = function(self) + self:finishtweening() + self:Load(getAvatarPath(PLAYER_1)) + self:zoomto(avatarSize, avatarSize) + end + }, + LoadFont("Common Large") .. { + Name = "Difficulty", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(diffXOffset, avatarSize/2 - 5) + self:zoom(difficultyTextSize) + self:maxwidth(diffwidth) + end, + SetCommand = function(self) + self:settext(getDifficulty(GAMESTATE:GetCurrentSteps():GetDifficulty())) + self:diffuse( + colorByDifficulty( + GetCustomDifficulty( + GAMESTATE:GetCurrentSteps():GetStepsType(), + GAMESTATE:GetCurrentSteps():GetDifficulty() + ) + ) + ) + end, + DoneLoadingNextSongMessageCommand = function(self) + self:queuecommand("Set") + end + }, + LoadFont("Common Large") .. { + Name = "MSD", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(msdXOffset, avatarSize - 2) + self:zoom(msdTextSize) + self:maxwidth(msdwidth / msdTextSize) + end, + SetCommand = function(self) + local meter = GAMESTATE:GetCurrentSteps():GetMSD(getCurRateValue(), 1) + self:settextf("%05.2f", meter) + self:diffuse(COLORS:colorByMSD(meter)) + end, + DoneLoadingNextSongMessageCommand = function(self) + self:queuecommand("Set") + end, + CurrentRateChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + PracticeModeReloadMessageCommand = function(self) + self:queuecommand("Set") + end, + }, + LoadFont("Common Normal") .. { + Name = "ModString", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(modsXOffset, avatarSize - 2) + self:zoom(modstringTextSize) + self:maxwidth(SCREEN_WIDTH / 5 / modstringTextSize) + end, + BeginCommand = function(self) + self:settext(getModifierTranslations(GAMESTATE:GetPlayerState():GetPlayerOptionsString("ModsLevel_Current"))) + end + }, + LoadFont("Common Normal") .. { + Name = "Judge", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(judgeXOffset, avatarSize/24) + self:zoom(judgeDiffTextSize) + end, + BeginCommand = function(self) + self:settextf("%s: %d", translated_info["Judge"], GetTimingDifficulty()) + end + }, + LoadFont("Common Normal") .. { + Name = "ScoreType", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(scoreTypeXOffset, avatarSize/2 - avatarSize/8) + self:zoom(scoringTextSize) + end, + BeginCommand = function(self) + self:settextf("%s: %s", translated_info["Scoring"], "Wife") + end + }, +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/ratedisplay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/ratedisplay.lua new file mode 100644 index 0000000000..5d14565890 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/ratedisplay.lua @@ -0,0 +1,35 @@ +-- music rate. its the rate of the music + +local rateTextSize = GAMEPLAY:getItemHeight("rateDisplayText") + +return Def.ActorFrame { + Name = "MusicRate", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.MusicRateX, MovableValues.MusicRateY) + self:zoom(MovableValues.MusicRateZoom) + end, + + LoadFont("Common Normal") .. { + InitCommand = function(self) + self:zoom(rateTextSize) + self:playcommand("SetRate") + end, + SetRateCommand = function(self) + self:settext(getCurRateDisplayString()) + end, + DoneLoadingNextSongMessageCommand = function(self) + self:playcommand("SetRate") + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("SetRate") + end + }, +} diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/targettracker.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/targettracker.lua new file mode 100644 index 0000000000..915d9df541 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/elements/targettracker.lua @@ -0,0 +1,75 @@ +local target = playerConfig:get_data().TargetGoal +GAMESTATE:GetPlayerState():SetTargetGoal(target / 100) +local targetTrackerMode = playerConfig:get_data().TargetTrackerMode + +-- describes the difference between 480p and current resolution (theme elements are resized based on theme height) +-- for the purpose of scaling some hardcoded values for element sizing purposes +-- was extremely lazy here +local GAMEPLAY_SIZING_RATIO = (480 / SCREEN_HEIGHT) + +local t = Def.ActorFrame { + Name = "TargetTracker", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.TargetTrackerX, MovableValues.TargetTrackerY) + self:zoom(MovableValues.TargetTrackerZoom / GAMEPLAY_SIZING_RATIO) + end, +} + +local aheadColor = COLORS:getGameplayColor("TargetGoalAhead") +local behindColor = COLORS:getGameplayColor("TargetGoalBehind") + +if targetTrackerMode == 0 then + t[#t+1] = LoadFont("Common Normal") .. { + Name = "PercentDifferential", + InitCommand = function(self) + self:halign(0):valign(1) + self:settext("") + end, + SpottedOffsetCommand = function(self, params) + local tDiff = params.targetDiff + if tDiff >= 0 then + self:diffuse(aheadColor) + else + self:diffuse(behindColor) + end + self:settextf("%5.2f (%5.2f%%)", tDiff, target) + end + } +else + t[#t+1] = LoadFont("Common Normal") .. { + Name = "PBDifferential", + InitCommand = function(self) + self:halign(0):valign(1) + self:settext("") + end, + SpottedOffsetCommand = function(self, params) + local tDiff = params.targetDiff + if params and params.pbTarget then + if tDiff >= 0 then + self:diffuse(aheadColor) + else + self:diffuse(behindColor) + end + self:settextf("%5.2f (%5.2f%%)", tDiff, params.pbTarget * 100) + else + -- if set to pb goal but there is no pb, default to the set target value + if tDiff >= 0 then + self:diffuse(aheadColor) + else + self:diffuse(behindColor) + end + self:settextf("%5.2f (%5.2f%%)", tDiff, target) + end + end + } +end + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/titlesplash.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/titlesplash.lua new file mode 100644 index 0000000000..0bb8963dc1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay overlay/titlesplash.lua @@ -0,0 +1,135 @@ +local mods = {} + +local translated_info = { + InvalidMods = THEME:GetString("ScreenGameplay", "InvalidMods") +} + +local textSize = 0.8 +local subtextSize = 0.75 +local bigTextSize = 1.2 + +local width = SCREEN_WIDTH / 3 +local linesize = 75 / 1080 * SCREEN_HEIGHT +local height = linesize * 3 + +local t = Def.ActorFrame { + Name = "Splashy", + DootCommand = function(self) + self:RemoveAllChildren() + end, + Def.Quad { + Name = "DestroyMe", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + self:zoomto(width, height) + self:valign(1) + self:diffuse(getMainColor("PrimaryBackground")) + self:fadeleft(0.4):faderight(0.4) + self:diffusealpha(0) + end, + OnCommand = function(self) + self:smooth(0.5) + self:diffusealpha(0.7) + self:sleep(1) + self:smooth(0.7) + self:diffusealpha(0) + end + }, + LoadFont("Common Large") .. { + Name = "DestroyMe2", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y - linesize * 2) + self:zoom(textSize) + self:diffuse(getMainColor("PrimaryText")) + self:diffusealpha(0) + self:maxwidth(width / textSize) + end, + BeginCommand = function(self) + self:settext(GAMESTATE:GetCurrentSong():GetDisplayMainTitle()) + end, + OnCommand = function(self) + self:smooth(0.5) + self:diffusealpha(1) + self:sleep(1) + self:smooth(0.7) + self:diffusealpha(0) + end + }, + LoadFont("Common Normal") .. { + Name = "DestroyMe3", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y - linesize * 1.5) + self:zoom(subtextSize) + self:diffuse(getMainColor("PrimaryText")) + self:diffusealpha(0) + self:maxwidth(width / subtextSize) + end, + BeginCommand = function(self) + self:settext(GAMESTATE:GetCurrentSong():GetDisplaySubTitle()) + end, + OnCommand = function(self) + self:smooth(0.5) + self:diffusealpha(1) + self:sleep(1) + self:smooth(0.7) + self:diffusealpha(0) + end + }, + LoadFont("Common Normal") .. { + Name = "DestroyMe4", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y - linesize) + self:zoom(subtextSize) + self:diffuse(getMainColor("PrimaryText")) + self:diffusealpha(0) + self:maxwidth(width / subtextSize) + end, + BeginCommand = function(self) + self:settext(GAMESTATE:GetCurrentSong():GetDisplayArtist()) + end, + OnCommand = function(self) + local time = 0.4 + if #mods > 0 then + time = 2 + end + self:smooth(0.5) + self:diffusealpha(1) + self:sleep(1) + self:smooth(0.7) + self:diffusealpha(0) + self:sleep(time) + self:queuecommand("Doot") + end, + DootCommand = function(self) + self:GetParent():queuecommand("Doot") + end + }, + LoadFont("Common Normal") .. { + Name = "DestroyMe5", + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y + linesize) + self:zoom(bigTextSize) + self:diffuse(getMainColor("PrimaryText")) + self:diffusealpha(0) + self:valign(0) + end, + BeginCommand = function(self) + mods = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():GetInvalidatingMods() + local translated = {} + if #mods > 0 then + for _,mod in ipairs(mods) do + table.insert(translated, THEME:HasString("OptionNames", mod) and THEME:GetString("OptionNames", mod) or mod) + end + self:settextf("%s\n%s", translated_info["InvalidMods"], table.concat(translated, "\n")) + end + end, + OnCommand = function(self) + self:smooth(0.5) + self:diffusealpha(1) + self:sleep(1) + self:smooth(2.7) + self:diffusealpha(0) + end + } +} +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay toasty/default.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay toasty/default.lua new file mode 100644 index 0000000000..002d0475ba --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay toasty/default.lua @@ -0,0 +1,35 @@ +-- look for a custom lua file and if there is one load it instead +if FILEMAN:DoesFileExist(getAssetPath("toasty").."/default.lua") then + local t = Def.ActorFrame {} + t[#t+1] = LoadActor("../../../../" .. getAssetPath("toasty") .. "/default") + return t + end + +local t = + Def.ActorFrame { + Def.Sprite { + InitCommand = function(self) + self:xy(SCREEN_WIDTH + 100, SCREEN_CENTER_Y) + self:Load(getToastyAssetPath("image")) + end, + StartTransitioningCommand = function(self) + self:diffusealpha(1) + self:decelerate(0.25) + self:x(SCREEN_WIDTH - 100) + self:sleep(1.75) + self:accelerate(0.5) + self:x(SCREEN_WIDTH + 100) + self:linear(0) + self:diffusealpha(0) + end + }, + Def.Sound { + InitCommand = function(self) + self:load(getToastyAssetPath("sound")) + end, + StartTransitioningCommand = function(self) + self:play() + end + } +} +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplay underlay.lua b/Themes/Rebirth/BGAnimations/ScreenGameplay underlay.lua new file mode 100644 index 0000000000..7ed87de1a0 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplay underlay.lua @@ -0,0 +1,47 @@ +-- the literal background is handled by the c++ +-- so here we micromanage underlay layer stuff + +-- also permamirror and receptorsize/mini because this is early in gameplay init again +local modslevel = "ModsLevel_Preferred" +local playeroptions = GAMESTATE:GetPlayerState():GetPlayerOptions(modslevel) +local profile = PROFILEMAN:GetProfile(PLAYER_1) +local replaystate = GAMESTATE:GetPlayerState():GetPlayerController() == "PlayerController_Replay" + +-- turn on mirror if song is flagged as perma mirror +if profile:IsCurrentChartPermamirror() and not replaystate then + playeroptions:Mirror(true) +end + +-- dont apply the player defined receptor size mini if viewing an emulated replay +local emulating = PREFSMAN:GetPreference("ReplaysUseScoreMods") and replaystate +if not emulating then + playeroptions:Mini(2 - playerConfig:get_data().ReceptorSize / 50) +end + +-- we can set staticbg prefs here and somehow that works out to be early enough to matter +local staticbg = themeConfig:get_data().global.StaticBackgrounds +local songoptions = GAMESTATE:GetSongOptionsObject("ModsLevel_Preferred") +if staticbg then + songoptions:StaticBackground(true) + songoptions:RandomBGOnly(false) +else + songoptions:StaticBackground(false) + songoptions:RandomBGOnly(false) +end + +local showbgs = themeConfig:get_data().global.ShowBackgrounds +if not showbgs then + return Def.ActorFrame { + Def.Quad { + Name = "SCUFFEDBACKGROUND", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + self:diffuse(color("#000000")) + self:diffusealpha(1) + end, + }, + } +end + +return Def.ActorFrame {} \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenGameplayPractice out.redir b/Themes/Rebirth/BGAnimations/ScreenGameplayPractice out.redir new file mode 100644 index 0000000000..51f3c31129 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenGameplayPractice out.redir @@ -0,0 +1 @@ +_fadeout \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/default.lua new file mode 100644 index 0000000000..87d32c4085 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/default.lua @@ -0,0 +1,6 @@ +local t = Def.ActorFrame {Name = "OverlayFile"} + +t[#t+1] = LoadActor("helpDisplay") +t[#t+1] = LoadActor("../_mouse.lua") + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/helpDisplay.lua b/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/helpDisplay.lua new file mode 100644 index 0000000000..c9049bb57b --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenHelpMenu overlay/helpDisplay.lua @@ -0,0 +1,965 @@ + +local ratios = { + InfoTopGap = 25 / 1080, -- top edge screen to top edge box + InfoLeftGap = 60 / 1920, -- left edge screen to left edge box + InfoWidth = 1799 / 1920, -- small box width + InfoHeight = 198 / 1080, -- small box height + InfoHorizontalBuffer = 40 / 1920, -- from side of box to side of text + InfoVerticalBuffer = 28 / 1080, -- from top/bottom edge of box to top/bottom edge of text + + MainDisplayTopGap = 250 / 1080, -- top edge screen to top edge box + MainDisplayLeftGap = 60 / 1920, -- left edge screen to left edge box + MainDisplayWidth = 1799 / 1920, -- big box width + MainDisplayHeight = 800 / 1080, -- big box height + + ScrollerWidth = 15 / 1920, -- width of the scroll bar and its area (height dependent on items) + ListWidth = 405 / 1920, -- from right edge of scroll bar to left edge of separation gap + SeparationGapWidth = 82 / 1920, -- width of the separation area between selection list and the info area + + TopBuffer = 45 / 1080, -- buffer from the top of any section to any item within the section + TopBuffer2 = 119 / 1080, -- from top edge of section to the subtitle text + TopBuffer3 = 200 / 1080, -- from top edge of section to the description text + EdgeBuffer = 25 / 1920, -- buffer from the edge of any section to any item within the section + + IconExitWidth = 47 / 1920, + IconExitHeight = 36 / 1080, +} + +local actuals = { + InfoTopGap = ratios.InfoTopGap * SCREEN_HEIGHT, + InfoLeftGap = ratios.InfoLeftGap * SCREEN_WIDTH, + InfoWidth = ratios.InfoWidth * SCREEN_WIDTH, + InfoHeight = ratios.InfoHeight * SCREEN_HEIGHT, + InfoHorizontalBuffer = ratios.InfoHorizontalBuffer * SCREEN_WIDTH, + InfoVerticalBuffer = ratios.InfoVerticalBuffer * SCREEN_HEIGHT, + MainDisplayTopGap = ratios.MainDisplayTopGap * SCREEN_HEIGHT, + MainDisplayLeftGap = ratios.MainDisplayLeftGap * SCREEN_WIDTH, + MainDisplayWidth = ratios.MainDisplayWidth * SCREEN_WIDTH, + MainDisplayHeight = ratios.MainDisplayHeight * SCREEN_HEIGHT, + ScrollerWidth = ratios.ScrollerWidth * SCREEN_WIDTH, + ListWidth = ratios.ListWidth * SCREEN_WIDTH, + SeparationGapWidth = ratios.SeparationGapWidth * SCREEN_WIDTH, + TopBuffer = ratios.TopBuffer * SCREEN_HEIGHT, + TopBuffer2 = ratios.TopBuffer2 * SCREEN_HEIGHT, + TopBuffer3 = ratios.TopBuffer3 * SCREEN_HEIGHT, + EdgeBuffer = ratios.EdgeBuffer * SCREEN_WIDTH, + IconExitWidth = ratios.IconExitWidth * SCREEN_WIDTH, + IconExitHeight = ratios.IconExitHeight * SCREEN_HEIGHT, +} + +local infoTextSize = 0.65 +local listTextSize = 0.4 +local titleTextSize = 0.95 +local subtitleTextSize = 0.55 +local descTextSize = 0.4 + +local textZoomFudge = 5 +local buttonHoverAlpha = 0.3 +local cursorAlpha = 0.3 +local cursorAnimationSeconds = 0.1 +local animationSeconds = 0.1 + +-- special handling to make sure our beautiful icon doesnt get tarnished +local logosourceHeight = 133 +local logosourceWidth = 102 +local logoratio = math.min(1920 / SCREEN_WIDTH, 1080 / SCREEN_HEIGHT) +local logoH, logoW = getHWKeepAspectRatio(logosourceHeight, logosourceWidth, logosourceWidth / logosourceWidth) + +local function helpMenu() + + -- describe each category + -- this appears in the scroll list and large above the main screen + -- the point of this table is solely to maintain the order of option categories that show up + local categoryDefs = { + "Common Pattern Terminology", + "Hotkeys", + "How-To", + "Information", + "Troubleshooting", + } + + -- describe each option in each category + -- each option within each category shows up in exactly that order. the categories do not use this order (refer to the above table instead) + -- the definition is: + --[[ + ["CategoryDef Entry"] = { + [1] = { + Name = "OptionName", -- this appears in the scroll list and large in the main area + ShortDescription = "1 sentence", -- this appears as a subtext to the large text in the main area + Description = "a paragraph", -- this appears as regular text in the remaining area + Image = "path to an image", -- OPTIONAL -- if supplied, this takes up the right half of the main area + }, + [2] = {}, .... + } + ]] + local optionDefs = { + ["Common Pattern Terminology"] = { + { + Name = "Roll (1234)", + ShortDescription = "Vaguely a jumptrill", + Description = "Roll is the common name given to this type of pattern which requires you to press all columns in succession before repeating any columns again. It comes in several forms.\n\nThe specific roll depicted here may be referred to as an ascending roll. If it went in the opposite direction (4321) it would be descending.\n\nThe direction of the roll does not affect its capacity to be jumptrilled. Only the speed of the roll does.", + Image = THEME:GetPathG("", "Patterns/1234 roll"), + }, + { + Name = "Roll (1243)", + ShortDescription = "Vaguely a jumptrill", + Description = "Roll is the common name given to this type of pattern which requires you to press all columns in succession before repeating any columns again. It comes in several forms.\n\nThe specific roll depicted here may be referred to as a split roll. If the pattern began with the opposite hand (4312) it would be functionally equivalent.\n\nRegardless, this roll variant can be effectively jumptrilled if its speed is sufficient.", + Image = THEME:GetPathG("", "Patterns/1243 roll"), + }, + { + Name = "Roll (4132)", + ShortDescription = "Vaguely a split jumptrill", + Description = "Roll is the common name given to this type of pattern which requires you to press all columns in succession before repeating any columns again. It comes in several forms.\n\nThe specific roll depicted here may be referred to as a split roll, split hand roll, or split trill. It may also begin in reverse order (3241, inside out) but still remains functionally equivalent.\n\nThis roll variant is more difficult to jumptrill than others. When hit quickly, it is physically similar to a split jumptrill.", + Image = THEME:GetPathG("", "Patterns/1423 roll"), + }, + { + Name = "Gluts", + ShortDescription = "Another word for 'in excess'", + Description = "Gluts is a broad term which captures jumpgluts and handgluts. It's frequently debated what defines a glut or even if a handglut exists.\n\nThe most accepted definition of a glut is literal: jumpgluts are many continuous jumps (jumpjacks) which typically form minijacks as they change column pairs over the course of the glut run. Depicted in the image is a run of jumpjacks referred to as gluts because of the minijacks on column 1 and 4.\n\nGluts are a subset of chordjacks with more focus on jack speed than chords.", + Image = THEME:GetPathG("", "Patterns/gluts"), + }, + { + Name = "Chordjack", + ShortDescription = "Chords which form jacks", + Description = "Chordjack is the blanket term for patterns made up entirely of n-chords which form jacks.\n\nThe chords contained in the overall pattern do not have to be the same. Jumps, hands, or quads are valid. Depicted to the right is a very generic medium density chordjack pattern containing only jumps and hands.", + Image = THEME:GetPathG("", "Patterns/chordjacks"), + }, + { + Name = "Dense Chordjack", + ShortDescription = "Don't break your keyboard", + Description = "Dense chordjacks are a specialization of chordjacks which are biased toward hands and quads. At high enough density, this may be referred to as holedodge because when reading, there are more notes than empty spaces.\n\nDense chordjacks require a lot of stamina and tend to have embedded longjacks due to the pattern being almost entirely hands and quads.", + Image = THEME:GetPathG("", "Patterns/dense chordjack"), + }, + { + Name = "Stream", + ShortDescription = "Continuous single taps", + Description = "Just like the name implies, a stream is a continuous stream of notes. More specifically, these continuous notes are mostly on separate columns, not forming jacks. There can be any kind of variation to the patterning as long as it doesn't deviate too much from the pure definition.\n\nMinijacks, chords, or other patterns that can be embedded may be found within a stream, but only serve to make it more difficult unless they dominate the overall pattern.\n\nTo the right is a stream which is slightly rolly.", + Image = THEME:GetPathG("", "Patterns/streams"), + }, + { + Name = "Jumpstream", + ShortDescription = "Stream with jumps", + Description = "Jumpstream expands the definition of a stream by requiring jumps at a certain frequency within the pattern. Other than that, it is still a stream.\n\nDepicted to the right is a simple jumpstream pattern with an anchor on column 1. A more observant player may also recognize that this pattern in isolation can be jumptrilled.", + Image = THEME:GetPathG("", "Patterns/jumpstream"), + }, + { + Name = "Handstream", + ShortDescription = "Stream with hands", + Description = "Handstream expands the definition of a stream by requiring hands at a certain frequency within the pattern. It is also not uncommon to find jumps embedded within a handstream. This may be referred to as dense handstream, although the most dense handstream is purely hands and single taps.\n\nAnchors are more common in a handstream since the charter only has 4 ways to fit a hand into 4 columns. In the depiction to the right, there is an anchor on column 4 and, depending who you ask, also column 3.", + Image = THEME:GetPathG("", "Patterns/handstream"), + }, + { + Name = "Quadstream", + ShortDescription = "Stream with quads", + Description = "Quadstream takes the definition of stream so far that it begins to look like chordjacks.\n\nIt forms a minijack with a quad using a single tap (usually) and still flows like a stream as opposed to pure chordjacks.\n\nIncreasing the density of quads will lose this characteristic due to the limited column space.", + Image = THEME:GetPathG("", "Patterns/quadstream"), + }, + { + Name = "Trill", + ShortDescription = "Like musical theory", + Description = "A trill is a sequence of two continuously alternating notes. In the context of this game, they can be either on one hand or split between both hands.\n\nWhen a trill is one handed, it is called a one hand trill. Otherwise, it is a two hand trill. Trills are not restricted to two adjacent columns, and can be on columns 1 and 3 for example.\n\nDepicted to the right is a simple two hand trill.", + Image = THEME:GetPathG("", "Patterns/trill"), + }, + { + Name = "Jumptrill", + ShortDescription = "Can be played blind", + Description = "Jumptrill is a very simple expansion of a two hand trill. Jump on one hand and then jump on the other. Most often this pattern makes up the highest NPS section of a chart unless it is dense chordjacks.\n\nOne special thing about the ordinary jumptrill is that many other patterns can be broken down into a jumptrill, which allows cheese-oriented gameplay. We don't recommend doing this too frequently for the sake of your scores and habit forming.", + Image = THEME:GetPathG("", "Patterns/jumptrill"), + }, + { + Name = "Split Jumptrill [13][24]", + ShortDescription = "Jumptrill but dangerous", + Description = "Split jumptrill is a shuffled jumptrill. It forms two one hand trills instead of one pure jumptrill.\n\nSplit jumptrills are very annoying due to many players' inability to hit them fluently. This specific variant of split jumptrills can be more difficult than the other variant of split jumptrills because a player tends to be more inclined to jumptrill or roll when simultaneously doing a same-direction rolling motion with both hands. Luckily they can be jumptrilled if hit just right.\n\nTo the right is a long split jumptrill.", + Image = THEME:GetPathG("", "Patterns/13 split jt"), + }, + { + Name = "Split Jumptrill [14][23]", + ShortDescription = "Jumptrill but dangerous", + Description = "Split jumptrill is a shuffled jumptrill. It forms two one hand trills instead of one pure jumptrill.\n\nSplit jumptrills are very annoying due to many players' inability to hit them fluently. Compared to the other variant of split jumptrills, this one is usually easier because it can feel more natural. Anyways, they can be jumptrilled if hit just right.\n\nTo the right is a long split jumptrill.", + Image = THEME:GetPathG("", "Patterns/14 split jt"), + }, + { + Name = "Minijacks", + ShortDescription = "Instant combo breaker", + Description = "Minijacks are pairs of jacks. They can be continuous like the image to the right or embedded in some other pattern like a stream.\nMinijacks can be difficult to hit accurately as they get closer together because of the nature of the hit window. Imagine hitting one note within 180ms. Now hit two notes in that same window. If the minijack is fast enough or the player is slow enough, it's guaranteed points lost.\nMinijacks can be embedded within broader jack oriented patterns as a jack burst. Most commonly they are in isolation or in stream transitions that jack instead of trill.", + Image = THEME:GetPathG("", "Patterns/minijacks"), + }, + { + Name = "Longjack", + ShortDescription = "Continuous taps", + Description = "A jack, or jackhammer, is a set of continuous taps in the same column. A longjack is the same thing, but a little longer than usual.\n\nThe length of a jack that is considered a longjack is debated, but generally it is around 5-6 notes. The longjack can continue into infinity.\n\nLongjacks are the base pattern which make up continuous jumpjacks, handjacks, or quadjacks. This base pattern also makes up much of the structure for files oriented towards the vibro playstyle.", + Image = THEME:GetPathG("", "Patterns/longjack"), + }, + { + Name = "Anchor", + ShortDescription = "Basically embedded longjacks", + Description = "An anchor is a common continuous set of columns being utilized relative to the other columns contained in a pattern. Most commonly, anchors are only one column at a time.\n\nAn anchor may be on a snap alternating from the rest of the pattern or not, or a bit of both. Anchors that last a while break down to be longjacks, which will cause an overall pattern to be more stamina draining.\n\nThe image to the right depicts an anchor on column 4.", + Image = THEME:GetPathG("", "Patterns/anchor"), + }, + { + Name = "Minedodge", + ShortDescription = "Spicy notes", + Description = "Minedodge is the term for a type of chart or general pattern which contains notes that are intentionally placed near mines to increase difficulty.\n\nMinedodge does not actually change the MSD of a chart, because the calculator measures physical difficulty of the taps.\n\nThe difficulty of minedodge comes from the necessity of more precisely timing the press and release of notes and increased difficulty to read the notes depending on the noteskin used.", + Image = THEME:GetPathG("", "Patterns/minedodge"), + }, + { + Name = "Hold", + ShortDescription = "Don't let go", + Description = "Holds are a note type which require the player to hold the button for the entire duration of the note. They may also be referred to as freezes or long notes.\n\nPatterns made up of more holds are sometimes referred to as a holdstream or, at the extreme end of the spectrum, full inverse (all empty space is a hold).\n\nIn this game, a hold can be released for a short period of time dependent on the judge difficulty. On judge 4, the time is 250ms. Holds can then be regrabbed. Holds do not have release timing, but release timing can be emulated with a lift or mine.", + Image = THEME:GetPathG("", "Patterns/hold"), + }, + { + Name = "Roll / Rolld", + ShortDescription = "Keep tapping", + Description = "Roll note types, not to be confused with the roll pattern, are a hold type which require the player to continuously tap the button for the duration of the note. They may also be referred to as a rolld.\n\nRolls cannot be held and must be continuously tapped. The speed of the tap has a threshold the player receives no judgment for, but it gets smaller at higher judge difficulties. On judge 4, the player has up to 500ms between taps.", + Image = THEME:GetPathG("", "Patterns/rolld"), + }, + { + Name = "Burst", + ShortDescription = "Explosive speed", + Description = "A burst is a pattern specialization which means exactly what it says. Relative to its pattern context, a burst is much quicker.\n\nBursts can come in any form which matches that definition, not only the scenario depicted to the right. Jacks and jumpstream can also burst. The point is that it is a quicker collection of notes, almost like a compressed pattern.\n\nOften, a burst is patterned in such a way that it isn't difficult to full combo. But that isn't always the case!", + Image = THEME:GetPathG("", "Patterns/burst"), + }, + { + Name = "Polyrhythm", + ShortDescription = "Brain melting patterns", + Description = "Polyrhythms are a pattern specialization which indicates that multiple rhythms are being charted simultaneously, leading to alternating snaps being utilized. Sometimes the result of this is a very awkward, technically difficult to execute pattern.\n\nTo the right is a depiction of a simpler polyrhythm of 16ths and 12ths.", + Image = THEME:GetPathG("", "Patterns/polyrhythms"), + }, + { + Name = "Graces", + ShortDescription = "A little extra flare", + Description = "Grace notes are slightly offset notes, exceptionally rarely forming minijacks, which represent a kind of grace or extra flare to an initial note.\n\nIn musical theory, these are defined as not so necessary, but typically when these are charted it means that a grace note was present in the music.\n\nFlams are made up of graces. Within this game, both usually mean the same. Graces usually break down to be a single chord, and can rarely contain chords themselves.", + Image = THEME:GetPathG("", "Patterns/graces"), + }, + { + Name = "Runningman", + ShortDescription = "Anchored stream", + Description = "Classically, runningman is a term referring to a stream that is anchored. We expand on that definition by allowing chords to be mixed in very lightly. An anchored jumpstream can technically contain a runningman, but is more likely to just be referred to as anchored jumpstream.\n\nIt is required that the anchor in a runningman be offset from the rest of the pattern so that it doesn't form chords with the rest of the pattern.\n\nTo the right is a runningman anchored on column 1. The off-taps may be on any column, but it is important that not too many taps be on the same hand as the anchor.", + Image = THEME:GetPathG("", "Patterns/runningman"), + }, + }, + ["Hotkeys"] = { + { + Name = "Global Hotkeys", + ShortDescription = "Hotkeys available anywhere", + Description = "F2 -- Reload Textures & Metrics\nF2 + Shift -- Reload Metrics\nF2 + Ctrl -- Reload Scripts\nF2 + Shift + Ctrl -- Reload Overlay Screens\nF3 -- Debug Menu (many shortcut options within)\nF9 -- Toggle author-defined metadata transliteration\nAlt + Enter -- Fullscreen Toggle\nTab -- Speed up animations x4\n~ -- Slow down animations x4\nPause|Break -- Toggle menu sounds\nPrintScreen -- Screenshot", + Image = nil, + }, + { + Name = "SelectMusic Hotkeys", + ShortDescription = "Hotkeys only in song selection", + Description = "F1 -- Song Search\nCtrl + Q -- Load new song folders\nShift + Ctrl + R -- Reload current song folder\nShift + Ctrl + P -- Reload current pack folder\nCtrl + F -- Favorite chart toggle\nCtrl + M -- Permanent mirror chart toggle\nCtrl + G -- Create goal on current chart\nCtrl + O -- Toggle practice mode\nCtrl + S -- Save profile\nCtrl + P -- Create playlist\nCtrl + A -- Add current chart to selected playlist\nCtrl + Number -- If main box active, access the top right buttons. If not, access main box tabs.\nNumber -- Switch tabs in main box/settings\nEscape/Back -- In side box context, exit to main box\nCtrl + L -- Login/Logout\nSpace -- General Tab/Settings Chart Preview toggle\nRight Click -- Pause music toggle if functionality not overridden", + Image = nil, + }, + { + Name = "Gameplay Hotkeys", + ShortDescription = "Hotkeys only in regular gameplay", + Description = "Hold Enter -- Force Fail\nF4 -- Undo sync changes before saving them\nF6 -- Toggle through Autosync Song, Autosync Machine\nF7 -- Toggle claps\nLeftShift + F7 -- Toggle metronome\nF8 -- Toggle autoplay\nF11 -- Move song offset earlier\nAlt + F11 -- Move song offset earlier (small increment)\nF12 -- Move song offset later\nAlt + F12 -- Move song offset later (small increment)\nShift + F11 -- Move global offset earlier\nAlt + Shift + F11 -- Move global offset earlier (small increment)\nShift + F12 -- Move global offset later\nAlt + Shift + F12 -- Move global offset later (small increment)", + Image = nil, + }, + { + Name = "Customize Gameplay Hotkeys", + ShortDescription = "Hotkeys only in gameplay customization", + Description = "Enter -- Select element or Deselect element\nDelete/Backspace/RestartGameplay -- Undo only the most recent change\nCtrl + Undo -- Reset selected element to default\nRight Click -- Deselect element\nDirectional -- Menu movement or selected element movement\nShift + Directional -- Smaller increment movement\nSpace -- Scroll through movement types of selected element", + Image = nil, + }, + { + Name = "Practice Hotkeys", + ShortDescription = "Hotkeys only in practice mode", + Description = "Backspace -- Jump to loop region start or bookmarked position\nEffectUp -- Increase rate 0.05x\nEffectDown -- Decrease rate 0.05x\nInsertCoin -- Set bookmark position or loop region boundaries\nInsertCoin twice while paused -- Reset loop region to a bookmark position\nMousewheel Scrolling -- If paused, move song position in fine increments\nRight Click -- Toggle pause\nLeft Click Graph -- Jump to position\nRight Click Graph -- Set bookmark or loop region boundaries", + Image = nil, + }, + { + Name = "Replay Hotkeys", + ShortDescription = "Hotkeys only in replays", + Description = "InsertCoin -- Toggle pause\n\nMust be paused before using any of the following:\nAlt + EffectUp -- Set bookmark position\nAlt + EffectDown -- Go to bookmark position\nShift + EffectUp -- Increase rate 0.05x\nShift + EffectDown -- Decrease rate 0.05x\nEffectUp -- Move song position 5 seconds\nCtrl + EffectUp -- Move song position 0.1 seconds\nEffectDown -- Move song position -5 seconds\nCtrl + EffectDown -- Move song position -0.01 seconds", + Image = nil, + }, + { + Name = "Evaluation Hotkeys", + ShortDescription = "Hotkeys only in the evaluation screen", + Description = "Ctrl + L -- Login/Logout\nUp/Down -- Scroll through column highlight settings\nEffectUp -- Increase display judge\nEffectDown -- Decrease display judge\nSelect -- Screenshot\nLeft + Right -- Screenshot", + Image = nil, + }, + { + Name = "Multiplayer Hotkeys", + ShortDescription = "Hotkeys only in multiplayer", + Description = "Multiplayer isn't finished!", + Image = nil, + } + }, + ["How-To"] = { + { + Name = "Create Charts", + ShortDescription = "Notes to noises", + Description = "You need an editor. Etterna doesn't currently come with an editor. The popular choices are ArrowVortex and DDReam. Others also choose to use the osu editor or older SM3/SM5 editors.\n\nWhat matters is you have a way to place notes and get them to a valid format like .sm.\n\nA chart only requires audio and the metadata file (.sm for example) to load. Be careful not to attempt to load the chart before both of these are present in your Etterna Songs folder.\n\nMore extensive tutorials on chart creation exist online, and most people are willing to help if you ask around.", + Image = nil, + }, + { + Name = "Manual Song Install", + ShortDescription = "When downloading in client isn't your thing", + Description = "Here's the scenario: you've created or downloaded a single song or a pack of songs. Now you need to make them load in the game.\n\nIdeally, the folders are structured in this pattern - /Packname/Songname/stuff.sm\n\nIf this applies to a pack you just downloaded, all you must do is extract the pack folder and place it in your Songs folder. The structure is then /Songs/Packname/Songname/stuff.sm.\n\nIf you have a single song, you should create a new pack folder for it in the Songs folder. It can have any name. After all of this is completed, either reopen the game or press Ctrl + Q in SelectMusic.\n\nIf for some reason you want to keep your install separate from your songs, open Preferences.ini and add a direct path to a new Songs folder in the AdditionalSongFolders field.", + Image = nil, + }, + { + Name = "Download Packs", + ShortDescription = "How to press a button", + Description = "Ingame, downloading packs is very simple. If you have no packs installed, you may be led first to the core bundle select screen which shows a few sets of packs of varying difficulty to get you started.\n\nIf you skipped that, don't want the bundles, or already have something installed, you can use the downloader screen in SelectMusic. In the top right, in the Ctrl + 3 menu, you should find every pack you could download from ingame. If this list is blank, you may not have an internet connection.\n\nThere are other sources of packs if these downloads ever fail which you must ask around for.\n\nDownloaded packs will extract and install automatically when finished. If it appears that they finish but nothing happens, the download may have failed instead.", + Image = nil, + }, + { + Name = "Song Search", + ShortDescription = "Finding the song", + Description = "You may have been expecting to be able to search for a song that you don't have installed. Let's get that out of the way first - it isn't currently possible from within the client.\n\nYou can search for songs that are installed using the F1 menu (the leftmost button in the top right of SelectMusic). The interface should be simple to use. Just fill out the fields you care for and hit enter, and the results come to you.\n\nOtherwise, places that host packs may offer search capabilities.", + Image = nil, + }, + { + Name = "Song Filter", + ShortDescription = "Filtering the songs", + Description = "The song filter is integrated with song search. It can be found in the F1 menu (the leftmost button in the top right of SelectMusic).\n\nThere is not currently any keyboard compatibility with this menu, but the sliders should be sufficient to let you set up a filter with your mouse. A slider placed at an extreme end is considered effectively 0 or infinite, a disabled bound.\n\nFilters stick if you happen to enter a song and come back, unlike the song search.", + Image = nil, + }, + { + Name = "Sortmodes", + ShortDescription = "Sorting the songs", + Description = "The song wheel can be resorted in different predefined ways.\n\nTry pressing up-down-up-down in the main area, and it changes your wheel into a menu to select a sortmode. Some people will find some sortmodes more useful than others.\n\nMost of the time, Group sort is used because it is the natural order everyone expects. Some sorts are for more informational purposes.\n\nIf you would like to modify the behavior of these sortmodes, check out the implementation in Scripts/WheelDataManager.", + Image = nil, + }, + { + Name = "Customize Gameplay", + ShortDescription = "Power at your fingertips", + Description = "Customize Gameplay is the gameplay screen which allows you to modify every element on the screen in almost any way to make it fit to your standard.\n\nOn the right side is the navigation and information panel which can be dragged around for visibility. The arrow keys can be used if the mouse is not preferred.\n\nEnter to select a highlighted element, or click an element to select it. Use space to change which movement type you are modifying (coordinates vs sizing). Some elements do not offer more than one type. The mouse can be used to drag around any element.\n\nHold shift to move with smaller increments. Use RestartGameplay to undo or hold Ctrl with it to reset to default.\n\nAll changes are saved to disk as soon as you exit the screen.", + Image = nil, + }, + { + Name = "Update Etterna", + ShortDescription = "Backup your user data", + Description = "Updating Etterna is typically a painless process on all platforms. In all cases, we would recommend you take a backup of any content you directly added to the game - Noteskins, Save, Assets. Songs do not need to be backed up.\n\nOn Windows, the installer may ask for you to uninstall the old version, and the uninstaller can fail. It's safe to ignore that. As long as you are not directly overlaying the latest version on top of the old one, your new install will run fine.\n\nOn Mac, updating is simply reinstalling the game again, but with less configuration.\n\nOn Linux, updating can be moving to the latest binary or a git pull and rebuild.\n\nThe most important folder you never want to lose is the entire Save folder. Nearly every file in this folder is related to user scores or settings in some way.", + Image = nil, + }, + { + Name = "Set Sync/Offset", + ShortDescription = "Notes follow music, you know", + Description = "Every player will experience an issue at some point which manifests in their tap offsets being generally late or generally early. This translates to a late or early mean. In any scenario, a negative offset or mean indicates 'early.'\n\nIf you suffer from an unplayable offset or a consistently bad mean (> 5-10ms +/-) here's a few things to try.\nEtterna offers a visual offset (also called the judge offset), a global (machine) offset, and a song offset. The song offset is set by each chart author, and if it ever ends up wrong, that tends to be their fault and not yours.\nMoving the visual offset moves the visual position where a perfect hit is relative to your receptors. To change it, find it in the F3 menu or in the SelectMusic settings.\nGlobal offset applies to all songs in addition to the song offset. It changes the position of the audio relative to the notes. Ideally, it is 0ms, but can be nonzero if you have some audio/monitor/input related discrepancies.\nGlobal and song offsets can be set automatically by going into a song and pressing F6, then playing to the best of your ability. Try not to let autosync run more than 2 iterations.\nYou may also try moving them manually with F11/F12.", + Image = nil, + }, + { + Name = "Change Theme", + ShortDescription = "It's not called a skin", + Description = "The general look of the game is called a Theme. Most of it is written in Lua.\n\nTo install a theme, you just extract its folder into the Themes folder of your install. You shouldn't ever have to backup this folder.\n\nTo change your theme, you can find the option in SelectMusic settings, or in Display Options. It won't look the same as this theme though.", + Image = nil, + }, + { + Name = "Change Language", + ShortDescription = "We need translators", + Description = "Etterna comes with a partial translation into a few popular languages, but overall none of them are nearly as sufficient as we would like them to be.\n\nTranslation support is always on our mind, but not a very high priority.\n\nTo change language and see what things look like, find it in Display Options or in SelectMusic settings.", + Image = nil, + }, + { + Name = "Toggle Menu Sounds", + ShortDescription = "Beep", + Description = "Sometimes menu sounds are annoying.\n\nTo turn them off, press Pause on your keyboard, or find the option in the F3 menu F5 page or in SelectMusic settings.", + Image = nil, + }, + { + Name = "Toggle Pitch Rates", + ShortDescription = "Nightcore Remix", + Description = "Some players enjoy high pitch noises more than others, and some players are better when the song is just faster but not high pitched.\n\nTo toggle pitch usage on rates, find it in the F3 menu F8 page, Advanced Options, or SelectMusic settings.", + Image = nil, + }, + { + Name = "Swap Wheel Side", + ShortDescription = "Change is bad", + Description = "Understandably, some people are bound to not like the fact that the wheel defaults to the left side in Rebirth.\n\nTo swap sides, find the wheel position option in SelectMusic settings > Graphics > Theme Options.", + Image = nil, + }, + { + Name = "Login", + ShortDescription = "Using Online Functionality", + Description = "Login is required if you ever want to upload scores or view leaderboards.\n\nTo begin the login process, you click the broken link in the top left or press Ctrl + L while in SelectMusic. Doing the same again will log you out.\n\nLogging in this way is not required to play multiplayer. Instead, you are separately asked to log in to that, if it is required.", + Image = nil, + }, + { + Name = "Upload Scores", + ShortDescription = "Flex your 89% on Game Time", + Description = "You must first be logged in to upload any score.\n\nEvery time you log in (and if you automatically log in at the start of a session) Etterna will try to upload any scores you have set that have not yet been uploaded. Scores will also attempt to upload immediately after setting a score, even before your profile saves.\n\nOnly scores on ranked charts will be uploaded. You can see if a chart is ranked by checking its leaderboards in the Scores tab.\n\nTo try force uploading, find the upload buttons either in the Profile tab or the Scores tab.\n\nEtterna uploads things in the background, so there is no clear progress indicator. Just trust.\n\nScores worth 0.00 or that are otherwise invalid will not upload.", + Image = nil, + }, + { + Name = "Favorite Songs", + ShortDescription = "Ear worms", + Description = "Your song library might become huge and Favoriting is a way to keep track of the files you really like. Just press Ctrl + F on a chart and it becomes a Favorite. Do the same again to remove it.\n\nFavorites can be viewed as a playlist or in the Favorites sortmode if you press Up-Down-Up-Down.", + Image = nil, + }, + { + Name = "Permanently Mirror Charts", + ShortDescription = "Hand bias bad", + Description = "Some charts out there are very biased toward one hand, and some players have particularly bad hands. Because Etterna calculates based on physical difficulty and mirroring a chart is still equivalent because only the hands are swapped, mirroring is a core feature of the game and is ranked.\n\nSome charts might need to always be mirrored for some players, but the players might not want to leave mirror on for everything. Permamirror is the solution.\n\nTo permanently toggle mirror on a specific chart, hover it and press Ctrl + M. Every time you open it, mirror will be on.", + Image = nil, + }, + }, + ["Information"] = { + { + Name = "About Keymodes", + ShortDescription = "Styles, StyleTypes, StepsTypes, Games ...", + Description = "At its root, this game is a simulator for several other real games. This is why keymodes are separated by Game, and then further by Style.\n\nDance - The core game. 4k. Dance-double can be found here, and is 8k. Also contains a 3k type.\nSolo - Separated from the core game, 6k.\nBeat - The BMS game. 5k+1, 7k+1, and doubles for both.\nKb7 - A gamemode that didn't take off, 7k.\nPump - 5k, 6k, and 10k in single, halfdouble, and doubles.\nPopn - Yes, popn. 5k and 9k.\n\nWithin a game, each style is visible in the difficulty displays. You can switch game in SelectMusic settings or the Select Game screen in Options.", + Image = nil, + }, + { + Name = "Old Key Config", + ShortDescription = "You shouldn't need this", + Description = "Old key config can still be accessed through the main menu options. The left half of the screen is where the relevant controls are - it's the Player 1 side. The right half is for Player 2, but now those controls are dead. The default columns cannot be modified directly.\n\nTo unbind a default control, rebind that key to a different button on the right side that is infrequently used.\n\nIf you only use the key rebinding screen in Rebirth, you never have to use this screen.\n\nBe aware - just because this screen allows you to bind the same button to multiple keys to play with does not mean that setting scores using that kind of setup is allowed. The feature is meant more for people with a particular playstyle or restriction.", + Image = nil, + }, + { + Name = "Replays", + ShortDescription = "Stuck in the past", + Description = "Local and online replays can be viewed for scores where valid replay data is present.\n\nLocal replays can be viewed if the replay is present in Replays or ReplaysV2. You have to find the score in the scores tab, and then click the button to view the replay.\n\nOnline replays can be viewed straight from the chart leaderboards.\n\nWhile in a replay, you can pause and change rates or seek around. The large progress bar can be used as a seeking bar.\n\nEvaluation will show a reconstruction of the actual score's evaluation screen.", + Image = nil, + }, + { + Name = "SelectMusic Tips", + ShortDescription = "Speedy menu navigation", + Description = "SelectMusic contains a long list of hotkeys that make navigation quick. Memorizing many of them will be very helpful. Find the page on SelectMusic hotkeys to learn more.\n\nCertain parts of the UI have extra mouse functionality. Clicking the header of the wheel will randomly pick a group or a song in the current group. Clicking certain text in the Overall page of the profile tab toggles the displayed information. Clicking the banner of a song opens chart preview.\n\nThe music wheel scroll bar can be clicked or dragged to quickly navigate through songs. Right clicking scores in the profile tab invalidates them.\n\nEscape is a very useful button to exit the side menus that come up when using the buttons at the top of the screen.", + Image = nil, + }, + { + Name = "Profile Tab Usage", + ShortDescription = "Your Profile and You", + Description = "The Profile tab contains most relevant information about your game usage and scores. The Overall section gives a glance at online and offline ratings as well as general stats. Here, you can upload all scores (that haven't been uploaded) or revalidate all scores locally. You can also click the Player Ratings text to switch it to Player Stats for more information.\n\nIn the View Recent Scores button, or any of the other tabs, you can view several hundred of your scores organized by whichever condition the tab specializes in. Stream for example sorts everything by stream. Recent scores sorts them by date.", + Image = nil, + }, + { + Name = "Goals Tab Usage", + ShortDescription = "Strive to be better eventually", + Description = "The Goals tab contains a listing of goals you created on charts which you intend to eventually reach. That's the nature of a goal. By default, all goals start at priority 1, rate 1x, and 93%. These attributes can be clicked to increase or decrease them.\n\nTo create a goal, there's a button for it in the tab or you can use Ctrl + G.\n\nThe top buttons of the tab are for sorting the list of goals. The rightmost button filters the list by complete status.\n\nA vacuous goal simply means that the goal is pointless because you have already set a score that beats it. Remove it or make a higher goal.\n\nGoals save to your XML.", + Image = nil, + }, + { + Name = "Playlists Tab Usage", + ShortDescription = "'Courses'", + Description = "The Playlists tab lets you create multiple lists of charts to play or refer back to. These don't actually have to be used to play in succession like a course, but can serve to be lists of files.\n\nTo create a playlist, Ctrl + P can be used or you can use the New Playlist button. Then click a playlist name to enter it.\n\nTo add charts to the selected playlist, use Ctrl + A or click the Add Current Chart button. The rate a chart gets played on in a course playback can be changed if you click the rate in the detail list.\n\nA playlist cannot be reordered.\n\nPlaylists save to your XML.", + Image = nil, + }, + { + Name = "Tags Tab Usage", + ShortDescription = "Describe your charts", + Description = "The Tags tab lets you put description tags on charts so you can either describe them in your own way or filter based on the tags.\n\nAny number of tags can be assigned to a chart. Assigned tags (up to a certain amount) will show up in the General tab, and also as a different color in the Tags tab.\n\nThe Require Tag button sets a filter on all songs that requires the tag is assigned. The Hide Tag button does the opposite - any song with that tag is not visible. You must click Apply to set these filters.\n\nTo assign tags to a chart, just click the Assign button then click some tags.\n\nDeleting tags is a 2 step process started by clicking Delete.\n\nThe Reset button resets all filters. It does not delete any tags.\n\nTags are saved in a tags.lua file which can be shared with other people.", + Image = nil, + }, + { + Name = "Getting Support", + ShortDescription = "Not for spoonfeeding", + Description = "Sometimes getting Etterna set up or getting things fixed is not a simple process. Well ... it is to a lot of us, but not everyone.\n\nSince many people know the solution to a lot of common issues, if you ever need help, you are free to ask around in the Etterna communities for help. Github issues (can be found on the Title Screen) is a place to report bugs, not ask for help.\n\nDiscord servers that have answers to your questions can be found on the front page of the Github repo, or any website associated with this game.", + Image = nil, + }, + }, + ["Troubleshooting"] = { + { + Name = "Softlocked", + ShortDescription = "Locked in", + Description = "In some rare conditions, not just in Rebirth, the game can lock but not hard crash or freeze. You can tell that this is what is happening if some buttons might work, but you can't advance or move or go back.\n\nThe instant solution to a softlock is Ctrl + Operator. By default, Operator is set to Scroll Lock. It can be rebound in the old key config. Pressing this combo will send you to the options menu.\n\nF3 + F8 + 9 will also put you in old key config.\n\nIf you ever end up in a softlock condition, it is helpful to be able to consistently reproduce and then describe how you did it in detail in a Github issue so that we can patch it.", + Image = nil, + }, + { + Name = "All Input Broke", + ShortDescription = "The menu just laughs at you", + Description = "Rebirth has some input quirks which can one way or another cause your menu navigation to completely stop working or work incorrectly.\n\nTo solve this, you can first try to escape back to the main menu. If not possible or you are already there, try reloading scripts and then overlays - Ctrl + F2 and then Ctrl + Shift F2. Then reload the screen by pressing F3 + F6 + 2.\n\nIf you ever end up in this scenario, it is very helpful to report it.", + Image = nil, + }, + { + Name = "General Tips", + ShortDescription = "Blanket fixes", + Description = "Lots of random things happen in this game. Here's some things to try before crying about it.\n\nRebirth locks, input issues, and error spam can be temporarily solved usually by reloading scripts, overlays, and the current screen. Ctrl + F2 > Ctrl + Shift F2 > F3 + F6 + 2. When developing on this theme especially, you will want to know this combo.\n\nCtrl + Operator or F3 + F8 + 9 will get you out of any softlock.\n\nIf input or the graphics don't work for some reason, rebooting is usually a fix.\n\nConstant crashing on startup is usually caused by putting random garbage in pack folders or 0 BPM related issues.", + Image = nil, + }, + { + Name = "Song Doesn't Load", + ShortDescription = "Mysterious", + Description = "Sometimes when creating charts, authors find that the chart they are working on never appears. There's a reason for this (it's mostly a bug)\n\nIf you put music into a song folder without a .sm and restart or Ctrl + Q at any point, the folder is now forever ignored as not a song directory. The same applies if you put a .sm in that folder but it contains no charts.\n\nTo be safe, you should put audio with the .sm and a valid chart in before restarting or using Ctrl + Q. This will load the new song fine.\n\nIf this situation ever comes up, to solve it you must either delete the Cache folder or rename the song folder.", + Image = nil, + }, + { + Name = "Song Doesn't Update", + ShortDescription = "When you don't know features", + Description = "When editing charts, authors may run into an issue where the notedata they set is updated when they go to playtest, but the song ends early or late. Or none of the metadata updates to what they saved it as.\n\nThe reason for this is that the game has a cache which it typically blindly trusts. It loads new notedata from disk when going into gameplay, but will not modify chart properties based on that notedata.\n\nTo fix this, hover the song and press Ctrl + Shift + R. That will update the song from disk.", + Image = nil, + }, + { + Name = "Scores Don't Save", + ShortDescription = "Rare bugs or game misuse", + Description = "There are a couple of scenarios you may find yourself in if scores stop saving.\n\nThe usual one is that you've accidentally turned off Save Scores. This is an option that is found somewhere in the menus. It normally shouldn't be turned off.\n\nAnother is a rare bug called 0x where in the evaluation somehow your music rate is set to 0x, and now the likelihood that the rest of the session doesn't save is very high. To fix, just restart.\n\nThe last is a faulty install. You installed in a location where you don't have write permissions, such as Program Files. Or you ran out of disk space.", + Image = nil, + }, + { + Name = "Scores Worth 0.00", + ShortDescription = "Invalid scores", + Description = "Scores become worth 0 instantly when the score is invalid upon creation.\n\nConditions for invalid scores:\nNegative BPMs or Warps\nInvalid modifiers (it is obvious which ones are being used)\nFile has less than 200 notes\nFailed\nChord Cohesion On\nAutoplay or Practice\nCertain lua mods active", + Image = nil, + }, + { + Name = "Random Crashing", + ShortDescription = "That's life", + Description = "Sometimes the game crashes. Crashdumps should be generated in a folder within your Program folder.\n\nIf you opt in to crash uploading, you should just let us know in an issue or a report on Discord that you are having crash issues. Otherwise, still let us know but be ready to supply us with the associated .dmp file and logs.\n\nIf you keep crashing for no clear reason, it's always good to submit an issue or ask the community what is happening.", + Image = nil, + }, + }, + } + + -- generated table + -- basically the data representation of the scroller thing + -- categories are top level items + -- options are slightly shifted over + -- pagination and indexing is based on this table + local items = {} + for _, cat in ipairs(categoryDefs) do + items[#items+1] = { + isCategory = true, + Name = cat, + } + for __, optionDef in ipairs(optionDefs[cat]) do + items[#items+1] = { + isCategory = false, + Parent = cat, + Name = optionDef.Name, + Def = optionDef, + } + end + end + + local itemsVisible = 20 + local cursorIndex = 1 + local page = 1 + local maxPage = math.ceil(#items / itemsVisible) + local function getPageFromIndex(i) + return math.ceil((i) / itemsVisible) + end + local function cursorHoversItem(i) + if getPageFromIndex(cursorIndex) ~= page then return false end + return ((cursorIndex-1) % itemsVisible == (i-1)) + end + local function movePage(n) + local newpage = page + n + if newpage > maxPage then newpage = 1 end + if newpage < 1 then newpage = maxPage end + page = newpage + MESSAGEMAN:Broadcast("UpdatePage") + end + local function moveCursor(n) + local newpos = cursorIndex + n + if newpos > #items then newpos = 1 end + if newpos < 1 then newpos = #items end + cursorIndex = newpos + local newpage = getPageFromIndex(cursorIndex) + if newpage ~= page then + page = newpage + MESSAGEMAN:Broadcast("UpdatePage") + end + MESSAGEMAN:Broadcast("UpdateCursor") + end + + local function menuItem(i) + local yIncrement = (actuals.MainDisplayHeight) / itemsVisible + local index = i + local item = items[index] + return Def.ActorFrame { + Name = "MenuItem_"..i, + InitCommand = function(self) + self:x(actuals.ScrollerWidth + actuals.EdgeBuffer/2) + -- center y + self:y(yIncrement * (i-1) + yIncrement / 2) + self:playcommand("UpdateItem") + end, + SelectCurrentCommand = function(self) + if cursorHoversItem(i) then + -- do something + MESSAGEMAN:Broadcast("SelectedItem", {def = item.Def, category = item.Parent}) + end + end, + UpdateItemCommand = function(self) + index = (page-1) * itemsVisible + i + item = items[index] + if item ~= nil then + self:finishtweening() + self:diffusealpha(0) + self:smooth(animationSeconds) + self:diffusealpha(1) + else + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + end + end, + UpdatePageMessageCommand = function(self) + self:playcommand("UpdateItem") + end, + + UIElements.QuadButton(1, 1) .. { + Name = "ItemBG", -- also the "cursor" position + InitCommand = function(self) + self:halign(0) + -- 97% full size to allow a gap for mouse hover logic reasons + self:zoomto(actuals.ListWidth - actuals.EdgeBuffer, yIncrement * 0.97) + self:diffusealpha(0) + self.alphaDeterminingFunction = function(self) + local alpha = 1 + if isOver(self) then + alpha = buttonHoverAlpha + if cursorHoversItem(i) then + alpha = (buttonHoverAlpha + 1) / 2 + end + else + alpha = 0 + if cursorHoversItem(i) then + alpha = cursorAlpha + end + end + + self:diffusealpha(alpha) + end + end, + CursorShowCommand = function(self) + self:smooth(cursorAnimationSeconds) + self:alphaDeterminingFunction() + end, + CursorHideCommand = function(self) + self:smooth(cursorAnimationSeconds) + self:alphaDeterminingFunction() + end, + MouseOverCommand = function(self) + if self:GetParent():IsInvisible() then return end + self:alphaDeterminingFunction() + end, + MouseOutCommand = function(self) + if self:GetParent():IsInvisible() then return end + self:alphaDeterminingFunction() + end, + UpdateCursorMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + UpdateItemCommand = function(self) + self:alphaDeterminingFunction() + end, + MouseDownCommand = function(self) + if self:GetParent():IsInvisible() then return end + cursorIndex = index + self:GetParent():playcommand("SelectCurrent") + end, + SelectedItemMessageCommand = function(self) + if self:GetParent():IsInvisible() then return end + self:alphaDeterminingFunction() + end, + }, + LoadFont("Menu Normal") .. { + Name = "Text", + InitCommand = function(self) + self:halign(0) + self:zoom(listTextSize) + self:maxwidth((actuals.ListWidth - actuals.EdgeBuffer * 2) / listTextSize) + end, + UpdateItemCommand = function(self) + if item ~= nil then + if not item.isCategory then + self:x(actuals.EdgeBuffer) + else + self:x(0) + end + self:settext(item.Name) + end + end, + } + } + end + + local rightAreaWidth = actuals.MainDisplayWidth - (actuals.ScrollerWidth + actuals.ListWidth + actuals.SeparationGapWidth) + local t = Def.ActorFrame { + Name = "MenuContainer", + BeginCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type == "InputEventType_Release" then return end + + local gameButton = event.button + local key = event.DeviceInput.button + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local enter = gameButton == "Start" + local back = key == "DeviceButton_escape" + + if up or left then + moveCursor(-1) + self:playcommand("SelectCurrent") + elseif down or right then + moveCursor(1) + self:playcommand("SelectCurrent") + elseif enter then + self:playcommand("SelectCurrent") + elseif back then + SCREENMAN:GetTopScreen():Cancel() + end + end) + self:playcommand("UpdateCursor") + end, + Def.Quad { + Name = "ScrollBar", + InitCommand = function(self) + self:zoomto(actuals.ScrollerWidth, actuals.MainDisplayHeight / maxPage) + self:halign(0):valign(0) + self:diffusealpha(0.6) + end, + UpdatePageMessageCommand = function(self) + self:finishtweening() + self:smooth(animationSeconds) + self:y(actuals.MainDisplayHeight / maxPage * (page-1)) + end, + }, + Def.Quad { + Name = "MouseScrollArea", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.ScrollerWidth + actuals.ListWidth, actuals.MainDisplayHeight) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end, + }, + Def.ActorFrame { + Name = "SelectedItemContainer", + InitCommand = function(self) + self:x(actuals.ScrollerWidth + actuals.ListWidth + actuals.SeparationGapWidth) + -- make empty defaults load + self:playcommand("UpdateSelectedItem") + end, + SelectedItemMessageCommand = function(self, params) + self:playcommand("UpdateSelectedItem", params) + end, + + LoadFont("Menu Normal") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.EdgeBuffer, actuals.TopBuffer) + self:zoom(titleTextSize) + end, + UpdateSelectedItemCommand = function(self, params) + if params and params.def ~= nil then + local def = params.def + self:settext(def.Name) + + if def.Image ~= nil and def.Image ~= "" then + self:maxwidth(((rightAreaWidth / 2) - actuals.EdgeBuffer) / titleTextSize) + else + self:maxwidth((rightAreaWidth - actuals.EdgeBuffer) / titleTextSize) + end + else + self:settext("") + end + end, + }, + LoadFont("Menu Normal") .. { + Name = "ShortDescription", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.EdgeBuffer, actuals.TopBuffer2) + self:skewx(-0.15) + self:zoom(subtitleTextSize) + self:maxheight((actuals.TopBuffer3 - actuals.TopBuffer2) / subtitleTextSize - textZoomFudge * 5) + end, + UpdateSelectedItemCommand = function(self, params) + if params and params.def ~= nil then + local def = params.def + self:settext(def.ShortDescription) + + if def.Image ~= nil and def.Image ~= "" then + self:wrapwidthpixels(((rightAreaWidth / 2) - actuals.EdgeBuffer) / subtitleTextSize) + else + self:wrapwidthpixels((rightAreaWidth - actuals.EdgeBuffer * 2) / subtitleTextSize) + end + else + self:settext("") + end + end, + }, + LoadFont("Menu Normal") .. { + Name = "Paragraph", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.EdgeBuffer, actuals.TopBuffer3) + end, + UpdateSelectedItemCommand = function(self, params) + if params and params.def ~= nil then + local def = params.def + self:settext(def.Description) + self:zoom(descTextSize) + self:maxheight((actuals.MainDisplayHeight - actuals.TopBuffer3 - actuals.EdgeBuffer) / descTextSize) + + if def.Image ~= nil and def.Image ~= "" then + self:wrapwidthpixels(((rightAreaWidth / 2) - actuals.EdgeBuffer) / descTextSize) + else + self:wrapwidthpixels((rightAreaWidth - actuals.EdgeBuffer * 2) / descTextSize) + end + else + self:zoom(subtitleTextSize) + self:settext("Select an item on the left to get info.\nScroll through the list for more categories.") + self:wrapwidthpixels((rightAreaWidth - actuals.EdgeBuffer) / subtitleTextSize) + end + end, + }, + UIElements.SpriteButton(1, 1, nil) .. { + Name = "Image", + InitCommand = function(self) + self:valign(0) + self:xy(rightAreaWidth / 4 * 3, actuals.TopBuffer) + end, + UpdateSelectedItemCommand = function(self, params) + if params and params.def ~= nil then + local def = params.def + if def.Image ~= nil and def.Image ~= "" then + self:diffusealpha(1) + self:Load(def.Image) + local h = self:GetHeight() + local w = self:GetWidth() + local allowedHeight = actuals.MainDisplayHeight - (actuals.TopBuffer * 2) + local allowedWidth = rightAreaWidth - (actuals.EdgeBuffer + actuals.IconExitWidth) + if h >= allowedHeight and w >= allowedWidth then + if h * (allowedWidth / allowedHeight) >= w then + self:zoom(allowedHeight / h) + else + self:zoom(allowedWidth / w) + end + elseif h >= allowedHeight then + self:zoom(allowedHeight / h) + elseif w >= allowedWidth then + self:zoom(allowedWidth / w) + else + self:zoom(1) + end + else + self:diffusealpha(0) + end + else + self:diffusealpha(0) + end + end, + }, + } + } + + for i = 1, itemsVisible do + t[#t+1] = menuItem(i) + end + return t +end + + +local t = Def.ActorFrame { + Name = "HelpDisplayFile", + + Def.ActorFrame { + Name = "InfoBoxFrame", + InitCommand = function(self) + self:xy(actuals.InfoLeftGap, actuals.InfoTopGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.InfoWidth, actuals.InfoHeight) + self:diffuse(color("0,0,0")) + self:diffusealpha(0.6) + end, + }, + Def.Sprite { + Name = "Logo", + Texture = THEME:GetPathG("", "Logo"), + InitCommand = function(self) + self:xy(actuals.ScrollerWidth + (actuals.ListWidth / 2), actuals.InfoHeight / 2) + self:zoomto(logoW, logoH) + end + }, + LoadColorFont("Menu Bold") .. { + Name = "Text", + InitCommand = function(self) + local textw = actuals.InfoWidth - (actuals.ScrollerWidth + actuals.ListWidth + actuals.SeparationGapWidth) + local textx = actuals.InfoWidth - textw / 2 + self:xy(textx, actuals.InfoHeight/2) + self:zoom(infoTextSize) + self:maxheight((actuals.InfoHeight - (actuals.InfoVerticalBuffer*2)) / infoTextSize) + self:wrapwidthpixels(textw / infoTextSize) + self:settext("Help") + end, + SelectedItemMessageCommand = function(self, params) + if params and params.category ~= nil then + self:settext(params.category) + else + self:settext("Help") + end + end + }, + }, + Def.ActorFrame { + Name = "MainDisplayFrame", + InitCommand = function(self) + self:xy(actuals.MainDisplayLeftGap, actuals.MainDisplayTopGap) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.MainDisplayWidth, actuals.MainDisplayHeight) + self:diffuse(color("0,0,0")) + self:diffusealpha(0.6) + end, + }, + Def.Quad { + Name = "Separator", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(actuals.ScrollerWidth + actuals.ListWidth) + self:zoomto(actuals.SeparationGapWidth, actuals.MainDisplayHeight) + self:diffuse(color("1,1,1")) + self:diffusealpha(0.2) + end, + }, + helpMenu() .. { + InitCommand = function(self) + self:xy(0, 0) + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "exit")) .. { + Name = "Exit", + InitCommand = function(self) + self:valign(0):halign(1) + self:xy(actuals.MainDisplayWidth - actuals.InfoVerticalBuffer/4, actuals.InfoVerticalBuffer/4) + self:zoomto(actuals.IconExitWidth, actuals.IconExitHeight) + end, + MouseDownCommand = function(self, params) + SCREENMAN:GetTopScreen():Cancel() + TOOLTIP:Hide() + end, + MouseOverCommand = function(self, params) + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Exit") + TOOLTIP:Show() + end, + MouseOutCommand = function(self, params) + self:diffusealpha(1) + TOOLTIP:Hide() + end, + }, + }, + +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenHelpMenu underlay.lua b/Themes/Rebirth/BGAnimations/ScreenHelpMenu underlay.lua new file mode 100644 index 0000000000..43330b4766 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenHelpMenu underlay.lua @@ -0,0 +1,5 @@ +local t = Def.ActorFrame {Name = "UnderlayFile"} + +t[#t+1] = LoadActor(THEME:GetPathG("Title", "BG")) + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenPrompt underlay.lua b/Themes/Rebirth/BGAnimations/ScreenPrompt underlay.lua new file mode 100644 index 0000000000..8ef4f96746 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenPrompt underlay.lua @@ -0,0 +1,7 @@ +-- this is the layer that sits behind the prompt that shows up in the key config screen + +return Def.Quad { + InitCommand = function(self) + self:FullScreen():Center():diffuse(color("#000000")) + end +} diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/curSongBox.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/curSongBox.lua new file mode 100644 index 0000000000..86ab7923ba --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/curSongBox.lua @@ -0,0 +1,393 @@ +local displayScore +local lastHovered +local focused = true +local t = Def.ActorFrame { + Name = "CurSongBoxFile", + WheelSettledMessageCommand = function(self, params) + -- update displayscore + -- it sets to nil properly by itself + displayScore = GetDisplayScore() + + lastHovered = params.hovered + + -- cascade visual update to everything + self:playcommand("Set", {song = params.song, group = params.group, hovered = params.hovered, steps = params.steps}) + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong(), hovered = lastHovered, steps = GAMESTATE:GetCurrentSteps()}) + end, + ChangedStepsMessageCommand = function(self, params) + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong(), hovered = lastHovered, steps = params.steps}) + end, + GeneralTabSetMessageCommand = function(self) + focused = true + end, + PlayerInfoFrameTabSetMessageCommand = function(self) + focused = false + end +} + +local ratios = { + LeftGap = 1140 / 1920, -- distance from left side of screen to left side of frame + TopGap = 109 / 1080, -- distance from top of screen to top of frame + Height = 359 / 1080, + Width = Var("widthRatio"), -- width taken from loading file default.lua + BannerHeight = 243 / 1080, + LowerLipHeight = 34 / 1080, + LeftTextLeftGap = 10 / 1920, + TextLowerGap1 = 8 / 1080, -- lowest text line, bottom frame to bottom letters + TextLowerGap2 = 49 / 1080, -- these gaps are from bottom frame to bottom text + TextLowerGap3 = 87 / 1080, + ApproximateTextVerticalHeight = 25 / 1080, -- exactly what it says, this determines the max allowed height for text. + + RateTextLeftGap = 330 / 1920, + BPMTextLeftGap = 210 / 1920, + BPMNumberLeftGap = 265 / 1920, -- from right edge to right edge of numbers + BPMWidth = 50 / 1920, -- from right edge of bpm number to right edge of bpm text + LengthTextLeftGap = 10 / 1920, + LengthNumberLeftGap = 110 / 1920, -- from right edge to right edge of numbers + LengthWidth = 62 / 1920, -- from right edge of len number to right edge of len text + + -- deprecated values but still used for initialization at the very least + -- these numbers are used for max width of the text + -- after initialization, it is dependent on the actual size of the diff frame, so these numbers are not used + -- DiffFrameRightGap IS USED for positioning the diff frame itself, do not remove + DiffFrameLeftGap = 407 / 1920, -- left edge of frame to left edge of leftmost item (?) + DiffFrameRightGap = 22 / 1920, -- from right edge of frame to right edge of rightmost item +} + +local actuals = { + LeftGap = ratios.LeftGap * SCREEN_WIDTH, + TopGap = ratios.TopGap * SCREEN_HEIGHT, + Height = ratios.Height * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + BannerHeight = ratios.BannerHeight * SCREEN_HEIGHT, + LowerLipHeight = ratios.LowerLipHeight * SCREEN_HEIGHT, + LeftTextLeftGap = ratios.LeftTextLeftGap * SCREEN_WIDTH, + TextLowerGap1 = ratios.TextLowerGap1 * SCREEN_HEIGHT, + TextLowerGap2 = ratios.TextLowerGap2 * SCREEN_HEIGHT, + TextLowerGap3 = ratios.TextLowerGap3 * SCREEN_HEIGHT, + ApproximateTextVerticalHeight = ratios.ApproximateTextVerticalHeight * SCREEN_HEIGHT, + RateTextLeftGap = ratios.RateTextLeftGap * SCREEN_WIDTH, + BPMTextLeftGap = ratios.BPMTextLeftGap * SCREEN_WIDTH, + BPMNumberLeftGap = ratios.BPMNumberLeftGap * SCREEN_WIDTH, + BPMWidth = ratios.BPMWidth * SCREEN_WIDTH, + LengthTextLeftGap = ratios.LengthTextLeftGap * SCREEN_WIDTH, + LengthNumberLeftGap = ratios.LengthNumberLeftGap * SCREEN_WIDTH, + LengthWidth = ratios.LengthWidth * SCREEN_WIDTH, + + DiffFrameLeftGap = ratios.DiffFrameLeftGap * SCREEN_WIDTH, + DiffFrameRightGap = ratios.DiffFrameRightGap * SCREEN_WIDTH, +} + +local textsize = 0.8 +local textzoomFudge = 5 + +local buttonHoverAlpha = 0.8 + +t[#t+1] = Def.ActorFrame { + Name = "Frame", + InitCommand = function(self) + self:xy(actuals.LeftGap, actuals.TopGap) + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + CONTEXTMAN:RegisterToContextSet(snm, "Main1", anm) + + -- the math with the logic inline will make increments be 0.1x + -- holding Select will do 0.05x increments + local selectPressed = false + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- require context is set and the general box is set to anything but the Scores tab + if not CONTEXTMAN:CheckContextSet(snm, "Main1") or SCUFF.generaltab == SCUFF.scoretabindex then + selectPressed = false + return + end + if event.type == "InputEventType_FirstPress" then + if event.button == "EffectUp" then + changeMusicRate(0.05 * (selectPressed and 1 or 2)) + elseif event.button == "EffectDown" then + changeMusicRate(-0.05 * (selectPressed and 1 or 2)) + elseif event.button == "Select" then + selectPressed = true + end + elseif event.type == "InputEventType_Release" then + if event.button == "Select" then + selectPressed = false + end + end + end) + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.83) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end + }, + --[[ + Def.Quad { + Name = "LowerLip", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(actuals.Height) + self:zoomto(actuals.Width, actuals.LowerLipHeight) + self:diffuse(color("#111111")) + self:diffusealpha(0.6) + end + }, + ]] + UIElements.SpriteButton(1, 1) .. { + Name = "Banner", + InitCommand = function(self) + self:halign(0):valign(0) + self:scaletoclipped(actuals.Width, actuals.BannerHeight) + self:SetDecodeMovie(useVideoBanners()) + end, + SetCommand = function(self, params) + self:finishtweening() + self:smooth(0.05) + self:diffusealpha(1) + if params.song then + local bnpath = params.song:GetBannerPath() + if not bnpath then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + self:LoadBackground(bnpath) + else + local bnpath = WHEELDATA:GetFolderBanner(params.hovered) + if not bnpath or bnpath == "" then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + self:LoadBackground(bnpath) + end + -- handles group banners or missing backgrounds + -- logic in the bg handles whether or not we successfully loaded a banner here + if params.song == nil or params.song:GetBackgroundPath() == nil then + MESSAGEMAN:Broadcast("SetAverageColor", {actor=self}) + end + end, + MouseDownCommand = function(self, params) + -- clicking the banner will toggle chart preview + -- tree: + -- self - frame - cursongbox.lua - rightframe + -- rightframe owns generalbox - owns general owns chart preview + -- this should work based on the actor tree that exists + -- if it fails, probably nothing was there to receive the message or the tree is bad + if SCUFF.generaltab == SCUFF.generaltabindex and focused and params.event == "DeviceButton_left mouse button" then + SCUFF.preview.active = not SCUFF.preview.active + self:GetParent():GetParent():GetParent():playcommand("ToggleChartPreview") + elseif params.event == "DeviceButton_right mouse button" then + local top = SCREENMAN:GetTopScreen() + if top.PauseSampleMusic then + top:PauseSampleMusic() + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + -- hover state only when button would work + if SCUFF.generaltab ~= SCUFF.generaltabindex then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + -- unhover state + self:diffusealpha(1) + end, + GeneralTabSetMessageCommand = function(self, params) + if self:IsInvisible() then return end + -- prevent "stuck" hovered state + if SCUFF.generaltab ~= SCUFF.generaltabindex or params ~= nil and params.tab ~= SCUFF.generaltabindex then + self:diffusealpha(1) + else + -- hover if already hovered + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + end + end + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Video Banners" then + self:SetDecodeMovie(useVideoBanners()) + end + end, + }, + LoadFont("Common Normal") .. { + Name = "TitleAuthor", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.LeftTextLeftGap, actuals.Height - actuals.TextLowerGap3) + self:zoom(textsize) + self:maxwidth((actuals.DiffFrameLeftGap - actuals.LeftTextLeftGap) / textsize - textzoomFudge) + self:maxheight(actuals.ApproximateTextVerticalHeight / textsize) + self:settext("Song Title - Song Author") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.song then + local title = params.song:GetDisplayMainTitle() + local artist = params.song:GetDisplayArtist() + self:settextf("%s - %s", title, artist) + else + self:settext("") + end + end, + DisplayLanguageChangedMessageCommand = function(self) + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong()}) + end + }, + LoadFont("Common Normal") .. { + Name = "SubTitle", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.LeftTextLeftGap, actuals.Height - actuals.TextLowerGap2) + self:zoom(textsize) + self:maxwidth((actuals.DiffFrameLeftGap - actuals.LeftTextLeftGap) / textsize - textzoomFudge) + self:maxheight(actuals.ApproximateTextVerticalHeight / textsize) + self:settext("Song SubTitle (1995)") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.song then + self:settext(params.song:GetDisplaySubTitle()) + else + self:settext("") + end + end, + DisplayLanguageChangedMessageCommand = function(self) + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong()}) + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + self:xy(actuals.RateTextLeftGap, actuals.Height - actuals.TextLowerGap1) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + txt:halign(0):valign(1) + txt:zoom(textsize) + txt:maxwidth((actuals.Width - actuals.RateTextLeftGap) / textsize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:halign(0):valign(1) + bg:zoomy(actuals.LowerLipHeight) + bg:y(actuals.TextLowerGap1) + end, + SetCommand = function(self, params) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local str = string.format("%.2f", getCurRateValue()) .. "x" + txt:settext(str) + bg:zoomx(txt:GetZoomedWidth()) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + changeMusicRate(0.05) + elseif params.event == "DeviceButton_right mouse button" then + changeMusicRate(-0.05) + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == 'in' then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + MouseScrollMessageCommand = function(self, params) + if self:IsInvisible() then return end + if isOver(self:GetChild("BG")) then + if params.direction == "Up" then + changeMusicRate(0.05) + elseif params.direction == "Down" then + changeMusicRate(-0.05) + end + end + end + }, + + LoadFont("Common Normal") .. { + Name = "LengthText", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.LengthTextLeftGap, actuals.Height - actuals.TextLowerGap1) + self:zoom(textsize) + self:maxwidth((actuals.LengthNumberLeftGap - actuals.LeftTextLeftGap) / textsize - textzoomFudge) + self:settext("LENGTH") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + LoadFont("Common Normal") .. { + Name = "LengthNumbers", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.LengthNumberLeftGap, actuals.Height - actuals.TextLowerGap1) + self:zoom(textsize) + self:maxwidth((actuals.BPMTextLeftGap - actuals.LengthNumberLeftGap) / textsize - textzoomFudge) + self:settext("55:55") + end, + SetCommand = function(self, params) + if params.steps then + local len = GetPlayableTime() + self:settext(SecondsToMMSS(len)) + self:diffuse(colorByMusicLength(len)) + else + self:settext("--:--") + self:diffuse(color("1,1,1,1")) + end + end + }, + + LoadFont("Common Normal") .. { + Name = "BPMText", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.BPMTextLeftGap, actuals.Height - actuals.TextLowerGap1) + self:zoom(textsize) + self:maxwidth((actuals.BPMNumberLeftGap - actuals.BPMTextLeftGap) / textsize - textzoomFudge) + self:settext("BPM") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.BPMDisplay { + File = THEME:GetPathF("Common", "Normal"), + Name = "BPMDisplay", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.BPMNumberLeftGap, actuals.Height - actuals.TextLowerGap1) + self:zoom(textsize) + self:maxwidth(actuals.BPMWidth / textsize - textzoomFudge) + end, + SetCommand = function(self, params) + -- it appears that SetFromSteps is broken... + -- note to self. + -- wow i forgot about this. time to forget about it again -11 months later + if params.steps then + self:visible(true) + self:SetFromSong(params.song) + else + self:visible(false) + end + end + }, + LoadActorWithParams("stepsdisplay", {ratios = ratios, actuals = actuals}) + +} + + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/default.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/default.lua new file mode 100644 index 0000000000..7128b7a0b2 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/default.lua @@ -0,0 +1,69 @@ +local t = Def.ActorFrame {} + +-- tracking the right frame - the box containing song info and the general tabs +-- this is not the right frame which pops up from off screen, that is the PlayerInfoFrame +local rightFrameVisible = true +local visibleX = 0 +local hiddenX = SCREEN_WIDTH +local widthRatio = 780 / 1920 +local widthActual = widthRatio * SCREEN_WIDTH + +t[#t+1] = LoadActor("wheel") + +t[#t+1] = Def.ActorFrame { + Name = "RightFrame", + InitCommand = function(self) + self:playcommand("SetThePositionForThisFrameNothingElse") + end, + SetThePositionForThisFrameNothingElseCommand = function(self) + if getWheelPosition() then + visibleX = 0 + hiddenX = widthActual + else + visibleX = -SCREEN_WIDTH + widthActual + hiddenX = -SCREEN_WIDTH - widthActual + end + if rightFrameVisible then + self:x(visibleX) + else + self:x(hiddenX) + end + end, + GeneralTabSetMessageCommand = function(self, params) + if params ~= nil and params.tab ~= nil then + SCUFF.generaltab = params.tab + end + if not rightFrameVisible then + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Main1") + self:finishtweening() + self:smooth(0.1) + self:x(visibleX) + rightFrameVisible = true + end + TOOLTIP:Hide() + end, + PlayerInfoFrameTabSetMessageCommand = function(self) + rightFrameVisible = false + self:finishtweening() + self:smooth(0.1) + self:x(hiddenX) + TOOLTIP:Hide() + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetThePositionForThisFrameNothingElse") + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Music Wheel Position" then + self:GetParent():playcommand("UpdateWheelPosition") + end + end, + + LoadActorWithParams("curSongBox", { + widthRatio = widthRatio, + }), + LoadActorWithParams("generalBox", { + widthRatio = widthRatio, + }), +} + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalBox.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalBox.lua new file mode 100644 index 0000000000..13112d85c8 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalBox.lua @@ -0,0 +1,202 @@ +local t = Def.ActorFrame { + Name = "GeneralBoxFile", + LoginMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + LogOutMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + LoginFailedMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end, + OnlineUpdateMessageCommand = function(self) + self:playcommand("UpdateLoginStatus") + end +} + +local ratios = { + LeftGap = 1140 / 1920, -- left side of screen to left edge of frame + TopGap = 468 / 1080, -- top of screen to top of frame + Width = Var("widthRatio"), -- width of the box taken from the loading file default.lua + Height = 612 / 1080, + LowerLipHeight = 57 / 1080, +} + +local actuals = { + LeftGap = ratios.LeftGap * SCREEN_WIDTH, + TopGap = ratios.TopGap * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + LowerLipHeight = ratios.LowerLipHeight * SCREEN_HEIGHT +} + +-- the page names in the order they go +local choiceNames = { + "General", + "Scores", + "Profile", + "Goals", + "Playlists", + "Tags", +} +SCUFF.generaltabcount = #choiceNames +SCUFF.generaltab = 1 -- reset generaltab page + +local choiceTextSize = 0.8 +local buttonHoverAlpha = 0.6 +local textzoomFudge = 5 + +-- controls the focus of the frame +-- starts true because it starts visible on the screen +-- goes false when forced away, such as when pressing search +local focused = true + +local function createChoices() + local selectedIndex = 1 + + local function createChoice(i) + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ButtonTab_"..choiceNames[i], + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceNames) * (i-1) + (actuals.Width / #choiceNames / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceNames / choiceTextSize - textzoomFudge) + txt:settext(choiceNames[i]) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceNames, actuals.LowerLipHeight) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateSelectedIndex") + end, + UpdateSelectedIndexCommand = function(self) + local txt = self:GetChild("Text") + if selectedIndex == i then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.7)) + else + txt:strokecolor(color("0,0,0,0")) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + selectedIndex = i + MESSAGEMAN:Broadcast("GeneralTabSet", {tab = i}) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.Height - actuals.LowerLipHeight / 2) + self:z(201) + self:playcommand("UpdateSelectedIndex") + self:draworder(3) + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + -- this keeps track of whether or not the user is allowed to use the keyboard to change tabs + CONTEXTMAN:RegisterToContextSet(snm, "Main1", anm) + + -- enable the possibility to press the keyboard to switch tabs + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if locked out, dont allow + if not CONTEXTMAN:CheckContextSet(snm, "Main1") then return end + if event.type == "InputEventType_FirstPress" then + -- must be a number and control not held down + if event.char and tonumber(event.char) and not INPUTFILTER:IsControlPressed() then + local n = tonumber(event.char) + if n == 0 then n = 10 end + -- n must be a valid option or we must not have focus on the general box (not in search for example) + if n >= 1 and n <= #choiceNames or not focused then + selectedIndex = n + MESSAGEMAN:Broadcast("GeneralTabSet", {tab = n}) + end + elseif event.DeviceInput.button == "DeviceButton_space" and focused and SCUFF.generaltab == SCUFF.generaltabindex then + -- toggle chart preview if the general tab is the current tab visible + SCUFF.preview.active = not SCUFF.preview.active + -- this should propagate off to the right places + self:GetParent():playcommand("ToggleChartPreview") + end + end + end) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab <= SCUFF.generaltabcount then + selectedIndex = params.tab + self:GetParent():hurrytweening(0.5):playcommand("UpdateSelectedIndex") + end + end, + + } + for i = 1, #choiceNames do + t[#t+1] = createChoice(i) + end + return t +end + +t[#t+1] = Def.ActorFrame { + Name = "Container", + InitCommand = function(self) + self:xy(actuals.LeftGap, actuals.TopGap) + end, + GeneralTabSetMessageCommand = function(self) + focused = true + end, + PlayerInfoFrameTabSetMessageCommand = function(self) + focused = false + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end + }, + Def.Quad { + Name = "Lip", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(actuals.Height) + self:zoomto(actuals.Width, actuals.LowerLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + self:draworder(3) + end + }, + createChoices(), + LoadActorWithParams("generalPages/general.lua", {ratios = ratios, actuals = actuals}) .. { + BeginCommand = function(self) + -- this will cause the general tab to become visible first on screen startup + self:playcommand("GeneralTabSet", {tab = SCUFF.generaltabindex}) + -- skip animation + self:finishtweening() + end + }, + LoadActorWithParams("generalPages/scores.lua", {ratios = ratios, actuals = actuals}), + LoadActorWithParams("generalPages/profile.lua", {ratios = ratios, actuals = actuals}), + LoadActorWithParams("generalPages/goals.lua", {ratios = ratios, actuals = actuals}), + LoadActorWithParams("generalPages/playlists.lua", {ratios = ratios, actuals = actuals}), + LoadActorWithParams("generalPages/tags.lua", {ratios = ratios, actuals = actuals}), +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/_chartPreview.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/_chartPreview.lua new file mode 100644 index 0000000000..88c2a746ec --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/_chartPreview.lua @@ -0,0 +1,246 @@ +local lastusedsong = nil +local t = Def.ActorFrame { + Name = "ChartPreviewFile", + InitCommand = function(self) + -- hide chart preview to start + SCUFF.preview.active = false + self:diffusealpha(0) + end, + WheelSettledMessageCommand = function(self, params) + -- should trigger if wheel stops moving + self:playcommand("LoadNoteData", {song = params.song, steps = params.steps}) + lastusedsong = params.song + + SCUFF.preview.resetmusic = false + if lastusedsong ~= nil and SCUFF.preview.active then + local top = SCREENMAN:GetTopScreen() + if top.PlayCurrentSongSampleMusic then + -- reset music, force start, force full length + SCUFF.preview.resetmusic = true + SOUND:StopMusic() + top:PlayCurrentSongSampleMusic(true, true) + end + end + end, + ChangedStepsMessageCommand = function(self, params) + -- should trigger only if switching steps, not when switching songs + self:playcommand("LoadNoteData", {song = GAMESTATE:GetCurrentSong(), steps = params.steps}) + lastusedsong = GAMESTATE:GetCurrentSong() + end, + ToggleChartPreviewCommand = function(self, params) + if params ~= nil and params.active ~= nil then + SCUFF.preview.active = params.active + end + + if SCUFF.preview.active then + -- chart preview turning on + if not SCUFF.preview.resetmusic and lastusedsong ~= nil then + local top = SCREENMAN:GetTopScreen() + if top.PlayCurrentSongSampleMusic then + -- reset music, force start, force full length + SCUFF.preview.resetmusic = true + SOUND:StopMusic() + top:PlayCurrentSongSampleMusic(true, true) + end + end + self:diffusealpha(1) + else + -- chart preview turning off + self:diffusealpha(0) + -- hide in case you are hovering the graph + TOOLTIP:Hide() + end + end +} + +local ratios = { + DensityGraphHeight = 53 / 555, + NoteFieldHeight = 502 / 555, +} + +local actuals = { + -- some actuals left out here and calculated below instead + +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +-- expected actual values may not exist for whatever reason, so default to 0 instead of nil error +actuals.LowerLipHeight = actuals.LowerLipHeight or 0 +actuals.Height = actuals.Height or 0 +-- the ratio is the percentage of the area we will use, so multiply it by the raw (actual) given area +-- when placing, take into account each other (notefield is placed below the density graph) +actuals.DensityGraphHeight = ratios.DensityGraphHeight * (actuals.Height - actuals.LowerLipHeight) +actuals.NoteFieldHeight = ratios.NoteFieldHeight * (actuals.Height - actuals.LowerLipHeight) + + +-- relative to the leftmost part of the general box, this is the horizontal center of the notefield +actuals.VerticalDividerLeftGap = actuals.VerticalDividerLeftGap or 0 +actuals.DividerThickness = actuals.DividerThickness or 0 +local rightHalfXBegin = actuals.VerticalDividerLeftGap + actuals.DividerThickness +local notefieldXCenter = rightHalfXBegin + (actuals.Width - rightHalfXBegin) / 2 +local notefieldYCenter = actuals.DensityGraphHeight + actuals.NoteFieldHeight / 2 +local expectedGeneralReceptorHeight = 64 -- this number varies slightly but typically receptors are "64x64" +local aspectRatioProportion = (16/9) / (SCREEN_WIDTH / SCREEN_HEIGHT) -- this was designed for 16:9 so compensate +local notefieldZoomBaseline = 0.8 -- zoom for 4key width +local notefieldWidthBaseline = 256 -- 4key width +local notefieldYOffset = actuals.DensityGraphHeight + expectedGeneralReceptorHeight / 1080 * SCREEN_HEIGHT * notefieldZoomBaseline +local notefieldReverseAdd = actuals.NoteFieldHeight - notefieldYOffset +local notefieldLengthPixels = 300 -- this isnt a perfect number but it fits for our use and i dont know how to calculate it +local notefieldAllowBeyondReceptorPixels = 0 -- this shouldnt be changed +local notefieldYReversePixelsBase = 288 -- this is what it is in gameplay, but it needs to change if we mess with mini/zoom + +local function getSizeForStyle() + local style = GAMESTATE:GetCurrentStyle() + if style == nil then return notefieldZoomBaseline, notefieldLengthPixels / notefieldZoomBaseline end + + local stylewidth = style:GetWidth() + -- the assumption is that a width of notefieldWidthBaseline uses a zoom of notefieldZoomBaseline + -- and notefieldLengthPixels is 300 for that baseline zoom + -- find a zoom and pixel length that fits using math + local pdiff = stylewidth / notefieldWidthBaseline + local newzoom = notefieldZoomBaseline / pdiff / aspectRatioProportion + local newlength = notefieldLengthPixels / newzoom + -- taking new calculated reverse pixel offset and making it smaller by a bit + local newreverse = notefieldYReversePixelsBase / newzoom + local newreversediff = notefieldYReversePixelsBase - (notefieldYReversePixelsBase - newreverse) / 3 + + return newzoom, newlength, newreversediff +end + +t[#t+1] = UIElements.QuadButton(1, 1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(rightHalfXBegin, actuals.DensityGraphHeight) + self:zoomto(actuals.Width - rightHalfXBegin, actuals.NoteFieldHeight + actuals.LowerLipHeight) + registerActorToColorConfigElement(self, "chartPreview", "Background") + end, + MouseDownCommand = function(self, params) + local top = SCREENMAN:GetTopScreen() + if params.event ~= "DeviceButton_left mouse button" then + if top.PauseSampleMusic then + top:PauseSampleMusic() + end + end + end +} + +t[#t+1] = Def.NoteFieldPreview { + Name = "NoteField", + DrawDistanceBeforeTargetsPixels = notefieldLengthPixels / notefieldZoomBaseline, + DrawDistanceAfterTargetsPixels = notefieldAllowBeyondReceptorPixels, -- notes disappear at the receptor + + InitCommand = function(self) + self:playcommand("SetPosition") + self:y(notefieldYCenter) + self:zoom(notefieldZoomBaseline) + -- make mods work + self:SetFollowPlayerOptions(true) + self:SetUpdateFunction(function(self) + ArrowEffects.Update() + end) + end, + BeginCommand = function(self) + -- we need to redo the draw order for the notefield and graph + -- the notefield ends up being on top of everything in the actorframe otherwise + self:draworder(1) + self:GetParent():GetChild("ChordDensityGraphFile"):draworder(2) + self:GetParent():SortByDrawOrder() + end, + SetPositionCommand = function(self) + -- THESE ARE LITERALLY RANDOM NUMBERS + -- I DO NOT KNOW WHY THIS IS NECESSARY + -- IT DOES NOT MAKE ANY SENSE + if getWheelPosition() then + self:x(rightHalfXBegin + 15) + else + self:x(rightHalfXBegin + (actuals.Width - rightHalfXBegin) / 2) + end + end, + LoadNoteDataCommand = function(self, params) + local steps = params.steps + if steps ~= nil then + self:LoadNoteData(steps, true) + else + self:LoadDummyNoteData() + end + local z, l, r = getSizeForStyle() + self:zoom(z) + self:SetConstantMini(ReceptorSizeToMini(z)) + -- when changing zoom of the notefield, the receptors change position just like the length needs to + -- so need to move the notefield up or down to compensate for the change in zoom + local compensation = -(actuals.NoteFieldHeight) * (notefieldZoomBaseline-z)/2 + -- running from what you fear brings you yet closer to that which you loathe + -- magic numbers run the world + compensation = compensation + (getPlayerOptions():UsingReverse() and 25 or 0) + self:y(notefieldYCenter - 40 + compensation) + self:UpdateDrawDistance(notefieldAllowBeyondReceptorPixels, l) + self:UpdateYReversePixels(r) + end, + OptionUpdatedMessageCommand = function(self, params) + if params ~= nil then + -- listen for the notedata modifying mods being toggled and apply their changes immediately + local options = { + Mirror = true, + Turn = true, + ["Pattern Transform"] = true, + ["Hold Transform"] = true, + Remove = true, + Insert = true, + Mines = true, + ["Scroll Direction"] = true, + } + if options[params.name] ~= nil then + self:playcommand("LoadNoteData", {steps = GAMESTATE:GetCurrentSteps()}) + end + + if params.name == "Music Wheel Position" then + self:playcommand("SetPosition") + end + end + end, +} + +t[#t+1] = LoadActorWithParams("../../chordDensityGraph.lua", {sizing = { + Width = actuals.Width - rightHalfXBegin, + Height = actuals.DensityGraphHeight, + NPSThickness = 1.5, + TextSize = 0.45, +}}) .. { + InitCommand = function(self) + self:x(rightHalfXBegin) + end, + LoadNoteDataCommand = function(self, params) + local steps = params.steps + if steps ~= nil then + self:playcommand("LoadDensityGraph", {steps = steps, song = params.song}) + else + self:playcommand("LoadDensityGraph", {steps = steps, song = params.song}) + end + end +} + +t[#t+1] = UIElements.QuadButton(1, 1) .. { + Name = "ChoicesCover", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(rightHalfXBegin, actuals.DensityGraphHeight + actuals.NoteFieldHeight) + self:zoomto(actuals.Width - rightHalfXBegin, actuals.LowerLipHeight) + self:diffuse(color("#000000")) + self:draworder(2):visible(0) + end, +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/general.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/general.lua new file mode 100644 index 0000000000..6903e789aa --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/general.lua @@ -0,0 +1,590 @@ +local displayScore +local t = Def.ActorFrame { + Name = "GeneralPageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + WheelSettledMessageCommand = function(self, params) + -- update displayscore + -- it sets to nil properly by itself + displayScore = GetDisplayScore() + + -- could not think of a good place to put this + -- this turns off mirror for the specific situation if you just finished a permamirrored chart + -- i have no idea why it works but it does + -- (shouldnt turning mirror off every time song changes break regular mirror? it doesnt) + if GetPlayerOrMachineProfile(PLAYER_1):IsCurrentChartPermamirror() then + local modslevel = topscreen == "ScreenEditOptions" and "ModsLevel_Stage" or "ModsLevel_Preferred" + local playeroptions = GAMESTATE:GetPlayerState():GetPlayerOptions(modslevel) + playeroptions:Mirror(false) + end + + -- cascade visual update to everything + self:playcommand("Set", {song = params.song, group = params.group, hovered = params.hovered, steps = params.steps}) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.generaltabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + end + end + end, + CurrentRateChangedMessageCommand = function(self) + -- update displayscore + -- it sets to nil properly by itself + displayScore = GetDisplayScore() + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong(), hovered = lastHovered, steps = GAMESTATE:GetCurrentSteps()}) + end, + ChangedStepsMessageCommand = function(self, params) + displayScore = GetDisplayScore() + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong(), hovered = lastHovered, steps = params.steps}) + end, +} + +local ratios = { + VerticalDividerLeftGap = 387 / 1920, -- from left edge to left edge of divider + VerticalDividerUpperGap = 42 / 1080, -- from top edge to top edge + VerticalDividerHeight = 374 / 1080, + HorizontalDividerLeftGap = 11 / 1920, -- from left edge to left edge of divider + HorizontalDividerUpperGap = 431 / 1080, -- from top edge to top edge + HorizontalDividerLength = 753 / 1920, + DividerThickness = 2 / 1080, -- consistently 2 pixels basically + + LeftTextColumn1NumbersMargin = 172 / 1920, -- from left edge to right edge of text + LeftTextColumn1LabelsMargin = 12 / 1920, -- from left edge to left edge of text + LeftTextColumn2Margin = 207 / 1920, -- from left edge to left edge + LeftTextUpperGap = 169 / 1080, -- from top edge to top edge + -- use the column1 and column2 x positions for the tag locations as well + MSDUpperGap = 43 / 1080, -- top edge to top edge + WifePercentUpperGap = 90 / 1080, -- top edge to top edge + + RightTextLabelsMargin = 327 / 1920, -- from right edge to left edge of text + RightTextNumbersMargin = 66 / 1920, -- from right edge to right edge of text + + LeftTextAllottedVerticalSpace = 210 / 1080, -- from top edge of top text to top edge of bottom text + -- right text also has allotted space but we will extrapolate from this number + TagTextUpperGap = 453 / 1080, -- from top edge to top edge + TagTextAllottedVerticalSpace = 42 / 1080, -- from top edge of top text to top edge of bottom text + + CDTitleRightGap = 5 / 1920, -- estimated right side gap for the cdtitle (restrict width) + CDTitleLeftGap = 190 / 1920, -- left edge to approximate left edge + -- CDTitle width is VerticalDividerX - CDTitleRightGap - CDTitleLeftGap + CDTitleUpperGap = 36 / 1080, -- top edge to approximate top edge + CDTitleAllowedHeight = 100 / 1080, -- approximated allowed height, from top edge to bottom edge +} + +local actuals = { + VerticalDividerLeftGap = ratios.VerticalDividerLeftGap * SCREEN_WIDTH, + VerticalDividerUpperGap = ratios.VerticalDividerUpperGap * SCREEN_HEIGHT, + VerticalDividerHeight = ratios.VerticalDividerHeight * SCREEN_HEIGHT, + HorizontalDividerLeftGap = ratios.HorizontalDividerLeftGap * SCREEN_WIDTH, + HorizontalDividerUpperGap = ratios.HorizontalDividerUpperGap * SCREEN_HEIGHT, + HorizontalDividerLength = ratios.HorizontalDividerLength * SCREEN_WIDTH, + DividerThickness = ratios.DividerThickness * SCREEN_HEIGHT, + LeftTextColumn1NumbersMargin = ratios.LeftTextColumn1NumbersMargin * SCREEN_WIDTH, + LeftTextColumn1LabelsMargin = ratios.LeftTextColumn1LabelsMargin * SCREEN_WIDTH, + LeftTextColumn2Margin = ratios.LeftTextColumn2Margin * SCREEN_WIDTH, + LeftTextUpperGap = ratios.LeftTextUpperGap * SCREEN_HEIGHT, + MSDUpperGap = ratios.MSDUpperGap * SCREEN_HEIGHT, + WifePercentUpperGap = ratios.WifePercentUpperGap * SCREEN_HEIGHT, + RightTextLabelsMargin = ratios.RightTextLabelsMargin * SCREEN_WIDTH, + RightTextNumbersMargin = ratios.RightTextNumbersMargin * SCREEN_WIDTH, + --RightTextNumbersMargin = ratios.LeftTextColumn1LabelsMargin * SCREEN_WIDTH, -- to have equal space as left stuff + LeftTextAllottedVerticalSpace = ratios.LeftTextAllottedVerticalSpace * SCREEN_HEIGHT, + TagTextUpperGap = ratios.TagTextUpperGap * SCREEN_HEIGHT, + TagTextAllottedVerticalSpace = ratios.TagTextAllottedVerticalSpace * SCREEN_HEIGHT, + CDTitleRightGap = ratios.CDTitleRightGap * SCREEN_WIDTH, + CDTitleLeftGap = ratios.CDTitleLeftGap * SCREEN_WIDTH, + CDTitleUpperGap = ratios.CDTitleUpperGap * SCREEN_HEIGHT, + CDTitleAllowedHeight = ratios.CDTitleAllowedHeight * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +local statNames = { + "Notes", + "Jumps", + "Hands", + "Holds", + "Rolls", + "Mines", +} + +-- output of the relevant radars function is in a certain order +-- it isnt the order of the above list +-- so this list takes those indices and points them in another direction +local statMapping = { + -- output -> desired + 1, -- notes - notes + 2, -- jumps - jumps + 3, -- hands - hands + 4, -- holds - holds + 6, -- mines - rolls + 5, -- rolls - mines + 7, -- lifts + 8, -- fakes +} + +local msdNames = { + "Average NPS", + "Stream", + "Jumpstream", + "Handstream", + "Stamina", + "JackSpeed", + "Chordjack", + "Technical", +} + +local mainTextSize = 1 +local largerTextSize = 1.35 +local displayScoreInfoTextSize = 0.75 + +local textzoomFudge = 5 +-- bump the second line of the display score info down by this much +local displayScoreBump = 8 + +local function createStatLines() + local function createStatLine(i) + return Def.ActorFrame { + Name = "Stat"..i, + InitCommand = function(self) + self:y(actuals.LeftTextUpperGap + (actuals.LeftTextAllottedVerticalSpace / (#statNames-1)) * (i-1)) + end, + + LoadFont("Common Normal") .. { + Name = "Label", + InitCommand = function(self) + self:x(actuals.LeftTextColumn1LabelsMargin) + self:halign(0):valign(0) + self:zoom(mainTextSize) + -- dont fudge this to avoid compressing static text + self:maxwidth(((actuals.LeftTextColumn1NumbersMargin - actuals.LeftTextColumn1LabelsMargin) / 1.9) / mainTextSize) + self:settextf("%s:", statNames[i]) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.RollingNumbers { + Name = "Count", + Font = "Common Normal", + InitCommand = function(self) + self:x(actuals.LeftTextColumn1NumbersMargin) + self:halign(1):valign(0) + self:zoom(mainTextSize) + self:maxwidth(((actuals.LeftTextColumn1NumbersMargin - actuals.LeftTextColumn1LabelsMargin) / 2) / mainTextSize - textzoomFudge) + self:Load("RollingNumbersNoLead") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetCommand = function(self, params) + if params.steps then + self:targetnumber(params.steps:GetRelevantRadars()[statMapping[i]]) + else + self:targetnumber(0) + end + end + } + } + end + local t = Def.ActorFrame {Name = "Stats"} + for i = 1, #statNames do + t[#t+1] = createStatLine(i) + end + return t +end + +local function createTopSkillsetLines() + local function createSkillsetLine(i) + return Def.ActorFrame { + Name = "SkillsetLine"..i, + InitCommand = function(self) + self:y(actuals.LeftTextUpperGap + (actuals.LeftTextAllottedVerticalSpace / (#statNames-1)) * (i-1)) + end, + + LoadFont("Common Normal") .. { + Name = "Text", + InitCommand = function(self) + self:x(actuals.LeftTextColumn2Margin) + self:halign(0):valign(0) + self:zoom(mainTextSize) + self:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftTextColumn1LabelsMargin - actuals.LeftTextColumn2Margin) / mainTextSize - textzoomFudge) + self:settext("Jumpstream") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.steps then + local ss = params.steps:GetRelevantSkillsetsByMSDRank(getCurRateValue(), i) + self:settext(ss) + else + self:settext("") + end + end + } + } + end + local t = Def.ActorFrame {Name = "TopSkillsets"} + for i = 1, 3 do + t[#t+1] = createSkillsetLine(i) + end + return t +end + +local function createMSDLines() + local function createMSDLine(i) + local labeltext = msdNames[i] + return Def.ActorFrame { + Name = "MSDLine"..i, + InitCommand = function(self) + self:y(actuals.LeftTextUpperGap + (actuals.LeftTextAllottedVerticalSpace / (#statNames-1)) * (i-1 - 2)) + end, + + LoadFont("Common Normal") .. { + Name = "Label", + InitCommand = function(self) + self:x(actuals.Width - actuals.RightTextLabelsMargin) + self:halign(0):valign(0) + self:zoom(mainTextSize) + -- dont fudge this to avoid compressing static text + self:maxwidth(((actuals.RightTextLabelsMargin - actuals.RightTextNumbersMargin) / 1.7) / mainTextSize) + if labeltext then + self:settextf("%s:", labeltext) + end + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + -- HACKS HACKS HACKS + -- (remove when validating negbpms soon) + if i == 0 then + if params.steps then + if params.steps:GetTimingData():HasWarps() then + self:settext("NegBPMs!") + self:diffusealpha(1) + else + self:diffusealpha(0) + end + else + self:diffusealpha(0) + end + end + end, + }, + Def.RollingNumbers { + Name = "Number", + Font = "Common Normal", + InitCommand = function(self) + self:x(actuals.Width - actuals.RightTextNumbersMargin) + self:halign(1):valign(0) + self:zoom(mainTextSize) + self:maxwidth(((actuals.RightTextLabelsMargin - actuals.RightTextNumbersMargin) / 2) / mainTextSize - textzoomFudge) + self:Load("RollingNumbers2Decimal") + if not labeltext then self:visible(false) end + end, + SetCommand = function(self, params) + if i == 0 then return end -- negbpm indicator HACKS remove when validating negbpms soon + -- i == 1 is Average NPS, otherwise are skillsets + if i == 1 then + if params.steps then + -- notecount / length * rate + local len = params.steps:GetLengthSeconds() + local notes = params.steps:GetRadarValues(PLAYER_1):GetValue("RadarCategory_Notes") + local avg = notes / len + if len == 0 then + if notes > 0 then + avg = notes + else + avg = 0 + end + elseif len < 1 then + avg = clamp(avg, 0, notes) + end + self:targetnumber(avg) + self:diffuse(colorByNPS(avg)) + else + -- failsafe + self:targetnumber(0) + self:diffuse(color("1,1,1,1")) + end + else + if params.song then + if params.steps then + local val = params.steps:GetMSD(getCurRateValue(), i) + self:targetnumber(val) + self:diffuse(colorByMSD(val)) + else + -- failsafe + self:targetnumber(0) + self:diffuse(color("1,1,1,1")) + end + else + self:targetnumber(0) + self:diffuse(color("1,1,1,1")) + end + end + end + } + } + end + local t = Def.ActorFrame {Name = "MSDLines"} + for i = 0, #msdNames do -- starts at 0 for NegBPMs + t[#t+1] = createMSDLine(i) + end + return t +end + +-- only accounting for room for 4 tags +local function createTagDisplays() + local currentTags = {"","","",""} + local function createTagDisplay(i) + local xPos = i < 3 and actuals.LeftTextColumn1LabelsMargin or actuals.LeftTextColumn2Margin + return LoadFont("Common Normal") .. { + Name = "Tag"..i, + InitCommand = function(self) + self:xy(xPos, actuals.TagTextUpperGap + (actuals.TagTextAllottedVerticalSpace * ((i-1) % 2))) + self:halign(0):valign(0) + self:zoom(mainTextSize) + self:maxwidth((actuals.VerticalDividerLeftGap - actuals.LeftTextColumn1LabelsMargin - actuals.LeftTextColumn2Margin) / mainTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetCommand = function(self, params) + if params.steps then + if currentTags[i] then + self:settext(currentTags[i]) + else + self:settext("") + end + else + self:settext("") + end + end + } + end + local t = Def.ActorFrame { + Name = "TagDisplays", + SetCommand = function(self, params) + -- update tag data + currentTags = {} + if params.song and params.steps then + local playerTags = TAGMAN:get_data().playerTags + local ck = params.steps:GetChartKey() + for k,v in pairs(playerTags) do + if playerTags[k][ck] then + currentTags[#currentTags+1] = k + end + end + table.sort( + currentTags, + function(a,b) return a:lower() < b:lower() end + ) + end + end, + ReassignedTagsMessageCommand = function(self) + self:playcommand("Set", {song = GAMESTATE:GetCurrentSong(), steps = GAMESTATE:GetCurrentSteps()}) + end + } + for i = 1, #currentTags do + t[#t+1] = createTagDisplay(i) + end + return t +end + +t[#t+1] = Def.Quad { + Name = "HorizontalDivider", + InitCommand = function(self) + self:xy(actuals.HorizontalDividerLeftGap, actuals.HorizontalDividerUpperGap) + self:zoomto(actuals.HorizontalDividerLength, actuals.DividerThickness) + self:halign(0):valign(0) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = Def.Quad { + Name = "VerticalDivider", + InitCommand = function(self) + self:xy(actuals.VerticalDividerLeftGap, actuals.VerticalDividerUpperGap) + self:zoomto(actuals.DividerThickness, actuals.VerticalDividerHeight) + self:halign(0):valign(0) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + ToggleChartPreviewCommand = function(self) + self:visible(not SCUFF.preview.active) + end, +} + +t[#t+1] = Def.RollingNumbers { + Name = "MSD", + Font = "Common Normal", + InitCommand = function(self) + self:xy(actuals.LeftTextColumn1NumbersMargin, actuals.MSDUpperGap) + self:halign(1):valign(0) + self:zoom(largerTextSize) + self:maxwidth((actuals.LeftTextColumn1NumbersMargin - actuals.LeftTextColumn1LabelsMargin) / largerTextSize - textzoomFudge) + self:Load("RollingNumbers2Decimal") + end, + SetCommand = function(self, params) + if params.steps then + local meter = params.steps:GetMSD(getCurRateValue(), 1) + self:targetnumber(meter) + self:diffuse(colorByMSD(meter)) + else + self:targetnumber(0) + self:diffuse(color("1,1,1,1")) + end + end +} + +t[#t+1] = Def.ActorFrame { + Name = "DisplayScoreFrame", + InitCommand = function(self) + self:xy(actuals.LeftTextColumn1NumbersMargin, actuals.WifePercentUpperGap) + end, + + LoadFont("Common Normal") .. { + Name = "WifePercent", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoom(largerTextSize) + self:maxwidth((actuals.LeftTextColumn1NumbersMargin - actuals.LeftTextColumn1LabelsMargin) / largerTextSize - textzoomFudge) + self:settext("99.99%") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self, params) + if displayScore then + local wife = displayScore:GetWifeScore() + local wifestr = checkWifeStr(wife) + self:settext(wifestr) + self:diffuse(colorByGrade(displayScore:GetWifeGrade())) + else + self:settext("") + end + end + }, + LoadFont("Common Normal") .. { + Name = "CurScoreInfoIndicator", + InitCommand = function(self) + self:valign(0):halign(1) + -- bump + self:y(self:GetZoomedHeight() * largerTextSize) + self:zoom(displayScoreInfoTextSize) + self:maxwidth((actuals.LeftTextColumn1NumbersMargin - actuals.LeftTextColumn1LabelsMargin) / displayScoreInfoTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + BeginCommand = function(self) + --self:x(-self:GetParent():GetX() +(actuals.LeftTextColumn1LabelsMargin + actuals.LeftTextColumn1NumbersMargin) / 2) + self:y(self:GetParent():GetChild("WifePercent"):GetZoomedHeight() + displayScoreBump) + end, + SetCommand = function(self, params) + if displayScore then + local wvstr = "W"..displayScore:GetWifeVers() + + local rate = notShit.round(displayScore:GetMusicRate(), 3) + local notCurRate = notShit.round(getCurRateValue(), 3) ~= rate + if notCurRate then + local ratestr = string.format("%.2f", rate) .. "x" + self:settextf("%s [%s]", wvstr, ratestr) + else + self:settext(wvstr) + end + else + self:settext("") + end + end + } +} + +t[#t+1] = UIElements.SpriteButton(1, 1, nil) .. { + Name = "CDTitle", + InitCommand = function(self) + -- lets... avoid aligning this. + -- we want to try to avoid moving the cdtitle a lot + -- so find the center position of the measure coordinates + local leftEdge = actuals.CDTitleLeftGap + local rightEdge = actuals.VerticalDividerLeftGap - actuals.CDTitleRightGap + local bottomEdge = actuals.CDTitleUpperGap + actuals.CDTitleAllowedHeight + local topEdge = actuals.CDTitleUpperGap + local cX = (leftEdge + rightEdge) / 2 + local cY = (bottomEdge + topEdge) / 2 + self:xy(cX, cY) + end, + SetCommand = function(self, params) + self:finishtweening() + self.song = params.song + if params.song then + if params.song:HasCDTitle() then + self:diffusealpha(1) + self:Load(params.song:GetCDTitlePath()) + + local h = self:GetHeight() + local w = self:GetWidth() + local allowedWidth = actuals.VerticalDividerLeftGap - actuals.CDTitleRightGap - actuals.CDTitleLeftGap + if h >= actuals.CDTitleAllowedHeight and w >= allowedWidth then + if h * (allowedWidth / actuals.CDTitleAllowedHeight) >= w then + self:zoom(actuals.CDTitleAllowedHeight / h) + else + self:zoom(allowedWidth / w) + end + elseif h >= actuals.CDTitleAllowedHeight then + self:zoom(actuals.CDTitleAllowedHeight / h) + elseif w >= allowedWidth then + self:zoom(allowedWidth / w) + else + self:zoom(1) + end + else + self:diffusealpha(0) + end + else + self:diffusealpha(0) + end + if isOver(self) then + self:playcommand("ToolTip") + end + end, + ToolTipCommand = function(self) + if isOver(self) then + if self.song and not self:IsInvisible() then + local auth = self.song:GetOrTryAtLeastToGetSimfileAuthor() + if auth and #auth > 0 then + TOOLTIP:SetText(auth) + TOOLTIP:Show() + end + else + TOOLTIP:Hide() + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:playcommand("ToolTip") + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + end, +} + +t[#t+1] = createStatLines() +t[#t+1] = createTopSkillsetLines() +t[#t+1] = createMSDLines() +t[#t+1] = createTagDisplays() +t[#t+1] = LoadActorWithParams("_chartPreview.lua", {ratios = ratios, actuals = actuals}) + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/goals.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/goals.lua new file mode 100644 index 0000000000..28739876ec --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/goals.lua @@ -0,0 +1,1238 @@ +local focused = false +local t = Def.ActorFrame { + Name = "GoalsPageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.goalstabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + focused = true + self:playcommand("UpdateGoalsTab") + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + focused = false + end + end + end, + WheelSettledMessageCommand = function(self, params) + if not focused then return end + end, + ChangedStepsMessageCommand = function(self, params) + if not focused then return end + end +} + +local ratios = { + UpperLipHeight = 43 / 1080, + LipSeparatorThickness = 2 / 1080, + + PageTextRightGap = 33 / 1920, -- right of frame, right of text + PageNumberUpperGap = 48 / 1080, -- bottom of upper lip to top of text + + ItemListUpperGap = 35 / 1080, -- bottom of upper lip to top of topmost item + ItemAllottedSpace = 405 / 1080, -- top of topmost item to top of bottommost item + ItemLowerLineUpperGap = 30 / 1080, -- top of top line to top of bottom line + ItemDividerThickness = 3 / 1080, -- you know what it is (i hope) (ok its based on height so things are consistent-ish) + ItemDividerLength = 26 / 1080, + + ItemPriorityLeftGap = 11 / 1920, -- left edge of frame to left edge of number + ItemPriorityWidth = 38 / 1920, -- left edge of number to uhh nothing + IconWidth = 18 / 1920, -- for the trash thing + IconHeight = 21 / 1080, +} + +local actuals = { + UpperLipHeight = ratios.UpperLipHeight * SCREEN_HEIGHT, + LipSeparatorThickness = ratios.LipSeparatorThickness * SCREEN_HEIGHT, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + PageNumberUpperGap = ratios.PageNumberUpperGap * SCREEN_HEIGHT, + ItemListUpperGap = ratios.ItemListUpperGap * SCREEN_HEIGHT, + ItemAllottedSpace = ratios.ItemAllottedSpace * SCREEN_HEIGHT, + ItemLowerLineUpperGap = ratios.ItemLowerLineUpperGap * SCREEN_HEIGHT, + ItemDividerThickness = ratios.ItemDividerThickness * SCREEN_HEIGHT, + ItemDividerLength = ratios.ItemDividerLength * SCREEN_HEIGHT, + ItemPriorityLeftGap = ratios.ItemPriorityLeftGap * SCREEN_WIDTH, + ItemPriorityWidth = ratios.ItemPriorityWidth * SCREEN_WIDTH, + IconWidth = ratios.IconWidth * SCREEN_WIDTH, + IconHeight = ratios.IconHeight * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +local goalLine1TextSize = 0.85 +local goalLine2TextSize = 0.75 +local pageTextSize = 0.7 + +-- our fontpage SUCKS so this should make things look better +-- undo this if the fontpage doesnt SUCK +-- update i fixed the font but it looked back after i undid this +local dividerUpwardBump = 1 + +local choiceTextSize = 0.7 +local buttonHoverAlpha = 0.6 +local textzoomFudge = 5 + +local goalListAnimationSeconds = 0.05 + +local function byAchieved(scoregoal) + if not scoregoal or scoregoal:IsAchieved() then + return COLORS:getColor("generalBox", "GoalAchieved") + end + return COLORS:getColor("generalBox", "GoalDefault") +end + +--===== +-- Sorting functions +-- used for sorting goal tables in various ways +-- we do this from lua because i dont want to let C++ control this +-- and im micromanaging most of the equal cases so it comes out as clean as possible +-- yea +-- my only justification for doing this in a drawn out and purpose specific manner is +-- that i want to make all the logic exposed and in one place +-- +-- dategetter is a function that takes a goal as input and outputs a date +-- it lets you determine either to sort by Set date or Achieved date +--===== +local function sortGoalsByPriority(goaltable, dategetter, ascending) + if ascending == nil then ascending = true end + table.sort( + goaltable, + function(a, b) + local aprio = a:GetPriority() + local bprio = b:GetPriority() + if aprio ~= bprio then + if ascending then + return aprio < bprio + else + return aprio > bprio + end + else + -- priority the same, sort by set date + local adate = dategetter(a) + local bdate = dategetter(b) + if adate ~= bdate then + -- whats funny about the dates is that because the format is consistent we can just compare the strings + -- ... i think + if ascending then + return adate < bdate + else + return adate > bdate + end + else + -- date somehow the same, sort by song name + local ack = a:GetChartKey() + local bck = b:GetChartKey() + local asong = SONGMAN:GetSongByChartKey(ack) + local bsong = SONGMAN:GetSongByChartKey(bck) + local aname = asong ~= nil and asong:GetDisplayMainTitle() or ack + local bname = bsong ~= nil and bsong:GetDisplayMainTitle() or bck + if aname ~= bname then + -- lua handles string comparisons on a char/byte kind of level so a is different from A + -- we dont want to care and we also want to keep the ordering equivalent to the wheel ordering + aname = WHEELDATA.makeSortString(aname) + bname = WHEELDATA.makeSortString(bname) + if ascending then + return aname < bname + else + return aname > bname + end + else + -- name the same, sort by rate + local arate = a:GetRate() + local brate = b:GetRate() + if arate ~= brate then + if ascending then + return arate < brate + else + return arate > brate + end + else + -- rate the same, sort by MSD (this is as far as ill go) + local asteps = SONGMAN:GetStepsByChartKey(ack) + local bsteps = SONGMAN:GetStepsByChartKey(bck) + -- the next 2 lines here might be backwards + if asteps == nil then return ascending end + if bsteps == nil then return not ascending end + + local amsd = asteps:GetMSD(arate, 1) + local bmsd = bsteps:GetMSD(brate, 1) + if ascending then + return amsd < bmsd + else + return amsd > bmsd + end + end + end + end + end + end + ) +end +local function sortGoalsByRate(goaltable, dategetter, ascending) + if ascending == nil then ascending = true end + table.sort( + goaltable, + function(a, b) + local arate = a:GetRate() + local brate = b:GetRate() + if arate ~= brate then + if ascending then + return arate < brate + else + return arate > brate + end + else + -- rate is the same, sort by priority + local aprio = a:GetPriority() + local bprio = b:GetPriority() + if aprio ~= bprio then + if ascending then + return aprio < bprio + else + return aprio > bprio + end + else + -- priority the same, sort by song name (a rate sort would probably be comparing goals on rates on songs) + -- the alternative here would be to sort by set date which makes no sense to me if that is the assumption + local ack = a:GetChartKey() + local bck = b:GetChartKey() + local asong = SONGMAN:GetSongByChartKey(ack) + local bsong = SONGMAN:GetSongByChartKey(bck) + local aname = asong ~= nil and asong:GetDisplayMainTitle() or ack + local bname = bsong ~= nil and bsong:GetDisplayMainTitle() or bck + if aname ~= bname then + -- lua handles string comparisons on a char/byte kind of level so a is different from A + -- we dont want to care and we also want to keep the ordering equivalent to the wheel ordering + aname = WHEELDATA.makeSortString(aname) + bname = WHEELDATA.makeSortString(bname) + if ascending then + return aname < bname + else + return aname > bname + end + else + -- names are the same so sort by date and thats as far as we care + local adate = dategetter(a) + local bdate = dategetter(b) + if ascending then + return adate < bdate + else + return adate > bdate + end + end + end + end + + end + ) +end +local function sortGoalscolorByMSD(goaltable, dategetter, ascending) + if ascending == nil then ascending = true end + table.sort( + goaltable, + function(a, b) + local ack = a:GetChartKey() + local bck = b:GetChartKey() + local asteps = SONGMAN:GetStepsByChartKey(ack) + local bsteps = SONGMAN:GetStepsByChartKey(bck) + if asteps == nil then return ascending end + if bsteps == nil then return not ascending end + local arate = a:GetRate() + local brate = b:GetRate() + + local amsd = asteps:GetMSD(arate, 1) + local bmsd = bsteps:GetMSD(brate, 1) + if amsd ~= bmsd then + if ascending then + return amsd < bmsd + else + return amsd > bmsd + end + else + -- msd is the same, sort by priority + local aprio = a:GetPriority() + local bprio = b:GetPriority() + if aprio ~= bprio then + if ascendingn then + return aprio < bprio + else + return aprio > bprio + end + else + -- priority is the same, sort by song name and thats as far as we care (msd is rarely equal to begin with) + local asong = SONGMAN:GetSongByChartKey(ack) + local bsong = SONGMAN:GetSongByChartKey(bck) + local aname = asong ~= nil and asong:GetDisplayMainTitle() or ack + local bname = bsong ~= nil and bsong:GetDisplayMainTitle() or bck + aname = WHEELDATA.makeSortString(aname) + bname = WHEELDATA.makeSortString(bname) + if ascending then + return aname < bname + else + return aname > bname + end + end + end + end + ) +end +local function sortGoalsByName(goaltable, dategetter, ascending) + if ascending == nil then ascending = true end + table.sort( + goaltable, + function(a, b) + local ack = a:GetChartKey() + local bck = b:GetChartKey() + local asong = SONGMAN:GetSongByChartKey(ack) + local bsong = SONGMAN:GetSongByChartKey(bck) + local aname = asong ~= nil and asong:GetDisplayMainTitle() or ack + local bname = bsong ~= nil and bsong:GetDisplayMainTitle() or bck + if aname ~= bname then + aname = WHEELDATA.makeSortString(aname) + bname = WHEELDATA.makeSortString(bname) + if ascending then + return aname < bname + else + return aname > bname + end + else + -- names the same, sort by priority + local aprio = a:GetPriority() + local bprio = b:GetPriority() + if aprio ~= bprio then + if ascending then + return aprio < bprio + else + return aprio > bprio + end + else + -- priority the same, sort by rate and thats as far as we care + local arate = a:GetRate() + local brate = b:GetRate() + if ascending then + return arate < brate + else + return arate > brate + end + end + end + end + ) +end +local function sortGoalsByDate(goaltable, dategetter, ascending) + if ascending == nil then ascending = true end + table.sort( + goaltable, + function(a, b) + local adate = dategetter(a) + local bdate = dategetter(b) + if adate ~= bdate then + if ascending then + return adate < bdate + else + return adate > bdate + end + else + -- date the same, sort by priority + -- date should basically never be the same... + local aprio = a:GetPriority() + local bprio = b:GetPriority() + if aprio ~= bprio then + if ascending then + return aprio < bprio + else + return aprio > bprio + end + else + -- priority the same, sort by name and thats as far as we care + local ack = a:GetChartKey() + local bck = b:GetChartKey() + local asong = SONGMAN:GetSongByChartKey(ack) + local bsong = SONGMAN:GetSongByChartKey(bck) + local aname = asong ~= nil and asong:GetDisplayMainTitle() or ack + local bname = bsong ~= nil and bsong:GetDisplayMainTitle() or bck + aname = WHEELDATA.makeSortString(aname) + bname = WHEELDATA.makeSortString(bname) + if ascending then + return aname < bname + else + return aname > bname + end + end + end + end + ) +end + +-- end sorting functions +--===== + +-- the entire goal ActorFrame +local function goalList() + -- modifiable parameters + local goalItemCount = 7 + + -- internal var storage + local page = 1 + local maxPage = 1 + local goalListFrame = nil + local profile = GetPlayerOrMachineProfile(PLAYER_1) + local goalTable = profile:GetGoalTable() + + -- sortmode info storage + -- default to Priority Sort + -- the order of preference for cases of equality is: + -- valid choices: Priority, Rate, MSD, Name, Date + local sortMode = "Priority" + local defaultAscending = false + local sortAscending = defaultAscending + -- filter visible goals by achievement + -- valid choices: All, Complete, Incomplete + local visibleGoalType = "All" + + -- this resets the goal table and sorting done by the C++ + -- why is this done in C++ and not Lua? great question + -- this is handled in Lua anyways (it would just change ascending and sortmodes for goals) + profile:SetFromAll() + + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + if goalListFrame then + goalListFrame:playcommand("UpdateGoalList") + end + end + + -- function to reset the goal list and filter it + local function resortGoals() + goalTable = {} + + -- filter the goal table by chosen visible goals + for _, g in ipairs(profile:GetGoalTable()) do + if visibleGoalType == "Complete" then + -- complete only + if g:IsAchieved() then + goalTable[#goalTable+1] = g + end + elseif visibleGoalType == "Incomplete" then + -- incomplete only + if not g:IsAchieved() and not g:IsVacuous() then + goalTable[#goalTable+1] = g + end + else + -- all goals + goalTable[#goalTable+1] = g + end + end + + page = 1 + maxPage = math.ceil(#goalTable / goalItemCount) + + -- set up date getter for sorting + local dategetter + if visibleGoalType == "Complete" then + -- if the visible goal type is Complete then we care about the achieved date + dategetter = function(goal) + return goal:WhenAchieved() + end + else + -- if the visible goal type is not Complete then we only care about the set date + dategetter = function(goal) + return goal:WhenAssigned() + end + end + + -- sort goals by sortmode + if sortMode == "Priority" then + sortGoalsByPriority(goalTable, dategetter, sortAscending) + elseif sortMode == "Rate" then + sortGoalsByRate(goalTable, dategetter, sortAscending) + elseif sortMode == "MSD" then + sortGoalscolorByMSD(goalTable, dategetter, sortAscending) + elseif sortMode == "Name" then + sortGoalsByName(goalTable, dategetter, sortAscending) + elseif sortMode == "Date" then + sortGoalsByDate(goalTable, dategetter, sortAscending) + else + sortGoalsByPriority(goalTable, dategetter, sortAscending) + end + end + + local function goalListItem(i) + local index = i + local goal = nil + local goalSteps = nil + + -- theres a lot going on here i just wanted to write down vars representing math so its a little clearer for everyone + -- i should have done this kind of thing in more places but ... + local itemWidth = actuals.Width + local prioX = actuals.ItemPriorityLeftGap + local prioW = actuals.ItemPriorityWidth + local remainingWidth = itemWidth - prioW - prioX + local diffW = remainingWidth / 60 * 13 -- keep this in line with the other divisions below (combined at around 1/1) -- 13/60 + local diffX = prioX + prioW + diffW/2 + local div1X = prioX + prioW + diffW + local rateW = remainingWidth / 60 * 13 -- above comment -- 13/60 + local rateX = div1X + rateW/2 + local div2X = div1X + rateW + local percentW = remainingWidth / 60 * 21 -- above comment -- 21/60 + local percentX = div2X + percentW/2 + local div3X = div2X + percentW + local msdW = remainingWidth / 60 * 13 - actuals.IconWidth * 2 -- above comment -- 13/60 + local msdX = div3X + msdW/2 + local itemHeight = (actuals.ItemAllottedSpace / (goalItemCount - 1)) + + return Def.ActorFrame { + Name = "GoalItemFrame_"..i, + InitCommand = function(self) + self:y(itemHeight * (i-1) + actuals.ItemListUpperGap + actuals.UpperLipHeight) + end, + UpdateGoalListCommand = function(self) + index = (page - 1) * goalItemCount + i + goal = goalTable[index] + goalSteps = nil + self:finishtweening() + self:diffusealpha(0) + if goal ~= nil then + goalSteps = SONGMAN:GetStepsByChartKey(goal:GetChartKey()) + self:playcommand("UpdateText") + self:smooth(goalListAnimationSeconds * i) + self:diffusealpha(1) + end + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:valign(0) + self:zoomto(itemWidth, itemHeight * 0.95) + self:y(-itemHeight/8) + self:diffusealpha(0.1) + registerActorToColorConfigElement(self, "generalBox", "GoalBackground") + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Priority", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(0) + bg:halign(0) + + self:x(prioX) + txt:zoom(goalLine1TextSize) + txt:maxwidth(prioW / goalLine1TextSize - textzoomFudge) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(prioW, txt:GetZoomedHeight()) + bg:y(txt:GetZoomedHeight() / 2) + end, + UpdateTextCommand = function(self) + if goal == nil then return end + local txt = self:GetChild("Text") + + txt:settextf("%d.", goal:GetPriority()) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if goal == nil then return end + if goal:IsAchieved() or goal:IsVacuous() then return end -- completed goals cant be updated + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + goal:SetPriority(goal:GetPriority() + 1) + self:GetParent():GetParent():playcommand("UpdateGoalList") + elseif params.event == "DeviceButton_right mouse button" then + goal:SetPriority(goal:GetPriority() - 1) + self:GetParent():GetParent():playcommand("UpdateGoalList") + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Difficulty", + InitCommand = function(self) + self:valign(0) + self:x(diffX) + self:zoom(goalLine1TextSize) + self:maxwidth(diffW / goalLine1TextSize - textzoomFudge) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + if goal == nil then return end + + if goalSteps ~= nil then + self:settextf("%s", getShortDifficulty(goalSteps:GetDifficulty())) + self:diffuse(colorByDifficulty(goalSteps:GetDifficulty())) + else + self:settext("") + end + end + }, + Def.Quad { + Name = "Div1", + InitCommand = function(self) + self:valign(0) + self:x(div1X) + self:y(-dividerUpwardBump) + self:zoomto(actuals.ItemDividerThickness, actuals.ItemDividerLength) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:valign(0) + + self:x(rateX) + txt:zoom(goalLine1TextSize) + txt:maxwidth(rateW / goalLine1TextSize - textzoomFudge) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(rateW, txt:GetZoomedHeight()) + bg:y(txt:GetZoomedHeight() / 2) + end, + UpdateTextCommand = function(self) + if goal == nil then return end + local txt = self:GetChild("Text") + + local ratestr = string.format("%.2f", goal:GetRate()):gsub("%.?0$", "") .. "x" + txt:settext(ratestr) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if goal == nil then return end + if goal:IsAchieved() then return end -- completed goals cant be updated + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + goal:SetRate(goal:GetRate() + 0.05) + self:GetParent():GetParent():playcommand("UpdateGoalList") + elseif params.event == "DeviceButton_right mouse button" then + goal:SetRate(goal:GetRate() - 0.05) + self:GetParent():GetParent():playcommand("UpdateGoalList") + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + Def.Quad { + Name = "Div2", + InitCommand = function(self) + self:valign(0) + self:x(div2X) + self:y(-dividerUpwardBump) + self:zoomto(actuals.ItemDividerThickness, actuals.ItemDividerLength) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Percentage", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:valign(0) + + self:x(percentX) + txt:zoom(goalLine1TextSize) + txt:maxwidth(percentW / goalLine1TextSize - textzoomFudge) + txt:settext(" ") + bg:zoomto(percentW, txt:GetZoomedHeight()) + bg:y(txt:GetZoomedHeight() / 2) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + if goal == nil then return end + local txt = self:GetChild("Text") + + local finalstr = "" + + local perc = notShit.round(goal:GetPercent() * 100000) / 1000 + local percStr = "" + if perc <= 99 or perc == 100 then + percStr = string.format("%5.2f", perc) + elseif (perc < 99.8) then + percStr = string.format("%5.2f", perc) + else + percStr = string.format("%5.3f", perc) + end + + local pb = goal:GetPBUpTo() + local pbStr = "" + if pb then + local pbperc = notShit.round(pb:GetWifeScore() * 100000) / 1000 + if pbperc <= 99 or pbperc == 100 then + pbStr = string.format("%5.2f", pbperc) + elseif (perc < 99.8) then + pbStr = string.format("%5.2f", pbperc) + else + pbStr = string.format("%5.3f", pbperc) + end + + local rstr = "" + if pb:GetMusicRate() < goal:GetRate() then + rstr = string.format(" %5.2f", pb:GetMusicRate()):gsub("%.?0$", "") .. "x" + end + -- these gsubs get rid of right trailing 0s and . + finalstr = string.format("%s (%s%s)", percStr:gsub("%.?0+$", "") .. "%", pbStr:gsub("%.?0+$", "") .. "%", rstr) + else + -- these gsubs get rid of right trailing 0s and . + finalstr = string.format("%s", percStr:gsub("%.?0+$", "") .. "%") + end + + txt:settext(finalstr) + txt:diffuse(byAchieved(goal)) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if goal == nil then return end + if goal:IsAchieved() then return end -- completed goals cant be updated + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + goal:SetPercent(goal:GetPercent() + 0.01) + self:GetParent():GetParent():playcommand("UpdateGoalList") + elseif params.event == "DeviceButton_right mouse button" then + goal:SetPercent(goal:GetPercent() - 0.01) + self:GetParent():GetParent():playcommand("UpdateGoalList") + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + Def.Quad { + Name = "Div3", + InitCommand = function(self) + self:valign(0) + self:x(div3X) + self:y(-dividerUpwardBump) + self:zoomto(actuals.ItemDividerThickness, actuals.ItemDividerLength) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end + }, + LoadFont("Common Normal") .. { + Name = "MSD", + InitCommand = function(self) + self:valign(0) + self:x(msdX) + self:zoom(goalLine1TextSize) + -- the trashcan intrudes in this area so dont let them overlap + self:maxwidth(msdW / goalLine1TextSize - textzoomFudge) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + if goal == nil then return end + + if goalSteps ~= nil then + local msd = goalSteps:GetMSD(goal:GetRate(), 1) + self:settextf("%5.1f", msd) + self:diffuse(colorByMSD(msd)) + else + self:settext("??") + self:diffuse(color("1,1,1,1")) + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "deleteGoal")) .. { + Name = "DeleteGoal", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(msdX + msdW/2) + self:zoomto(actuals.IconWidth, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateTextCommand = function(self) + if goal == nil then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Delete Goal") + TOOLTIP:Show() + else + self:diffusealpha(1) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if goal == nil then return end + + if params.event == "DeviceButton_left mouse button" then + -- delete goal and then refresh the list + goal:Delete() + local pagebefore = page + -- this updates the list that comes from resetting the goal table + -- (if you dont do this then delete a goal twice you crash) + profile:SetFromAll() + resortGoals() + page = clamp(pagebefore, 1, maxPage) + self:GetParent():GetParent():playcommand("UpdateGoalList") + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:SetText("Delete Goal") + TOOLTIP:Show() + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + self:diffusealpha(1) + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "Name", + InitCommand = function(self) + self:valign(0):halign(0) + self:x(prioX + prioW/2) + self:y(actuals.ItemLowerLineUpperGap) + self:zoom(goalLine2TextSize) + self:maxwidth((div2X - prioX - prioW/2) / goalLine2TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + self:diffusealpha(1) + local ck = goal:GetChartKey() + local wheel = SCREENMAN:GetTopScreen():GetChild("WheelFile") + if wheel then + setMusicRate(goal:GetRate()) + wheel:playcommand("FindSong", {chartkey = ck}) + end + end + end, + UpdateTextCommand = function(self) + if goal == nil then return end + + local sname = "" + if goalSteps ~= nil then + sname = SONGMAN:GetSongByChartKey(goal:GetChartKey()):GetDisplayMainTitle() + else + sname = goal:GetChartKey() + end + + self:settextf("%s", sname) + end + }, + LoadFont("Common Normal") .. { + Name = "Date", + InitCommand = function(self) + self:valign(0) + self:x(percentX) + self:y(actuals.ItemLowerLineUpperGap) + self:zoom(goalLine2TextSize) + self:maxwidth(percentW / goalLine2TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + UpdateTextCommand = function(self) + if goal == nil then return end + + local when = "" + local status = goal:IsAchieved() and "Achieved" or (goal:IsVacuous() and "Vacuous" or "Set") + + if status == "Achieved" then + when = extractDateFromDateString(goal:WhenAchieved()) + elseif status == "Vacuous" then + when = "- Already Beat" + else + -- Created/Set + when = extractDateFromDateString(goal:WhenAssigned()) + end + + self:settextf("%s %s", status, when) + end + } + } + end + + local function goalChoices() + -- keeping track of which choices are on at any moment (keys are indices, values are true/false/nil) + -- starting with priority sort on + local activeChoices = {[1] = true} + + -- identify each choice using this table + -- Name: The name of the choice (NOT SHOWN TO THE USER) + -- Type: Toggle/Exclusive/Tap + -- Toggle - This choice can be clicked multiple times to scroll through choices + -- Exclusive - This choice is one out of a set of Exclusive choices. Only one Exclusive choice can be picked at once + -- Tap - This choice can only be pressed (if visible by Condition) and will only run TapFunction at that time + -- Display: The string the user sees. One option for each choice must be given if it is a Toggle choice + -- Condition: A function that returns true or false. Determines if the choice should be visible or not + -- IndexGetter: A function that returns an index for its status, according to the Displays set + -- TapFunction: A function that runs when the button is pressed + local choiceDefinitions = { + { -- Sort by Priority + Name = "prioritysort", + Type = "Exclusive", + Display = {"Priority"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + if sortMode == "Priority" then + sortAscending = not sortAscending + else + sortMode = "Priority" + sortAscending = defaultAscending + end + resortGoals() + end, + }, + { -- Sort by Rate + Name = "ratesort", + Type = "Exclusive", + Display = {"Rate"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + if sortMode == "Rate" then + sortAscending = not sortAscending + else + sortMode = "Rate" + sortAscending = defaultAscending + end + resortGoals() + end, + }, + { -- Sort by MSD + Name = "msdsort", + Type = "Exclusive", + Display = {"MSD"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + if sortMode == "MSD" then + sortAscending = not sortAscending + else + sortMode = "MSD" + sortAscending = defaultAscending + end + resortGoals() + end, + }, + { -- Sort by Song (Song + ChartKey) + Name = "namesort", + Type = "Exclusive", + Display = {"Name"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + if sortMode == "Name" then + sortAscending = not sortAscending + else + sortMode = "Name" + sortAscending = defaultAscending + end + resortGoals() + end, + }, + { -- Sort by Set Date + Name = "datesort", + Type = "Exclusive", + Display = {"Date"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + if sortMode == "Date" then + sortAscending = not sortAscending + else + sortMode = "Date" + sortAscending = defaultAscending + end + resortGoals() + end, + }, + { -- New Goal on current Chart + Name = "newgoal", + Type = "Tap", + Display = {"New Goal"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + local steps = GAMESTATE:GetCurrentSteps() + if steps ~= nil then + local ck = steps:GetChartKey() + local success = profile:AddGoal(ck) + -- success means goal was added + -- false means it was a duplicate or something else weird maybe + if success then + -- this will load the new goal into the list and keep the page where it already was + local pagebefore = page + profile:SetFromAll() + resortGoals() + page = clamp(pagebefore, 1, maxPage) + end + end + end, + }, + { -- Toggle between All, Completed, and Incomplete Goals + Name = "filtergoals", + Type = "Toggle", + Display = {"Showing All", "Showing Complete", "Showing Incomplete"}, + IndexGetter = function() + if visibleGoalType == "All" then + return 1 + elseif visibleGoalType == "Complete" then + return 2 + elseif visibleGoalType == "Incomplete" then + return 3 + else + return 1 + end + end, + Condition = function() return true end, + TapFunction = function() + if visibleGoalType == "All" then + visibleGoalType = "Complete" + elseif visibleGoalType == "Complete" then + visibleGoalType = "Incomplete" + elseif visibleGoalType == "Incomplete" then + visibleGoalType = "All" + else + visibleGoalType = "All" + end + resortGoals() + end, + }, + } + + local function createChoice(i) + local definition = choiceDefinitions[i] + local displayIndex = 1 + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ChoiceButton_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceDefinitions) * (i-1) + (actuals.Width / #choiceDefinitions / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceDefinitions / choiceTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceDefinitions, actuals.UpperLipHeight) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- update index + displayIndex = definition.IndexGetter() + + -- update visibility by condition + if definition.Condition() then + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + else + self:diffusealpha(0) + end + + if activeChoices[i] then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.75)) + else + txt:strokecolor(color("0,0,0,0")) + end + + -- update display + txt:settext(definition.Display[displayIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- exclusive choices cause activechoices to be forced to this one + if definition.Type == "Exclusive" then + activeChoices = {[i]=true} + else + -- uhh i didnt implement any other type that would ... be used for.. this + end + + -- run the tap function + if definition.TapFunction ~= nil then + definition.TapFunction() + end + self:GetParent():GetParent():playcommand("UpdateGoalList") + self:GetParent():playcommand("UpdateText") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.UpperLipHeight / 2) + end, + } + + for i = 1, #choiceDefinitions do + t[#t+1] = createChoice(i) + end + + return t + end + + local t = Def.ActorFrame { + Name = "GoalListFrame", + BeginCommand = function(self) + goalListFrame = self + resortGoals() + self:playcommand("UpdateGoalList") + self:playcommand("UpdateText") + end, + UpdateGoalsTabCommand = function(self) + page = 1 + self:playcommand("UpdateGoalList") + self:playcommand("UpdateText") + end, + UpdateGoalListCommand = function(self) + -- in case tooltip is stuck for some reason + TOOLTIP:Hide() + end, + + goalChoices(), + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + self:GetParent():playcommand("UpdateGoalList") + end + end + }, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.PageNumberUpperGap) + self:zoom(pageTextSize) + -- oddly precise max width but this should fit with the original size + self:maxwidth(actuals.Width * 0.14 / pageTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateGoalListCommand = function(self) + local lb = clamp((page-1) * (goalItemCount) + 1, 0, #goalTable) + local ub = clamp(page * goalItemCount, 0, #goalTable) + self:settextf("%d-%d/%d", lb, ub, #goalTable) + end + } + } + + for i = 1, goalItemCount do + t[#t+1] = goalListItem(i) + end + + return t +end + +t[#t+1] = Def.Quad { + Name = "UpperLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.UpperLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "LipTop", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.LipSeparatorThickness) + self:diffusealpha(0.3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = goalList() + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/playlists.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/playlists.lua new file mode 100644 index 0000000000..dbb136c95b --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/playlists.lua @@ -0,0 +1,1061 @@ +local focused = false +local inPlaylistDetails = false +local t = Def.ActorFrame { + Name = "PlaylistsPageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.playliststabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + focused = true + inPlaylistDetails = false + self:playcommand("UpdatePlaylistsTab") + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + focused = false + end + end + end +} + +local ratios = { + UpperLipHeight = 43 / 1080, + LipSeparatorThickness = 2 / 1080, + + PageTextRightGap = 33 / 1920, -- right of frame, right of text + PageNumberUpperGap = 48 / 1080, -- bottom of upper lip to top of text + + ItemListUpperGap = 35 / 1080, -- bottom of upper lip to top of topmost item + ItemAllottedSpace = 405 / 1080, -- top of topmost item to top of bottommost item + ItemLowerLineUpperGap = 30 / 1080, -- top of top line to top of bottom line + ItemDividerThickness = 3 / 1080, -- you know what it is (i hope) (ok its based on height so things are consistent-ish) + ItemDividerLength = 26 / 1080, + + ItemIndexLeftGap = 11 / 1920, -- left edge of frame to left edge of number + ItemIndexWidth = 38 / 1920, -- left edge of number to uhh nothing + IconWidth = 18 / 1920, -- for the trash thing + IconHeight = 21 / 1080, + + -- pertains to the playlist detail pages, not the playlist display + DetailPageLeftGap = 743 / 1920, -- left edge to left edge of text + DetailPageUpperGap = 52 / 1080, + DetailItemAllottedSpace = 440 / 1080, +} + +local actuals = { + UpperLipHeight = ratios.UpperLipHeight * SCREEN_HEIGHT, + LipSeparatorThickness = ratios.LipSeparatorThickness * SCREEN_HEIGHT, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + PageNumberUpperGap = ratios.PageNumberUpperGap * SCREEN_HEIGHT, + ItemListUpperGap = ratios.ItemListUpperGap * SCREEN_HEIGHT, + ItemAllottedSpace = ratios.ItemAllottedSpace * SCREEN_HEIGHT, + ItemLowerLineUpperGap = ratios.ItemLowerLineUpperGap * SCREEN_HEIGHT, + ItemDividerThickness = ratios.ItemDividerThickness * SCREEN_HEIGHT, + ItemDividerLength = ratios.ItemDividerLength * SCREEN_HEIGHT, + ItemIndexLeftGap = ratios.ItemIndexLeftGap * SCREEN_WIDTH, + ItemIndexWidth = ratios.ItemIndexWidth * SCREEN_WIDTH, + IconWidth = ratios.IconWidth * SCREEN_WIDTH, + IconHeight = ratios.IconHeight * SCREEN_HEIGHT, + DetailPageLeftGap = ratios.DetailPageLeftGap * SCREEN_WIDTH, + DetailPageUpperGap = ratios.DetailPageUpperGap * SCREEN_HEIGHT, + DetailItemAllottedSpace = ratios.DetailItemAllottedSpace * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +-- playlist list sizing +local itemLine1TextSize = 0.85 +local itemLine2TextSize = 0.75 +local pageTextSize = 0.7 + +-- playlist detail sizing +local itemIndexSize = 0.9 +local nameTextSize = 0.9 +local rateTextSize = 0.9 +local msdTextSize = 0.9 +local diffTextSize = 0.9 +local detailPageTextSize = 0.7 + +local choiceTextSize = 0.7 +local buttonHoverAlpha = 0.6 +local textzoomFudge = 5 + +local itemListAnimationSeconds = 0.05 + +-- for accessibility concerns, make buttons a bit bigger than the text they cover +local textButtonHeightFudgeScalarMultiplier = 1.6 + +-- the entire playlist display ActorFrame +local function playlistList() + -- modifiable parameters + local itemCount = 7 + local detailItemCount = 15 + + -- internal var storage + local page = 1 + local maxPage = 1 + local playlistListFrame = nil + local playlistTable = {} + + local displayListFrame = nil + local detailPage = 1 + local detailMaxPage = 1 + + local function updatePlaylists() + playlistTable = SONGMAN:GetPlaylists() + maxPage = math.ceil(#playlistTable / itemCount) + + table.sort( + playlistTable, + function(a, b) + -- this sorts the playlists using the typical alphabetical order we are all familiar with + local aname = WHEELDATA.makeSortString(a:GetName()) + local bname = WHEELDATA.makeSortString(b:GetName()) + return aname < bname + end + ) + end + + local function movePage(n) + if inPlaylistDetails then + if detailMaxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (detailPage + n) % (detailMaxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or detailMaxPage + end + detailPage = nn + + if displayListFrame then + displayListFrame:playcommand("UpdateItemList") + end + else + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + if playlistListFrame then + playlistListFrame:playcommand("UpdateItemList") + end + end + end + + local function playlistItem(i) + local index = i + local playlist = nil + + -- theres a lot going on here i just wanted to write down vars representing math so its a little clearer for everyone + -- i should have done this kind of thing in more places but ... + local itemWidth = actuals.Width * 0.84 -- this 0.84 is balanced with the multiplier for the page number width + local indX = actuals.ItemIndexLeftGap + local indW = actuals.ItemIndexWidth + local remainingWidth = itemWidth - indW - indX + local nameX = indX + indW -- halign 0 + local deleteX = itemWidth - indX -- halign 1 + local playX = deleteX - actuals.IconWidth - actuals.IconWidth/2 -- halign 1 + local nameW = remainingWidth - (actuals.IconWidth * 2.5) - indX -- area between index and leftmost icon + + return Def.ActorFrame { + Name = "PlaylistItemFrame_"..i, + InitCommand = function(self) + self:y((actuals.ItemAllottedSpace / (itemCount - 1)) * (i-1) + actuals.ItemListUpperGap + actuals.UpperLipHeight) + end, + UpdateItemListCommand = function(self) + index = (page - 1) * itemCount + i + playlist = playlistTable[index] + self:finishtweening() + self:diffusealpha(0) + if playlist ~= nil then + self:playcommand("UpdateText") + self:smooth(itemListAnimationSeconds * i) + self:diffusealpha(1) + end + end, + + LoadFont("Common Normal") .. { + Name = "Index", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(indX) + self:zoom(itemLine1TextSize) + self:maxwidth(indW / itemLine1TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateTextCommand = function(self) + if playlist == nil then return end + self:settextf("%d.", index) + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(nameX) + self:zoom(itemLine1TextSize) + self:maxwidth(nameW / itemLine1TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateTextCommand = function(self) + if playlist == nil then return end + self:settext(playlist:GetName()) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if playlist == nil then return end + self:diffusealpha(1) + SONGMAN:SetActivePlaylist(playlist:GetName()) + MESSAGEMAN:Broadcast("OpenPlaylistDetails", {playlist = playlist}) + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + + self:diffusealpha(1) + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "showReplay")) .. { + Name = "PlayCourse", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(playX) + self:zoomto(actuals.IconWidth, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateTextCommand = function(self) + if playlist == nil then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Play As Course") + TOOLTIP:Show() + else + self:diffusealpha(1) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if playlist == nil then return end + SONGMAN:SetActivePlaylist(playlist:GetName()) + SCREENMAN:GetTopScreen():StartPlaylistAsCourse(playlist:GetName()) + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:SetText("Play As Course") + TOOLTIP:Show() + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + self:diffusealpha(1) + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "deleteGoal")) .. { + Name = "DeletePlaylist", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(deleteX) + self:zoomto(actuals.IconWidth, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateTextCommand = function(self) + -- dont allow deleting the Favorites playlist + -- this breaks so many things + if playlist == nil or playlist:GetName() == "Favorites" then + self:diffusealpha(0) + if isOver(self) then + TOOLTIP:Hide() + end + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Delete Playlist\n(Triggers a Save!)") + TOOLTIP:Show() + else + self:diffusealpha(1) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if playlist == nil then return end + if playlist:GetName() == "Favorites" then + -- block the ability to delete the favorites playlist here too + else + -- this will trigger a save + SONGMAN:DeletePlaylist(playlist:GetName()) + updatePlaylists() + -- self - item - itemlist - playlist tab + self:GetParent():GetParent():GetParent():playcommand("UpdatePlaylistsTab") + TOOLTIP:Hide() + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:SetText("Delete Playlist\n(Triggers a Save!)") + TOOLTIP:Show() + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + self:diffusealpha(1) + end + }, + LoadFont("Common Normal") .. { + Name = "Count", + InitCommand = function(self) + self:valign(0):halign(0) + self:x(nameX) + self:y(actuals.ItemLowerLineUpperGap) + self:zoom(itemLine2TextSize) + self:maxwidth((itemWidth - nameX) / 2 / itemLine2TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + UpdateTextCommand = function(self) + if playlist == nil then return end + self:settextf("Number of charts: %d", playlist:GetNumCharts()) + end + }, + LoadFont("Common Normal") .. { + Name = "Average", + InitCommand = function(self) + self:valign(0):halign(1) + self:x(itemWidth - indX) + self:y(actuals.ItemLowerLineUpperGap) + self:zoom(itemLine2TextSize) + self:maxwidth((itemWidth - nameX) / 2 / itemLine2TextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + UpdateTextCommand = function(self) + if playlist == nil then return end + self:settextf("(Average MSD %5.2f)", playlist:GetAverageRating()) + end + } + } + end + + -- functionally created frame for only displaying contents of a playlist + local function detailPageFrame() + -- playlists keep track of basically just chartkeys and the songs for them might not be loaded + -- we do our best to care about that ... kind of + local playlist = nil + local keylist = {} -- this is a list of keys + local chartlist = {} -- this is a list of "Chart" which isnt a Steps + + local function detailItem(i) + local chart = nil + local chartkey = nil + local stepsloaded = false + local index = i + + local itemWidth = actuals.Width + local indX = actuals.ItemIndexLeftGap + local indW = actuals.ItemIndexWidth + local remainingWidth = itemWidth - indW - indX + local nameX = indX + indW -- halign 0 + local nameW = remainingWidth / 8 * 5 + local rateX = nameX + nameW + local rateW = remainingWidth / 12 * 1 + local msdX = rateX + rateW + local msdW = remainingWidth / 8 * 1 + local diffX = msdX + msdW + local diffW = remainingWidth / 16 * 1 + local deleteX = itemWidth - indX - diffW -- halign 0 + + return Def.ActorFrame { + Name = "ChartItem_"..i, + InitCommand = function(self) + self:y((actuals.DetailItemAllottedSpace / (detailItemCount)) * (i-1) + actuals.ItemListUpperGap) + end, + SetChartCommand = function(self) + index = (detailPage - 1) * detailItemCount + i + -- make the assumption that these lists are the same length and if one is nil the other is too + chart = chartlist[index] + chartkey = keylist[index] + self:finishtweening() + self:diffusealpha(0) + if chart ~= nil then + self:smooth(itemListAnimationSeconds * i) + self:diffusealpha(1) + end + end, + + LoadFont("Common Normal") .. { + Name = "Index", + InitCommand = function(self) + self:valign(0):halign(0) + self:x(indX) + self:zoom(itemIndexSize) + self:maxwidth((indW) / itemIndexSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetChartCommand = function(self) + if chart ~= nil then + self:settextf("%d.", index) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Name", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + self:x(nameX) + + txt:halign(0):valign(0) + bg:halign(0):valign(0) + -- this upwards bump fixes font related positioning + -- the font has a baseline which pushes it downward by some bit + -- this corrects the bg so that the hover is not wrong as a result + bg:y(-1) + + txt:zoom(nameTextSize) + txt:maxwidth(nameW / nameTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomy(actuals.ItemAllottedSpace / detailItemCount) + end, + SetChartCommand = function(self) + if chart ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + local name = chart:GetSongTitle() + txt:settext(name) + + bg:zoomx(txt:GetZoomedWidth()) + + -- if mouse is currently hovering + if isOver(bg) and chart:IsLoaded() then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- find song on click (even if filtered) + local w = SCREENMAN:GetTopScreen():GetChild("WheelFile") + if w ~= nil then + if chart:IsLoaded() then + setMusicRate(chart:GetRate()) + w:playcommand("FindSong", {chartkey = chartkey}) + else + -- not loaded - do nothing + end + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" and chart ~= nil and chart:IsLoaded() then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + self:x(msdX - textzoomFudge) + + txt:halign(1):valign(0) + bg:halign(1):valign(0) + -- this upwards bump fixes font related positioning + -- the font has a baseline which pushes it downward by some bit + -- this corrects the bg so that the hover is not wrong as a result + bg:y(-1) + + txt:zoom(rateTextSize) + txt:maxwidth(rateW / rateTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomy(actuals.ItemAllottedSpace / detailItemCount) + end, + SetChartCommand = function(self) + if chart ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- cant change rate in the favorites playlist + if playlist:GetName() == "Favorites" then + self:diffusealpha(0) + return + else + self:diffusealpha(1) + end + + local rate = chart:GetRate() + local ratestring = string.format("%.2f", rate):gsub("%.?0+$", "") .. "x" + txt:settext(ratestring) + bg:zoomx(txt:GetZoomedWidth()) + + -- if mouse is currently hovering + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + chart:ChangeRate(0.05) + else + chart:ChangeRate(-0.05) + end + self:GetParent():GetParent():playcommand("UpdateDetailDisplay") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + LoadFont("Common Normal") .. { + Name = "MSD", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(msdX) + self:zoom(msdTextSize) + self:maxwidth(msdW / msdTextSize - textzoomFudge) + end, + SetChartCommand = function(self) + if chart ~= nil then + local msd = 0 + if chart:IsLoaded() then + local steps = SONGMAN:GetStepsByChartKey(chartkey) + msd = steps:GetMSD(chart:GetRate(), 1) + end + self:settextf("%05.2f", msd) + self:diffuse(colorByMSD(msd)) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Difficulty", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(diffX) + self:zoom(diffTextSize) + self:maxwidth(diffW / diffTextSize - textzoomFudge) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetChart") + end, + SetChartCommand = function(self) + if chart ~= nil then + local diff = nil + if chart:IsLoaded() then + local steps = SONGMAN:GetStepsByChartKey(chartkey) + diff = steps:GetDifficulty() + else + diff = chart:GetDifficulty() + end + self:settext(getShortDifficulty(diff)) + self:diffuse(colorByDifficulty(diff)) + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "deleteGoal")) .. { + Name = "DeleteChart", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(deleteX) + self:zoomto(actuals.IconWidth, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + SetChartCommand = function(self) + -- dont allow deleting from the Favorites playlist + -- this breaks so many things + if chart == nil or playlist:GetName() == "Favorites" then + self:diffusealpha(0) + if isOver(self) then + TOOLTIP:Hide() + end + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Delete Chart\n(Triggers a Save!)") + TOOLTIP:Show() + else + self:diffusealpha(1) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if playlist == nil or playlist:GetName() == "Favorites" then return end + -- this will trigger a save + playlist:DeleteChart(index) + updatePlaylists() + self:GetParent():GetParent():playcommand("UpdateDetailDisplay") + TOOLTIP:Hide() + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:SetText("Delete Chart\n(Triggers a Save!)") + TOOLTIP:Show() + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + self:diffusealpha(1) + end + } + } + end + + local t = Def.ActorFrame { + Name = "DetailPageFrame", + InitCommand = function(self) + self:y(actuals.DetailPageUpperGap) + self:diffusealpha(0) + end, + BeginCommand = function(self) + displayListFrame = self + end, + UpdateDetailDisplayCommand = function(self, params) + -- not updating page here because we can use this command from any page + -- but do update max page + playlist = SONGMAN:GetActivePlaylist() + if playlist ~= nil then + keylist = playlist:GetChartkeys() + chartlist = playlist:GetAllSteps() + self:GetChild("PageText"):diffusealpha(1) + else + keylist = {} + chartlist = {} + self:GetChild("PageText"):diffusealpha(0) + end + detailMaxPage = math.ceil(#chartlist / detailItemCount) + -- just in case max page did change ... clamp the page but dont move it otherwise + detailPage = clamp(detailPage, 1, detailMaxPage) + self:playcommand("UpdateItemList") + end, + UpdateItemListCommand = function(self) + if inPlaylistDetails then + self:diffusealpha(1) + self:playcommand("SetChart") + else + self:diffusealpha(0) + end + end, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.DetailPageLeftGap) + self:zoom(detailPageTextSize) + -- oddly precise max width but this should fit with the original size + self:maxwidth(actuals.Width / 3 / detailPageTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateItemListCommand = function(self) + local lb = clamp((detailPage-1) * (detailItemCount) + 1, 0, #chartlist) + local ub = clamp(detailPage * detailItemCount, 0, #chartlist) + self:settextf("%d-%d/%d", lb, ub, #chartlist) + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "PlaylistName", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(0) + bg:halign(0):valign(0) + self:x(actuals.Width - actuals.DetailPageLeftGap) + txt:zoom(detailPageTextSize) + -- oddly precise max width but this should fit with the original size + txt:maxwidth(actuals.Width / 3 * 2 / detailPageTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + UpdateItemListCommand = function(self) + if playlist ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:settext(playlist:GetName()) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + -- cant rename the favorites playlist + if params.update == "OnMouseDown" and playlist:GetName() ~= "Favorites" then + if params.event == "DeviceButton_left mouse button" then + renamePlaylistDialogue(playlist:GetName()) + self:diffusealpha(1) + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" and playlist:GetName() ~= "Favorites" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + PlaylistRenamedMessageCommand = function(self, params) + if params and params.success then + ms.ok("Successfully renamed playlist") + self:playcommand("UpdateItemList") + else + ms.ok("Failed to rename playlist") + end + end, + }, + } + + for i = 1, detailItemCount do + t[#t+1] = detailItem(i) + end + + return t + end + + local function tabChoices() + -- keeping track of which choices are on at any moment (keys are indices, values are true/false/nil) + local activeChoices = {} + + -- identify each choice using this table + -- Name: The name of the choice (NOT SHOWN TO THE USER) + -- Type: Toggle/Exclusive/Tap + -- Toggle - This choice can be clicked multiple times to scroll through choices + -- Exclusive - This choice is one out of a set of Exclusive choices. Only one Exclusive choice can be picked at once + -- Tap - This choice can only be pressed (if visible by Condition) and will only run TapFunction at that time + -- Display: The string the user sees. One option for each choice must be given if it is a Toggle choice + -- Condition: A function that returns true or false. Determines if the choice should be visible or not + -- IndexGetter: A function that returns an index for its status, according to the Displays set + -- TapFunction: A function that runs when the button is pressed + local choiceDefinitions = { + { -- Make a new playlist or add the current chart to the opened playlist + Name = "newentry", + Type = "Tap", + Display = {"New Playlist", "Add Current Chart"}, + IndexGetter = function() + if inPlaylistDetails then + return 2 + else + return 1 + end + end, + Condition = function() + if inPlaylistDetails then + -- dont allow adding to favorites here because of weird interactions internally + if SONGMAN:GetActivePlaylist():GetName() == "Favorites" then + return false + end + end + return true + end, + TapFunction = function() + if inPlaylistDetails then + -- adding chart to playlist + local pl = SONGMAN:GetActivePlaylist() + if pl:GetName() == "Favorites" then + -- no adding to favorites this way :) + else + local steps = GAMESTATE:GetCurrentSteps() + if steps ~= nil then + -- this triggers a save + pl:AddChart(steps:GetChartKey()) + updatePlaylists() + if displayListFrame ~= nil then + displayListFrame:playcommand("UpdateDetailDisplay") + end + end + end + else + -- adding new playlist + newPlaylistDialogue() + -- this will trigger the DisplayAll Message after success (also a Profile Save) + -- this triggers nothing on failure + end + end, + }, + { -- Exit the page that lets you see inside a playlist + Name = "back", + Type = "Tap", + Display = {"Back"}, + IndexGetter = function() return 1 end, + Condition = function() return inPlaylistDetails end, + TapFunction = function() + MESSAGEMAN:Broadcast("ClosePlaylistDetails") + end, + }, + } + + local function createChoice(i) + local definition = choiceDefinitions[i] + local displayIndex = 1 + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ChoiceButton_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceDefinitions) * (i-1) + (actuals.Width / #choiceDefinitions / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceDefinitions / choiceTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceDefinitions, actuals.UpperLipHeight) + self:playcommand("UpdateText") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- update index + displayIndex = definition.IndexGetter() + + -- update visibility by condition + if definition.Condition() then + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + else + self:diffusealpha(0) + end + + if activeChoices[i] then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.75)) + else + txt:strokecolor(color("0,0,0,0")) + end + + -- update display + txt:settext(definition.Display[displayIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- exclusive choices cause activechoices to be forced to this one + if definition.Type == "Exclusive" then + activeChoices = {[i]=true} + else + -- uhh i didnt implement any other type that would ... be used for.. this + end + + -- run the tap function + if definition.TapFunction ~= nil then + definition.TapFunction() + end + self:GetParent():GetParent():playcommand("UpdateItemList") + self:GetParent():playcommand("UpdateText") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.UpperLipHeight / 2) + end, + } + + for i = 1, #choiceDefinitions do + t[#t+1] = createChoice(i) + end + + return t + end + + local t = Def.ActorFrame { + Name = "PlaylistListFrame", + BeginCommand = function(self) + playlistListFrame = self + updatePlaylists() + self:playcommand("UpdateItemList") + self:playcommand("UpdateText") + end, + UpdatePlaylistsTabCommand = function(self) + page = 1 + inPlaylistDetails = false + self:playcommand("ClosePlaylistDetails") + self:playcommand("UpdateItemList") + self:playcommand("UpdateText") + end, + UpdateItemListCommand = function(self) + -- in case tooltip gets stuck + TOOLTIP:Hide() + end, + OpenPlaylistDetailsMessageCommand = function(self) + inPlaylistDetails = true + detailPage = 1 + self:GetChild("Choices"):playcommand("UpdateText") + + if displayListFrame ~= nil then + displayListFrame:diffusealpha(1) + displayListFrame:z(10) + displayListFrame:playcommand("UpdateDetailDisplay") + end + + local itemframe = self:GetChild("ItemListFrame") + itemframe:diffusealpha(0) + self:GetChild("PageText"):diffusealpha(0) + end, + ClosePlaylistDetailsMessageCommand = function(self) + inPlaylistDetails = false + self:GetChild("Choices"):playcommand("UpdateText") + + if displayListFrame ~= nil then + displayListFrame:diffusealpha(0) + displayListFrame:z(-10) + displayListFrame:playcommand("UpdateDetailDisplay") + end + + local itemframe = self:GetChild("ItemListFrame") + itemframe:diffusealpha(1) + self:GetChild("PageText"):diffusealpha(1) + end, + DisplayAllMessageCommand = function(self) + -- this should only trigger if a new playlist was successfully made + -- if not ... uhhh..... ??? + updatePlaylists() + self:playcommand("UpdatePlaylistsTab") + end, + DisplaySinglePlaylistMessageCommand = function(self) + -- really hate this garbage naming convention for the message + -- this is used for displaying a single playlist in til death but i dont like it + updatePlaylists() + self:playcommand("UpdateText") + self:playcommand("UpdateDetailDisplay") + end, + + tabChoices(), + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + self:GetParent():playcommand("UpdateItemList") + end + end + }, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.PageNumberUpperGap) + self:zoom(pageTextSize) + -- oddly precise max width but this should fit with the original size + self:maxwidth(actuals.Width * 0.14 / pageTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateItemListCommand = function(self) + local lb = clamp((page-1) * (itemCount) + 1, 0, #playlistTable) + local ub = clamp(page * itemCount, 0, #playlistTable) + self:settextf("%d-%d/%d", lb, ub, #playlistTable) + end + }, + detailPageFrame() + } + + -- doing this in a weird way partly out of laziness but also necessity + -- want to wrap all the playlist displays into a single frame separate from the overall frame + -- so we can control between showing those and the detail page quickly + local tt = Def.ActorFrame {Name = "ItemListFrame"} + for i = 1, itemCount do + tt[#tt+1] = playlistItem(i) + end + t[#t+1] = tt + + return t +end + +t[#t+1] = Def.Quad { + Name = "UpperLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.UpperLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "LipTop", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.LipSeparatorThickness) + self:diffusealpha(0.3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = playlistList() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/profile.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/profile.lua new file mode 100644 index 0000000000..04c684eafb --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/profile.lua @@ -0,0 +1,1390 @@ +local focused = false +local t = Def.ActorFrame { + Name = "ProfilePageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.profiletabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + focused = true + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + focused = false + end + end + end +} + +local ratios = { + UpperLipHeight = 43 / 1080, -- frame edge to lip edge + LipSeparatorThickness = 2 / 1080, + + MainIndicatorLeftGap = 12 / 1920, -- left edge to left edge of text, this is the online/local thing + MainIndicatorUpperGap = 48 / 1080, + PageTextRightGap = 33 / 1920, -- right of frame, right of text + ItemIndexMargin = 29 / 1920, -- left edge to center of the indices + ItemSSRRightAlignLeftGap = 129 / 1920, -- left edge to right edge of SSR + ItemSSRWidth = 70 / 1920, -- rough estimation + ItemSongNameLeftGap = 146 / 1920, -- left edge to song name + ItemSongNameWidth = 375 / 1920, -- rough estimation + ItemRateRightAlignLeftGap = 569 / 1920, -- left edge to right edge of rate + ItemRateWidth = 48 / 1920, -- rough estimaion, lines up with the song name width + ItemPercentRightAlignLeftGap = 691 / 1920, -- left edge to right edge of percent + ItemPercentWidth = 115 / 1920, -- slightly less than the distance to the rate + ItemDiffRightAlignLeftGap = 748 / 1920, -- left edge to right edge of diff + ItemDiffWidth = 50 / 1920, -- slightly less than the distance to the percent + + ItemListUpperGap = 87 / 1080, -- top of frame to top of first item + ItemSpacing = 29 / 1080, -- from top of item to top of next item + ItemAllottedSpace = 470 / 1080, -- from first item to top of bottom lip + + -- Overall tab is like a subtab, partially separate measurements + InfoUpperMargin = 87 / 1080, -- this appears to be the gap for everything from top of frame to top of everything + AvatarLeftGap = 19 / 1920, -- left edge to avatar + AvatarSize = 109 / 1080, -- height based for squareness + -- positions are relative to the avatar to work with all sizes (complexification) + NameInfoLeftMargin = 8 / 1920, -- avatar to left edge of text + NameInfoLargeLineSpacing = 32 / 1080, -- top of top line to top of next line + NameInfoSmallerUpperGap = 150 / 1080, -- top of frame to top of third line of text + NameInfoSmallerLineSpacing = 26 / 1080, -- top of top line to top of next line + RightTextLeftGap = 414 / 1920, -- left frame edge to left edge of rightmost text + RightTextSlightOffset = 1 / 1920, -- really? + RightSmallTextUpperGap = 124 / 1080, -- top of frame to top of stream skillset + RightSmallTextAllottedSpace = 410 / 1080, -- the reference doesnt show overall so we have to position based on this to be dynamic +} + +local actuals = { + UpperLipHeight = ratios.UpperLipHeight * SCREEN_HEIGHT, + LipSeparatorThickness = ratios.LipSeparatorThickness * SCREEN_HEIGHT, + MainIndicatorLeftGap = ratios.MainIndicatorLeftGap * SCREEN_WIDTH, + MainIndicatorUpperGap = ratios.MainIndicatorUpperGap * SCREEN_HEIGHT, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + ItemIndexMargin = ratios.ItemIndexMargin * SCREEN_WIDTH, + ItemSSRRightAlignLeftGap = ratios.ItemSSRRightAlignLeftGap * SCREEN_WIDTH, + ItemSSRWidth = ratios.ItemSSRWidth * SCREEN_WIDTH, + ItemSongNameLeftGap = ratios.ItemSongNameLeftGap * SCREEN_WIDTH, + ItemSongNameWidth = ratios.ItemSongNameWidth * SCREEN_WIDTH, + ItemRateRightAlignLeftGap = ratios.ItemRateRightAlignLeftGap * SCREEN_WIDTH, + ItemRateWidth = ratios.ItemRateWidth * SCREEN_WIDTH, + ItemPercentRightAlignLeftGap = ratios.ItemPercentRightAlignLeftGap * SCREEN_WIDTH, + ItemPercentWidth = ratios.ItemPercentWidth * SCREEN_WIDTH, + ItemDiffRightAlignLeftGap = ratios.ItemDiffRightAlignLeftGap * SCREEN_WIDTH, + ItemDiffWidth = ratios.ItemDiffWidth * SCREEN_WIDTH, + ItemListUpperGap = ratios.ItemListUpperGap * SCREEN_HEIGHT, + ItemSpacing = ratios.ItemSpacing * SCREEN_HEIGHT, + ItemAllottedSpace = ratios.ItemAllottedSpace * SCREEN_HEIGHT, + InfoUpperMargin = ratios.InfoUpperMargin * SCREEN_HEIGHT, + AvatarLeftGap = ratios.AvatarLeftGap * SCREEN_WIDTH, + AvatarSize = ratios.AvatarSize * SCREEN_HEIGHT, + NameInfoLeftMargin = ratios.NameInfoLeftMargin * SCREEN_WIDTH, + NameInfoLargeLineSpacing = ratios.NameInfoLargeLineSpacing * SCREEN_HEIGHT, + NameInfoSmallerUpperGap = ratios.NameInfoSmallerUpperGap * SCREEN_HEIGHT, + NameInfoSmallerLineSpacing = ratios.NameInfoSmallerLineSpacing * SCREEN_HEIGHT, + RightTextLeftGap = ratios.RightTextLeftGap * SCREEN_WIDTH, + RightTextSlightOffset = ratios.RightTextSlightOffset * SCREEN_WIDTH, + RightSmallTextUpperGap = ratios.RightSmallTextUpperGap * SCREEN_HEIGHT, + RightSmallTextAllottedSpace = ratios.RightSmallTextAllottedSpace * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +-- the page names in the order they go +-- it happens to literally just be the skillsets including Overall +local choiceNames = ms.SkillSets + +local itemCount = 15 +local maxPages = 40 +local scoreListAnimationSeconds = 0.03 +local textListAnimationSeconds = 0.03 +-- we can potentially display every score on the profile +-- there isn't a good reason to do that or not to do that +-- so let's just default to about twice as many as we are used to +local upperBoundOfScoreCount = itemCount * maxPages + +local indicatorTextSize = 0.7 +local pageTextSize = 0.7 +local itemIndexSize = 0.9 +local ssrTextSize = 0.9 +local nameTextSize = 0.9 +local dateTextSize = 0.7 +local rateTextSize = 0.9 +local percentTextSize = 0.9 +local diffTextSize = 0.9 + +-- overall page text sizes +local largelineTextSize = 0.8 +local smalllineTextSize = 0.6 +local rightSmallTextSize = 0.7 + +local choiceTextSize = 0.7 +local buttonHoverAlpha = 0.6 +local textzoomFudge = 5 +-- movement on the Z axis to fix button related issues +-- this value doesnt change anything as long as it is smaller than 1 +-- basically z movement is necessary to tell which buttons are on top even if they are invisible +-- invisible buttons on top of other buttons will make the bottom buttons inaccessible +local overallPageZBump = 0.1 +-- this upwards bump fixes font related positioning +-- the font has a baseline which pushes it downward by some bit +-- this corrects the bg so that the hover is not wrong as a result +local scoreitembgbump = 1 + +local function createChoices() + local selectedIndex = 1 + local function createChoice(i) + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ButtonTab_"..choiceNames[i], + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceNames) * (i-1) + (actuals.Width / #choiceNames / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceNames / choiceTextSize - textzoomFudge) + txt:settext(choiceNames[i]) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceNames, actuals.LowerLipHeight) + end, + UpdateSelectedIndexCommand = function(self, params) + local txt = self:GetChild("Text") + if params ~= nil and params.index ~= nil then + selectedIndex = params.index + end + if selectedIndex == i then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.75)) + else + txt:strokecolor(color("0,0,0,0")) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + selectedIndex = i + -- change chosen skillset and regrab all scores + -- but know that overall does something different + self:GetParent():GetParent():playcommand("UpdateScores", {index = i}) + self:GetParent():playcommand("UpdateSelectedIndex") + self:GetParent():GetParent():playcommand("UpdateList") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.UpperLipHeight / 2) + self:playcommand("UpdateSelectedIndex") + end + } + for i = 1, #choiceNames do + t[#t+1] = createChoice(i) + end + return t +end + +-- functionally create the score list +-- this is basically a slimmed version of the Scores Tab +local function createList() + local page = 1 + local maxPage = 1 + local scorelistframe = nil + local isLocal = true + local chosenSkillset = ms.SkillSets[1] -- Overall default + + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + if scorelistframe then + scorelistframe:playcommand("MovedPage") + end + end + + local scores = {} + + local t = Def.ActorFrame { + Name = "ScoreListFrame", + BeginCommand = function(self) + scorelistframe = self + end, + UpdateScoresCommand = function(self, params) + page = 1 + + -- if an index is given, we are switching the chosenSkillset + if params ~= nil and params.index ~= nil and ms.SkillSets[params.index] ~= nil then + chosenSkillset = ms.SkillSets[params.index] + if chosenSkillset ~= "Overall" then + -- sort all top scores by skillset + SCOREMAN:SortSSRsForGame(ms.SkillSets[params.index]) + else + -- for overall, we dont want to sort + -- we are showing a special page instead + -- UpdateList (UpdateListCommand) should handle that + scores = {} + return + end + end + + -- if the index -1 is given, this is the recentscores index + if params ~= nil and params.index == -1 then + isLocal = true + SCOREMAN:SortRecentScoresForGame() + chosenSkillset = "Recent" + scores = {} + local sortedScore = SCOREMAN:GetRecentScoreForGame(1) + while sortedScore ~= nil and #scores < upperBoundOfScoreCount do + scores[#scores+1] = sortedScore + sortedScore = SCOREMAN:GetRecentScoreForGame(#scores + 1) + end + return + end + + if isLocal then + -- repopulate the scores list with the internally sorted score list + scores = {} + local sortedScore = SCOREMAN:GetTopSSRHighScoreForGame(1, chosenSkillset) + while sortedScore ~= nil and #scores < upperBoundOfScoreCount do + scores[#scores+1] = sortedScore + sortedScore = SCOREMAN:GetTopSSRHighScoreForGame(#scores + 1, chosenSkillset) + end + else + -- operate with dlman scores + scores = {} + local sortedScore = DLMAN:GetTopSkillsetScore(1, chosenSkillset) + while sortedScore ~= nil and #scores < upperBoundOfScoreCount do + scores[#scores+1] = sortedScore + sortedScore = DLMAN:GetTopSkillsetScore(#scores + 1, chosenSkillset) + end + end + end, + UpdateListCommand = function(self) + maxPage = math.ceil(#scores / itemCount) + + for i = 1, itemCount do + local index = (page - 1) * itemCount + i + self:GetChild("ScoreItem_"..i):playcommand("SetScore", {scoreIndex = index}) + end + if chosenSkillset == "Overall" then + self:GetChild("OverallPage"):smooth(0.2):diffusealpha(1):z(overallPageZBump) + self:GetChild("OnlineOfflineToggle"):diffusealpha(0) + self:GetChild("PageText"):diffusealpha(0) + else + self:GetChild("OverallPage"):smooth(0.2):diffusealpha(0):z(-overallPageZBump) + if DLMAN:IsLoggedIn() and chosenSkillset ~= "Recent" then + self:GetChild("OnlineOfflineToggle"):smooth(0.2):diffusealpha(1) + end + self:GetChild("PageText"):smooth(0.2):diffusealpha(1) + end + end, + MovedPageCommand = function(self) + self:playcommand("UpdateList") + end + } + + local function createItem(i) + local score = nil + local scoreIndex = i + + return Def.ActorFrame { + Name = "ScoreItem_"..i, + InitCommand = function(self) + self:y((actuals.ItemAllottedSpace / (itemCount)) * (i-1) + actuals.ItemListUpperGap) + end, + SetScoreCommand = function(self, params) + scoreIndex = params.scoreIndex + score = scores[scoreIndex] + self:finishtweening() + self:diffusealpha(0) + if score ~= nil then + self:smooth(scoreListAnimationSeconds * i) + self:diffusealpha(1) + end + end, + + LoadFont("Common Normal") .. { + Name = "Index", + InitCommand = function(self) + self:valign(0) + self:x(actuals.ItemIndexMargin) + self:zoom(itemIndexSize) + self:maxwidth((actuals.ItemSSRRightAlignLeftGap - actuals.ItemSSRWidth) / itemIndexSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + self:settextf("%d.", scoreIndex) + end + end + }, + LoadFont("Common Normal") .. { + Name = "SSR", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.ItemSSRRightAlignLeftGap) + self:zoom(ssrTextSize) + self:maxwidth(actuals.ItemSSRWidth / ssrTextSize - textzoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + local ssr = 0 + if isLocal then + if chosenSkillset == "Recent" then + ssr = score:GetSkillsetSSR("Overall") + else + ssr = score:GetSkillsetSSR(chosenSkillset) + end + else + ssr = score.ssr + end + self:settextf("%05.2f", ssr) + self:diffuse(colorByMSD(ssr)) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Name", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + self:x(actuals.ItemSongNameLeftGap) + + txt:halign(0):valign(0) + bg:halign(0):valign(0) + bg:y(-scoreitembgbump) + + txt:zoom(nameTextSize) + txt:maxwidth(actuals.ItemSongNameWidth / nameTextSize - textzoomFudge) + txt:maxheight(actuals.ItemAllottedSpace / itemCount / nameTextSize) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomy(actuals.ItemAllottedSpace / itemCount) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetScore") + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + if isLocal then + local song = SONGMAN:GetSongByChartKey(score:GetChartKey()) + if song then + txt:settext(song:GetDisplayMainTitle()) + if score:GetEtternaValid() then + txt:diffuse(COLORS:getMainColor("PrimaryText")) + else + txt:diffuse(COLORS:getColor("generalBox", "InvalidScore")) + end + else + txt:settext("") + end + else + txt:settext(score.songName) + txt:diffuse(COLORS:getMainColor("PrimaryText")) + end + -- for recent scores, cut the width in half to make room for the date + if chosenSkillset == "Recent" then + txt:maxwidth(actuals.ItemSongNameWidth / 3 * 2 / nameTextSize - textzoomFudge) + else + txt:maxwidth(actuals.ItemSongNameWidth / nameTextSize - textzoomFudge) + end + + bg:zoomx(txt:GetZoomedWidth()) + + -- if mouse is currently hovering + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + DisplayLanguageChangedMessageCommand = function(self) + self:playcommand("SetScore") + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if params.event == "DeviceButton_left mouse button" then + -- find song on click (even if filtered) + local w = SCREENMAN:GetTopScreen():GetChild("WheelFile") + if w ~= nil then + local ck + if isLocal then + ck = score:GetChartKey() + else + ck = score.chartkey + end + if ck ~= nil then + w:playcommand("FindSong", {chartkey = ck}) + end + end + elseif params.event == "DeviceButton_right mouse button" and isLocal then + if score ~= nil then + score:ToggleEtternaValidation() + if score:GetEtternaValid() then + ms.ok("Score validated") + else + ms.ok("Score invalidated") + end + STATSMAN:UpdatePlayerRating() + self:playcommand("SetScore") + end + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Date", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(actuals.ItemSongNameLeftGap + actuals.ItemSongNameWidth / 3 * 2) + self:zoom(dateTextSize) + self:maxwidth(actuals.ItemSongNameWidth / 3 / dateTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + if chosenSkillset == "Recent" then + self:diffusealpha(1) + local d = score:GetDate() + if d ~= nil then + self:settext(d) + else + self:settext("") + end + else + self:diffusealpha(0) + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.ItemRateRightAlignLeftGap) + self:zoom(rateTextSize) + self:maxwidth(actuals.ItemRateWidth / rateTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local rate = 0 + if isLocal then + rate = score:GetMusicRate() + else + rate = score.rate + end + local ratestring = string.format("%.2f", rate):gsub("%.?0+$", "") .. "x" + self:settext(ratestring) + end + end + }, + LoadFont("Common Normal") .. { + Name = "WifePercent", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.ItemPercentRightAlignLeftGap) + self:zoom(percentTextSize) + self:maxwidth(actuals.ItemPercentWidth / percentTextSize - textzoomFudge) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetScore") + end, + SetScoreCommand = function(self) + if score ~= nil then + if isLocal then + local w = score:GetWifeScore() + local wifestr = checkWifeStr(w) + self:settext(wifestr) + self:diffuse(colorByGrade(score:GetWifeGrade())) + else + local w = score.wife + local wifestr = checkWifeStr(w) + self:settext(wifestr) + self:diffuse(colorByGrade(score.grade)) + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "Difficulty", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.ItemDiffRightAlignLeftGap) + self:zoom(diffTextSize) + self:maxwidth(actuals.ItemDiffWidth / diffTextSize - textzoomFudge) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetScore") + end, + SetScoreCommand = function(self) + if score ~= nil then + if isLocal then + local steps = SONGMAN:GetStepsByChartKey(score:GetChartKey()) + if steps then + self:settext(getShortDifficulty(steps:GetDifficulty())) + self:diffuse(colorByDifficulty(steps:GetDifficulty())) + else + self:settext("") + end + else + local diff = score.difficulty + self:settext(getShortDifficulty(diff)) + self:diffuse(colorByDifficulty(diff)) + end + end + end + } + + } + end + + -- this generates the overall page + -- it is within the createList function because it is really part of the skillset list + -- but overall just needs its own special behavior + local function overallPage() + local textmarginX = actuals.AvatarLeftGap + actuals.AvatarSize + actuals.NameInfoLeftMargin + + local profile = GetPlayerOrMachineProfile(PLAYER_1) + local pname = profile:GetDisplayName() + + -- list of skillsets mapped to number of plays + -- sorted immediately after being emplaced here + local playsbyskillset = SCOREMAN:GetPlaycountPerSkillset(profile) + local playsbyskillsetSorted = {} + + -- sort and replace the skillsetplays table + local function sortPlaysBySkillset() + local t = {} + for i,v in ipairs(playsbyskillset) do + t[i] = {v, ms.SkillSets[i]} + end + + table.sort( + t, + function(a,b) + return a[1] > b[1] + end + ) + return t + end + playsbyskillsetSorted = sortPlaysBySkillset() + + -- returns the skillset and count of the skillset in the desired position + local function getSkillsetPlaysByPosition(position) + position = clamp(position, 1, #ms.SkillSets) + return playsbyskillsetSorted[position][1], playsbyskillsetSorted[position][2] + end + + -- a list of functions which basically determine the behavior of each small text line in the overall page + -- self refers to the text actor + -- the amount of functions listed here determines how many small lines appear + -- Left refers to the stat lines constantly on the left + -- Right refers to the stat lines in the alt page on the right + local smallTextFunctions = { + Left = { + -- songs loaded + function(self) + local count = SONGMAN:GetNumSongs() + self:settextf("%d songs loaded", count) + end, + -- song packs installed (and filtered) + function(self) + local packcount = SONGMAN:GetNumSongGroups() + local groupcount = #WHEELDATA:GetAllGroups() + if packcount ~= groupcount then + self:settextf("%d packs loaded (%d visible)", packcount, groupcount) + else + self:settextf("%d packs loaded", packcount) + end + end, + -- current judge + function(self) + local judge = GetTimingDifficulty() + self:settextf("Judge: %d", judge) + end, + -- a line break + function(self) + end, + -- top skillset plays header + function(self) + self:settext("Top 3 Played Skillsets") + end, + -- top played skillset + function(self) + local count, name = getSkillsetPlaysByPosition(1) + self:settextf("%s (%d)", name, count) + end, + -- 2nd top played skillset + function(self) + local count, name = getSkillsetPlaysByPosition(2) + self:settextf("%s (%d)", name, count) + end, + -- 3rd top played skillset + function(self) + local count, name = getSkillsetPlaysByPosition(3) + self:settextf("%s (%d)", name, count) + end, + -- blank + function(self) + end, + -- Upload all scores button + function(self) + if DLMAN:IsLoggedIn() then + self:settext("Upload all scores to EO") + else + self:settext("") + end + end, + -- Validate all scores button + function(self) + self:settext("Validate all scores") + end, + }, + Right = { + -- playcount + function(self) + local pcount = SCOREMAN:GetTotalNumberOfScores() + self:settextf("%d plays", pcount) + end, + -- arrow count + function(self) + local parrows = profile:GetTotalTapsAndHolds() + -- shorten if over 1 million since the number is kind of long + local nstr = shortenIfOver1Mil(parrows) + self:settextf("%s arrows smashed", nstr) + end, + -- playtime (overall, not gameplay) + function(self) + local ptime = profile:GetTotalSessionSeconds() + self:settextf("%s playtime", SecondsToHHMMSS(ptime)) + end, + -- empty padding + function(self) + end, + function(self) + end, + -- score stats + function(self) + self:settextf("Score Stats:") + end, + -- AAAAA count + function(self) + local quints = WHEELDATA:GetTotalClearsByGrade("Grade_Tier01") + self:settextf("AAAAAs: %d", quints) + end, + -- AAAA count + function(self) + local scores = WHEELDATA:GetTotalClearsByGrade("Grade_Tier02") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier03") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier04") + self:settextf("AAAAs: %d", scores) + end, + -- AAA count + function(self) + local scores = WHEELDATA:GetTotalClearsByGrade("Grade_Tier05") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier06") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier07") + self:settextf("AAAs: %d", scores) + end, + -- AA count + function(self) + local scores = WHEELDATA:GetTotalClearsByGrade("Grade_Tier08") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier09") + WHEELDATA:GetTotalClearsByGrade("Grade_Tier10") + self:settextf("AAs: %d", scores) + end, + -- lamp count + function(self) + local lamps = WHEELDATA:GetTotalLampCount() + self:settextf("Pack Lamps: %d", lamps) + end, + + -- at the cost of your fps to make the game look better + -- these are here for space padding + -- feel free to replace or remove them at will + function(self) + end, + function(self) + end, + function(self) + end, + function(self) + end, + function(self) + end, + }, + } + + -- these functions run immediately after init if they exist + -- they should have access to the top screen + -- Left refers to the stat lines constantly on the left + -- Right refers to the stat lines in the alt page on the right + local smallTextInitFunctions = { + Left = { + -- [index] = function(self) end, + [5] = function(self) -- hack to make this text slightly bigger + self:zoom(smalllineTextSize + 0.1) + end, + }, + Right = { + -- [index] = function(self) end, + [5] = function(self) -- hack to make this text slightly bigger + self:zoom(smalllineTextSize + 0.1) + end, + }, + } + + -- these functions run as you mouse over the text + -- each subentry is 3 functions accepting no params, only self + -- first function is onHover (mouse on) + -- second function is onUnHover (mouse out) + -- third function is onClick (mouse down) + -- invisible checks are not necessary + -- Left refers to the stat lines constantly on the left + -- Right refers to the stat lines in the alt page on the right + -- the indices of the subtable elements correspond to the function index implicitly defined in the tables above + local smallTextHoverFunctions = { + Left = { + -- [index] = { function(self) end, function(self) end } + [10] = { + function(self) + if DLMAN:IsLoggedIn() then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("May be slow - Will run in background") + TOOLTIP:Show() + end + end, + function(self) + self:diffusealpha(1) + TOOLTIP:Hide() + end, + function(self, params) + if DLMAN:IsLoggedIn() then + if params.event == "DeviceButton_left mouse button" then + DLMAN:UploadAllScores() + end + end + end, + }, + [11] = { + function(self) + self:diffusealpha(buttonHoverAlpha) + end, + function(self) + self:diffusealpha(1) + end, + function(self, params) + if profile then + profile:UnInvalidateAllScores() + STATSMAN:UpdatePlayerRating() + end + end, + }, + }, + Right = { + -- [index] = { function(self) end, function(self) end } + [2] = { + function(self) + -- for these functions i could just be changing the text itself + -- but to keep it consistent with how i did the other thing + -- im going to use tooltips here + TOOLTIP:SetText(profile:GetTotalTapsAndHolds()) + TOOLTIP:Show() + end, + function(self) + TOOLTIP:Hide() + end, + } + } + } + + local function leftTextSmall(i) + local cHover = nil + local cUnHover = nil + local cClick = nil + -- set the mouse commands if they exist + if smallTextHoverFunctions.Left[i] ~= nil then + cHover = function(self, params) + if self:IsInvisible() then return end + smallTextHoverFunctions.Left[i][1](self) + end + cUnHover = function(self, params) + if self:IsInvisible() then return end + smallTextHoverFunctions.Left[i][2](self) + end + cClick = function(self, params) + if self:IsInvisible() then return end + smallTextHoverFunctions.Left[i][3](self, params) + end + end + + return UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "LeftSmallText_"..i, + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(textmarginX, actuals.NameInfoSmallerUpperGap + (i-1) * actuals.NameInfoSmallerLineSpacing) + self:zoom(smalllineTextSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.AvatarLeftGap - actuals.AvatarSize - actuals.NameInfoLeftMargin) / smalllineTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "SecondaryText") + self:queuecommand("Startup") + end, + SetCommand = smallTextFunctions.Left[i], + StartupCommand = smallTextInitFunctions.Left[i], + MouseOverCommand = cHover, + MouseOutCommand = cUnHover, + MouseDownCommand = cClick, + UpdateLoginStatusCommand = function(self) + self:playcommand("Set") + if cHover ~= nil then + if isOver(self) then self:playcommand("MouseOver") end + end + if cUnHover ~= nil then + if not isOver(self) then self:playcommand("MouseOut") end + end + end, + } + end + + -- generalized function for the right column of text + local function rightTextSmall(i, maxIndex) + return UIElements.TextToolTip(1, 1, "Common Normal") .. { + InitCommand = function(self) + -- do not override this stuff probably + self:halign(0):valign(0) + self:x(actuals.RightTextLeftGap) + self:y(actuals.RightSmallTextUpperGap + (actuals.RightSmallTextAllottedSpace / (maxIndex)) * (i-1)) + self:zoom(rightSmallTextSize) + self:maxwidth((actuals.Width - actuals.RightTextLeftGap) / rightSmallTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "SecondaryText") + self:playcommand("SubInit") + end, + SubInitCommand = function(self) + -- implement this within rightTextSkillsets or rightTextStats + -- it is run at init and should only be run at init (as the last thing) + -- (this is different from the StartupCommand, although it is the same exact concept) + end, + SetCommand = function(self) + -- implement this within rightTextSkillsets or rightTextStats + end, + } + end + + -- right column text for skillsets + local function rightTextSkillsets(i, maxIndex) + local skillsetIndex = math.ceil(i/2) + local skillset = ms.SkillSets[skillsetIndex] + return rightTextSmall(i, maxIndex) .. { + Name = "RightTextSkillsets_"..i, + SubInitCommand = function(self) + -- for rating indices, give a tiny right shift + if i % 2 == 0 then + self:addx(actuals.RightTextSlightOffset) + end + end, + SetCommand = function(self) + -- even indices are the ratings + -- odd indices are the titles + if i % 2 == 0 then + if DLMAN:IsLoggedIn() then + local lrating = profile:GetPlayerSkillsetRating(skillset) + local orating = DLMAN:GetSkillsetRating(skillset) + local rank = DLMAN:GetSkillsetRank(skillset) + self:settextf("%5.2f (#%d) / %5.2f", orating, rank, lrating) + self:diffuse(colorByMSD(orating)) + else + local rating = profile:GetPlayerSkillsetRating(skillset) + self:settextf("%5.2f", rating) + self:diffuse(colorByMSD(rating)) + end + else + self:settextf("%s:", skillset) + end + end, + UpdateLoginStatusCommand = function(self) + self:playcommand("Set") + end, + PlayerRatingUpdatedMessageCommand = function(self) + self:playcommand("Set") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + if i % 2 ~= 0 then return end -- cant click non numbers + if i <= 2 then return end -- cant click the overall skillset + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + if i % 2 ~= 0 then return end + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" and i % 2 == 0 then + -- change chosen skillset and regrab all scores + -- but know that overall does something different + -- this many parents should lead to the t of this .lua + self:GetParent():GetParent():GetParent():GetParent():playcommand("UpdateScores", {index = i/2}) + self:GetParent():GetParent():GetParent():GetParent():playcommand("UpdateSelectedIndex", {index = i/2}) + self:GetParent():GetParent():GetParent():GetParent():playcommand("UpdateList") + self:diffusealpha(1) + end + end, + } + end + + -- right column text for stats + local function rightTextStats(i, maxIndex) + local cHover = nil + local cUnHover = nil + -- set the hover commands if they exist + if smallTextHoverFunctions.Right[i] ~= nil then + cHover = function(self, params) + if self:IsInvisible() then return end + smallTextHoverFunctions.Right[i][1](self) + end + cUnHover = function(self, params) + if self:IsInvisible() then return end + smallTextHoverFunctions.Right[i][2](self) + end + end + + return rightTextSmall(i, maxIndex) .. { + Name = "RightTextStats_"..i, + SetCommand = smallTextFunctions.Right[i], + StartupCommand = smallTextInitFunctions.Right[i], + MouseOverCommand = cHover, + MouseOutCommand = cUnHover, + } + end + + local t = Def.ActorFrame { + Name = "OverallPage", + BeginCommand = function(self) + self:z(overallPageZBump) + self:playcommand("Set") + end, + + Def.Sprite { + Name = "Avatar", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.AvatarLeftGap, actuals.InfoUpperMargin) + end, + AvatarChangedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + self:Load(getAvatarPath(PLAYER_1)) + self:zoomto(actuals.AvatarSize, actuals.AvatarSize) + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "NameRank", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(textmarginX, actuals.InfoUpperMargin) + self:zoom(largelineTextSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.AvatarLeftGap - actuals.AvatarSize - actuals.NameInfoLeftMargin) / largelineTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + local rank = DLMAN:GetSkillsetRank("Overall") + self:settextf("%s (#%d)", pname, rank) + else + self:settext(pname) + end + end, + ProfileRenamedMessageCommand = function(self) + pname = profile:GetDisplayName() + self:playcommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + renameProfileDialogue(profile) + end + end, + UpdateLoginStatusCommand = function(self) + self:playcommand("Set") + end + }, + LoadFont("Common Normal") .. { + Name = "LoggedInAs", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(textmarginX, actuals.InfoUpperMargin + actuals.NameInfoLargeLineSpacing) + self:zoom(largelineTextSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.AvatarLeftGap - actuals.AvatarSize - actuals.NameInfoLeftMargin) / largelineTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + self:settextf("(EO: %s)", DLMAN:GetUsername()) + else + self:settext("") + end + end, + UpdateLoginStatusCommand = function(self) + self:playcommand("Set") + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "PlayerRatingsTitle", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + self:xy(actuals.RightTextLeftGap, actuals.InfoUpperMargin) + + txt:halign(0):valign(0) + bg:halign(0):valign(0) + txt:zoom(largelineTextSize) + txt:maxwidth((actuals.Width - actuals.RightTextLeftGap) / largelineTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + + -- this is the ultimate fudge value + -- meant to be the approximate size of the text vertically but a lot smaller + bg:y(-actuals.NameInfoLargeLineSpacing / 3) + + self.ratings = true + self:queuecommand("UpdateToggle") + end, + UpdateToggleCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + if self.ratings then + if DLMAN:IsLoggedIn() then + txt:settext("Player Ratings (Online/Offline):") + else + txt:settext("Player Ratings:") + end + else + txt:settext("Player Stats:") + end + + bg:zoomto(txt:GetZoomedWidth(), actuals.NameInfoLargeLineSpacing + textzoomFudge) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + self.ratings = not self.ratings + if self.ratings then + self:GetParent():GetChild("RightSmallTextFrame"):playcommand("ShowRatings") + else + self:GetParent():GetChild("RightSmallTextFrame"):playcommand("ShowStats") + end + self:playcommand("UpdateToggle") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + UpdateLoginStatusCommand = function(self) + self:playcommand("UpdateToggle") + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "RecentScores", + InitCommand = function(self) + self:halign(0):valign(1) + self:x(actuals.AvatarLeftGap) + self:y(actuals.Height - actuals.InfoUpperMargin) + self:zoom(largelineTextSize) + self:maxwidth((actuals.Width - actuals.AvatarLeftGap - actuals.RightTextLeftGap) / largelineTextSize - textzoomFudge) + self:settext("View Recent Scores") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + self:diffusealpha(1) + self:GetParent():GetParent():playcommand("UpdateScores", {index = -1}) + self:GetParent():GetParent():playcommand("UpdateSelectedIndex") + self:GetParent():GetParent():playcommand("UpdateList") + end + end + } + } + + local function leftSmallTextContainer() + -- just a container for control purposes + local t = Def.ActorFrame { + Name = "LeftSmallTextFrame", + } + for i = 1, #smallTextFunctions.Left do + t[#t+1] = leftTextSmall(i) + end + return t + end + t[#t+1] = leftSmallTextContainer() + + local function rightSmallTextContainer() + local skillsetTextCount = #ms.SkillSets * 2 + local statTextCount = #smallTextFunctions.Right + + -- just a container for control purposes + local t = Def.ActorFrame { + Name = "RightSmallTextFrame", + BeginCommand = function(self) + self:playcommand("ShowRatings") + end, + ShowRatingsCommand = function(self) + for i = 1, skillsetTextCount do + local c = self:GetChild("RightTextSkillsets_"..i) + c:finishtweening() + c:z(5) + c:smooth(textListAnimationSeconds * i) + c:diffusealpha(1) + end + for i = 1, statTextCount do + local c = self:GetChild("RightTextStats_"..i) + c:finishtweening() + c:z(-5) + c:smooth(textListAnimationSeconds) + c:diffusealpha(0) + end + end, + ShowStatsCommand = function(self) + for i = 1, skillsetTextCount do + local c = self:GetChild("RightTextSkillsets_"..i) + c:finishtweening() + c:z(-5) + c:smooth(textListAnimationSeconds) + c:diffusealpha(0) + end + for i = 1, statTextCount do + local c = self:GetChild("RightTextStats_"..i) + c:finishtweening() + c:z(5) + c:smooth(textListAnimationSeconds * i) + c:diffusealpha(1) + end + end, + } + for i = 1, skillsetTextCount do + t[#t+1] = rightTextSkillsets(i, skillsetTextCount) + end + for i = 1, statTextCount do + t[#t+1] = rightTextStats(i, statTextCount) + end + return t + end + t[#t+1] = rightSmallTextContainer() + + return t + end + + for i = 1, itemCount do + t[#t+1] = createItem(i) + end + + t[#t+1] = overallPage() + + t[#t+1] = Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused and chosenSkillset ~= "Overall" then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end + } + + t[#t+1] = UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "OnlineOfflineToggle", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + self:x(actuals.MainIndicatorLeftGap) + self:y(actuals.MainIndicatorUpperGap) + txt:halign(0):valign(0) + txt:zoom(indicatorTextSize) + txt:maxwidth((actuals.ItemSongNameWidth + actuals.ItemSongNameLeftGap) / indicatorTextSize - textzoomFudge) + txt:settext("") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:halign(0) + -- really really bad approximation in conjunction with the zoomto height below + -- there must be a better way (dont suggest txt:GetZoomedHeight()) + bg:y(actuals.ItemSpacing / 2.4) + self:queuecommand("UpdateToggle") + end, + UpdateToggleCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + if chosenSkillset == "Overall" or not DLMAN:IsLoggedIn() then + self:diffusealpha(0) + else + self:diffusealpha(1) + end + + if isLocal then + txt:settext("Showing Local") + else + txt:settext("Showing Online") + end + + -- itemspacing is probably a good approximation for this button height + bg:zoomto(txt:GetZoomedWidth(), actuals.ItemSpacing + textzoomFudge) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if DLMAN:IsLoggedIn() then + isLocal = not isLocal + self:GetParent():playcommand("UpdateScores") + self:GetParent():playcommand("UpdateList") + else + -- to avoid being stuck in online mode when somehow not logged in + if not isLocal then + isLocal = true + self:GetParent():playcommand("UpdateScores") + self:GetParent():playcommand("UpdateList") + end + end + self:playcommand("UpdateToggle") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + UpdateLoginStatusCommand = function(self) + -- this logic should handle case where you are logged out while in the online portion of the menu + -- will force an exit + local loggedIn = DLMAN:IsLoggedIn() + if not isLocal and not loggedIn then + self:playcommand("Click", {update = "OnMouseDown"}) + isLocal = true + else + self:playcommand("UpdateToggle") + end + end + } + + t[#t+1] = LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.MainIndicatorUpperGap) + self:zoom(pageTextSize) + -- oddly precise max width but this should fit with the original size + self:maxwidth(actuals.Width * 0.14 / pageTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + local lb = clamp((page-1) * (itemCount) + 1, 0, #scores) + local ub = clamp(page * itemCount, 0, #scores) + self:settextf("%d-%d/%d", lb, ub, #scores) + end + } + + return t +end + +t[#t+1] = Def.Quad { + Name = "UpperLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.UpperLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "LipTop", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.LipSeparatorThickness) + self:diffusealpha(0.3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = createChoices() + +t[#t+1] = createList() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/scores.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/scores.lua new file mode 100644 index 0000000000..a2e4005a61 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/scores.lua @@ -0,0 +1,1880 @@ +local focused = false +local t = Def.ActorFrame { + Name = "ScoresPageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + WheelSettledMessageCommand = function(self, params) + -- cascade visual update to everything + -- self:playcommand("Set", {song = params.song, group = params.group, hovered = params.hovered, steps = params.steps}) + self:playcommand("UpdateDisplay") + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.scoretabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + -- if focused was already on, we double tapped + -- enable "double tap" behavior + if focused then + self:playcommand("Doubletap") + end + focused = true + self:playcommand("UpdateDisplay") + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + focused = false + end + end + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("UpdateDisplay") + end, + ChangedStepsMessageCommand = function(self, params) + self:playcommand("UpdateDisplay") + end, + UpdateDisplayCommand = function(self) + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end +} + +-- this can be nil if no scores exist in the profile +local mostRecentScore = SCOREMAN:GetMostRecentScore() + +-- make text buttons slightly taller +local textButtonHeightFudgeScalarMultiplier = 1.6 + +local ratios = { + UpperLipHeight = 43 / 1080, + LipSeparatorThickness = 2 / 1080, + ItemUpperSpacing = 68 / 1080, -- top of frame to top of text, to push all the items down + + PageTextRightGap = 33 / 1920, -- right of frame, right of text + PageTextUpperGap = 48 / 1080, -- top of frame, top of text + + ItemWidth = 776 / 1920, -- this should just be frame width + ItemHeight = 45 / 1080, -- rough approximation of height (top of text to somewhere down below) + ItemAllottedSpace = 381 / 1080, -- from top edge of top item to top edge of bottom item + + ItemIndexLeftGap = 11 / 1920, -- left edge of frame to left edge of number + ItemIndexWidth = 38 / 1920, -- left edge of number to just before SSR + + LeftCenteredAlignmentLineLeftGap = 126 / 1920, -- from left edge of frame to half way between SSR and player name + LeftCenteredAlignmentDistance = 8 / 1920, -- the distance to push each alignment away from the line above + RightInfoLeftAlignLeftGap = 566 / 1920, -- left edge of frame to left edge of text/icons on right side + RightInfoRightAlignRightGap = 28 / 1920, -- right edge of frame to right edge of text + + SSRRateWidth = 70 / 1920, -- rough measurement of the area needed for SSR (rate should be smaller but will be restricted the same) + NameJudgmentWidth = 430 / 1920, -- same but for name and judgments + + TrophySize = 21 / 1080, -- height and width of the icon + PlaySize = 19 / 1080, + IconHeight = 19 / 1080, -- uhhhhh + + -- local page stuff + SideBufferGap = 12 / 1920, -- from edge of frame to left end of leftmost items, and rightmost of all rightmost items (ideally) + GradeUpperGap = 10 / 1080, -- from bottom of top lip to top of grade + ClearTypeUpperGap = 74 / 1080, -- bottom of top lip to top of cleartype + IconSetUpperGap = 109 / 1080, -- bottom of top lip to top of all icons under cleartype + IconSetSpacing = 6 / 1920, -- distance between icons + + -- width of each of these is Width - DetailLineLeftGap - SideBufferGap*2 + DetailLineLeftGap = 118 / 1920, -- left edge to left edge of text + DetailLine1TopGap = 12 / 1080, -- bottom of top lip to top of text + DetailLine2TopGap = 37 / 1080, -- bottom of top lip to top of text + DetailLine3TopGap = 62 / 1080, -- bottom of top lip to top of text + DetailLine4TopGap = 87 / 1080, -- bottom of top lip to top of text + DetailLine5TopGap = 112 / 1080, -- bottom of top lip to top of text + + -- entries necessary for the copy-paste judgment bars from evaluation + JudgmentBarsTopGap = 145 / 1080, -- bottom of top lip to top of first judgment bar + JudgmentBarHeight = 22 / 1080, + JudgmentBarLength = 640 / 1920, + JudgmentBarSpacing = 4 / 1080, + JudgmentBarAllottedSpace = 129 / 1080, + JudgmentNameLeftGap = 21 / 1920, + JudgmentCountRightGap = 74 / 1920, + + ScoreRateListTopGap = 145 / 1080, -- bottom of top lip to top of frame + ScoreRateListLeftGap = 664 / 1920, -- left edge to left edge of items + ScoreRateListTopBuffer = 19 / 1080, -- top of frame to top of first item (the top of frame holds the "up" button) + ScoreRateListAllottedSpace = 261 / 1080, -- top of top item to top of bottom item + ScoreRateListBottomTopGap = 289 / 1080, -- really bad name: top of frame to top of "down" button + ScoreRateListWidth = 100 / 1920, -- width of the text + + MainGraphicWidth = 640 / 1920, -- width of judgment bars and offset plot + OffsetPlotHeight = 184 / 1080, + OffsetPlotUpperGap = 308 / 1080, -- bottom of top lip to top of graph +} + +local actuals = { + UpperLipHeight = ratios.UpperLipHeight * SCREEN_HEIGHT, + LipSeparatorThickness = ratios.LipSeparatorThickness * SCREEN_HEIGHT, + ItemUpperSpacing = ratios.ItemUpperSpacing * SCREEN_HEIGHT, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + PageTextUpperGap = ratios.PageTextUpperGap * SCREEN_HEIGHT, + ItemWidth = ratios.ItemWidth * SCREEN_WIDTH, + ItemHeight = ratios.ItemHeight * SCREEN_HEIGHT, + ItemAllottedSpace = ratios.ItemAllottedSpace * SCREEN_HEIGHT, + ItemIndexLeftGap = ratios.ItemIndexLeftGap * SCREEN_WIDTH, + ItemIndexWidth = ratios.ItemIndexWidth * SCREEN_WIDTH, + LeftCenteredAlignmentLineLeftGap = ratios.LeftCenteredAlignmentLineLeftGap * SCREEN_WIDTH, + LeftCenteredAlignmentDistance = ratios.LeftCenteredAlignmentDistance * SCREEN_WIDTH, + RightInfoLeftAlignLeftGap = ratios.RightInfoLeftAlignLeftGap * SCREEN_WIDTH, + RightInfoRightAlignRightGap = ratios.RightInfoRightAlignRightGap * SCREEN_WIDTH, + SSRRateWidth = ratios.SSRRateWidth * SCREEN_WIDTH, + NameJudgmentWidth = ratios.NameJudgmentWidth * SCREEN_WIDTH, + TrophySize = ratios.TrophySize * SCREEN_HEIGHT, + PlaySize = ratios.PlaySize * SCREEN_HEIGHT, + IconHeight = ratios.IconHeight * SCREEN_HEIGHT, + SideBufferGap = ratios.SideBufferGap * SCREEN_WIDTH, + GradeUpperGap = ratios.GradeUpperGap * SCREEN_HEIGHT, + ClearTypeUpperGap = ratios.ClearTypeUpperGap * SCREEN_HEIGHT, + IconSetUpperGap = ratios.IconSetUpperGap * SCREEN_HEIGHT, + IconSetSpacing = ratios.IconSetSpacing * SCREEN_WIDTH, + DetailLineLeftGap = ratios.DetailLineLeftGap * SCREEN_WIDTH, + DetailLine1TopGap = ratios.DetailLine1TopGap * SCREEN_HEIGHT, + DetailLine2TopGap = ratios.DetailLine2TopGap * SCREEN_HEIGHT, + DetailLine3TopGap = ratios.DetailLine3TopGap * SCREEN_HEIGHT, + DetailLine4TopGap = ratios.DetailLine4TopGap * SCREEN_HEIGHT, + DetailLine5TopGap = ratios.DetailLine5TopGap * SCREEN_HEIGHT, + JudgmentBarsTopGap = ratios.JudgmentBarsTopGap * SCREEN_HEIGHT, + JudgmentBarHeight = ratios.JudgmentBarHeight * SCREEN_HEIGHT, + JudgmentBarLength = ratios.JudgmentBarLength * SCREEN_WIDTH, + JudgmentBarSpacing = ratios.JudgmentBarSpacing * SCREEN_HEIGHT, + JudgmentBarAllottedSpace = ratios.JudgmentBarAllottedSpace * SCREEN_HEIGHT, + JudgmentNameLeftGap = ratios.JudgmentNameLeftGap * SCREEN_WIDTH, + JudgmentCountRightGap = ratios.JudgmentCountRightGap * SCREEN_WIDTH, + ScoreRateListTopGap = ratios.ScoreRateListTopGap * SCREEN_HEIGHT, + ScoreRateListLeftGap = ratios.ScoreRateListLeftGap * SCREEN_WIDTH, + ScoreRateListTopBuffer = ratios.ScoreRateListTopBuffer * SCREEN_HEIGHT, + ScoreRateListAllottedSpace = ratios.ScoreRateListAllottedSpace * SCREEN_HEIGHT, + ScoreRateListBottomTopGap = ratios.ScoreRateListBottomTopGap * SCREEN_HEIGHT, + ScoreRateListWidth = ratios.ScoreRateListWidth * SCREEN_WIDTH, + MainGraphicWidth = ratios.MainGraphicWidth * SCREEN_WIDTH, + OffsetPlotHeight = ratios.OffsetPlotHeight * SCREEN_HEIGHT, + OffsetPlotUpperGap = ratios.OffsetPlotUpperGap * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +t[#t+1] = Def.Quad { + Name = "UpperLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.UpperLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "LipTop", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.LipSeparatorThickness) + self:diffusealpha(0.3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +-- online and offline (default is offline) +local isLocal = true +-- all scores or top scores (online only) +-- dont have to modify this var with direct values, call DLMAN to update it +local allScores = not DLMAN:GetTopScoresOnlyFilter() + +-- how many to display +local itemCount = 7 +local scoreListAnimationSeconds = 0.05 +local localPageAnimationSeconds = 0.1 + +local itemIndexSize = 0.95 +local ssrTextSize = 0.9 +local rateTextSize = 0.92 +local nameTextSize = 0.9 +local judgmentTextSize = 0.6 +local wifePercentTextSize = 0.92 +local dateTextSize = 0.6 +local pageTextSize = 0.7 +local loadingTextSize = 0.9 +local textzoomFudge = 5 + +local buttonHoverAlpha = 0.6 + +local gradeTextSize = 2.2 +local clearTypeTextSize = 1.15 +local detailTextSize = 0.75 +local rateTextSize = 0.65 +local onlinePlotTextSize = 0.8 + +-- functionally create the score list +-- this is basically a slimmed version of the Evaluation Scoreboard +local function createList() + local page = 1 + local maxPage = 1 + local scorelistframe = nil + + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + if scorelistframe then + scorelistframe:playcommand("MovedPage") + end + end + + -- yes, we do have a country filter + -- we don't tell anyone + -- the Global country just shows everyone + local dlmanScoreboardCountryFilter = "Global" + + local scores = {} -- online scores + local localscore = nil -- local score + local localscoreIndex = 1 + local localrtTable = nil -- local rate table (scores) + local localrateIndex = 1 + local localrates = nil + + -- to prevent bombing the server repeatedly with leaderboard requests + -- chartkeys to booleans + local alreadyRequestedLeaderboard = {} + -- to signify whether or not an active request for a chart leaderboard is taking place + local fetchingScores = {} + + local t = Def.ActorFrame { + Name = "ScoreListFrame", + BeginCommand = function(self) + scorelistframe = self + end, + UpdateScoresCommand = function(self) + if self:IsInvisible() then return end + -- HACK BEHAVIOR: use SCUFFMAN's general tab index to pretend we aren't on the scores tab if isLocal is false + -- this lets us change rate for the CurrentRate stuff in the online tab + if not isLocal then + SCUFF.generaltab = -1 + else + SCUFF.generaltab = SCUFF.scoretabindex + end + -- end hack + page = 1 + + -- hide the online offset plot + self:playcommand("HidePlot") + + -- no steps, no scores. + local steps = GAMESTATE:GetCurrentSteps() + if steps == nil then + scores = {} + if isLocal then localrtTable = nil end + return + end + + -- local scoreboard is somewhere else + if isLocal then + scores = {} + localrtTable = getRateTable() + local sng = GAMESTATE:GetCurrentSong() + if localrtTable ~= nil then + localrates, localrateIndex = getUsedRates(localrtTable) + localscoreIndex = 1 + end + return + else + -- operate with dlman scores + -- ... everything here is determined by internal bools set by the toggle buttons + scores = DLMAN:GetChartLeaderBoard(steps:GetChartKey(), dlmanScoreboardCountryFilter) + + -- if scores comes back nil, then the chart is unranked + if scores == nil then + if steps then + local kee = steps:GetChartKey() + fetchingScores[kee] = false + end + return + end + + -- this is the initial request for the leaderboard which should only end up running once + -- and when it finishes, it loops back through this command + -- on the second passthrough, the leaderboard is hopefully filled out + if #scores == 0 then + if steps then + local kee = steps:GetChartKey() + if not alreadyRequestedLeaderboard[kee] then + alreadyRequestedLeaderboard[kee] = true + fetchingScores[kee] = true + DLMAN:RequestChartLeaderBoardFromOnline( + steps:GetChartKey(), + function(leaderboard) + -- disallow replacing the leaderboard if the request doesnt match the current steps + local s = GAMESTATE:GetCurrentSteps() + if s and s:GetChartKey() == kee then + fetchingScores[kee] = false + self:queuecommand("UpdateScores") + self:queuecommand("UpdateList") + end + end + ) + end + end + end + end + end, + UpdateListCommand = function(self) + if self:IsInvisible() then return end + + -- local logic + if isLocal then + if localrtTable ~= nil and localrates ~= nil and localrates[localrateIndex] ~= nil and localrtTable[localrates[localrateIndex]] ~= nil then + localscore = localrtTable[localrates[localrateIndex]][localscoreIndex] + else + localscore = nil + end + else + localscore = nil + end + + + -- online logic + if scores == nil then + maxPage = 1 + else + maxPage = math.ceil(#scores / itemCount) + end + + for i = 1, itemCount do + local index = (page - 1) * itemCount + i + self:GetChild("ScoreItem_"..i):playcommand("SetScore", {scoreIndex = index}) + end + self:GetParent():playcommand("UpdateButtons") + self:playcommand("HidePlot") + end, + MovedPageCommand = function(self) + self:playcommand("UpdateList") + end, + ToggleCurrentRateCommand = function(self) + if isLocal then + -- the local scoreboard is sorted by rate always + else + DLMAN:ToggleRateFilter() + end + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end, + ToggleAllScoresCommand = function(self) + if DLMAN:IsLoggedIn() then + DLMAN:ToggleTopScoresOnlyFilter() + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end + end, + ToggleLocalCommand = function(self) + -- this will allow Online -> Local if you happen to be in an impossible state + if not isLocal or DLMAN:IsLoggedIn() then + isLocal = not isLocal + end + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end, + ToggleInvalidCommand = function(self) + if DLMAN:IsLoggedIn() then + DLMAN:ToggleCCFilter() + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end + end, + UpdateLoginStatusCommand = function(self) + local nowloggedin = DLMAN:IsLoggedIn() + if not isLocal and not nowloggedin then + isLocal = true + self:playcommand("UpdateScores") + self:playcommand("UpdateList") + end + end, + DoubletapCommand = function(self) + -- if double tapping the scores tab, swap online/local + -- convenience :) + self:playcommand("ToggleLocal") + end + } + + + local function createItem(i) + local score = nil + local scoreIndex = i + + return Def.ActorFrame { + Name = "ScoreItem_"..i, + InitCommand = function(self) + self:y((actuals.ItemAllottedSpace / (itemCount - 1)) * (i-1) + actuals.ItemUpperSpacing) + end, + SetScoreCommand = function(self, params) + scoreIndex = params.scoreIndex + -- nil scores table: unranked chart + if scores ~= nil then + score = scores[scoreIndex] + else + score = nil + end + self:finishtweening() + self:diffusealpha(0) + if score ~= nil then + self:smooth(scoreListAnimationSeconds * i) + self:diffusealpha(1) + end + end, + + LoadFont("Common Normal") .. { + Name = "Index", + InitCommand = function(self) + self:halign(0):valign(1) + self:xy(actuals.ItemIndexLeftGap, actuals.ItemHeight) + self:zoom(itemIndexSize) + self:maxwidth(actuals.ItemIndexWidth / itemIndexSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + self:settextf("%d.", scoreIndex) + end + end + }, + LoadFont("Common Normal") .. { + Name = "SSR", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.LeftCenteredAlignmentLineLeftGap - actuals.LeftCenteredAlignmentDistance) + self:zoom(ssrTextSize) + self:maxwidth(actuals.SSRRateWidth / ssrTextSize - textzoomFudge) + end, + SetScoreCommand = function(self) + if score ~= nil then + local ssr = score:GetSkillsetSSR("Overall") + self:settextf("%05.2f", ssr) + self:diffuse(colorByMSD(ssr)) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Rate", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(1):valign(1) + bg:halign(1):valign(1) + self:xy(actuals.LeftCenteredAlignmentLineLeftGap - actuals.LeftCenteredAlignmentDistance, actuals.ItemHeight) + txt:zoom(rateTextSize) + txt:maxwidth(actuals.SSRRateWidth / rateTextSize - textzoomFudge) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + registerActorToColorConfigElement(txt, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local rt = score:GetMusicRate() + txt:settext(getRateString(rt)) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + setMusicRate(score:GetMusicRate()) + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "PlayerName", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(0) + bg:halign(0):valign(0) + self:x(actuals.LeftCenteredAlignmentLineLeftGap + actuals.LeftCenteredAlignmentDistance) + txt:zoom(nameTextSize) + txt:maxwidth(actuals.NameJudgmentWidth / nameTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local n = score:GetName() + if n == "" then + n = "" + elseif n == "#P1#" then + if score:GetScoreKey() == mostRecentScore:GetScoreKey() then + n = "Last Score" + else + n = "You" + end + end + txt:settext(n) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + if score ~= nil then + local url = "https://etternaonline.com/user/" .. score:GetDisplayName() + GAMESTATE:ApplyGameCommand("urlnoexit," .. url) + end + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "JudgmentsAndCombo", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(1) + bg:halign(0):valign(1) + self:xy(actuals.LeftCenteredAlignmentLineLeftGap + actuals.LeftCenteredAlignmentDistance, actuals.ItemHeight) + txt:zoom(judgmentTextSize) + txt:maxwidth(actuals.NameJudgmentWidth / judgmentTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- a HighScore method does exist to produce this but we want to be able to modify it from the theme + local jgMaStr = tostring(score:GetTapNoteScore("TapNoteScore_W1")) + local jgPStr = tostring(score:GetTapNoteScore("TapNoteScore_W2")) + local jgGrStr = tostring(score:GetTapNoteScore("TapNoteScore_W3")) + local jgGoStr = tostring(score:GetTapNoteScore("TapNoteScore_W4")) + local jgBStr = tostring(score:GetTapNoteScore("TapNoteScore_W5")) + local jgMiStr = tostring(score:GetTapNoteScore("TapNoteScore_Miss")) + local comboStr = tostring(score:GetMaxCombo()) + txt:settextf("%s | %s | %s | %s | %s | %s x%s", jgMaStr, jgPStr, jgGrStr, jgGoStr, jgBStr, jgMiStr, comboStr) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + if score ~= nil then + local url = "https://etternaonline.com/score/view/" .. score:GetScoreid() .. score:GetUserid() + GAMESTATE:ApplyGameCommand("urlnoexit," .. url) + end + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "WifePercent", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(1):valign(0) + bg:halign(1):valign(0) + self:x(actuals.ItemWidth - actuals.RightInfoRightAlignRightGap) + txt:zoom(wifePercentTextSize) + -- 5/6 the space allowed vs the date + -- the icons take up the other half probably + txt:maxwidth((actuals.ItemWidth - actuals.RightInfoLeftAlignLeftGap - actuals.RightInfoRightAlignRightGap) / 6 * 4 / wifePercentTextSize) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetScore") + end, + SetScoreCommand = function(self) + if score ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local ws = score:GetWifeScore() + local wifeStr = checkWifeStr(ws) + local grade = GetGradeFromPercent(score:GetWifeScore()) + txt:settext(wifeStr) + txt:diffuse(colorByGrade(grade)) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + if score ~= nil and score:HasReplayData() then + TOOLTIP:SetText("Show Offset Plot") + TOOLTIP:Show() + self:diffusealpha(buttonHoverAlpha) + end + else + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + if self:IsInvisible() then return end + if params.event == "DeviceButton_left mouse button" then + if score ~= nil then + if score:HasReplayData() then + ms.ok("Loading offsets...") + DLMAN:RequestOnlineScoreReplayData( + score, + function() + ms.ok("Loaded offsets") + self:GetParent():GetParent():playcommand("DisplayOnlineOffsets", { + score = score, + index = i + }) + end + ) + end + end + end + end, + }, + LoadFont("Common Normal") .. { + Name = "DateTime", + InitCommand = function(self) + self:halign(1):valign(1) + self:xy(actuals.ItemWidth - actuals.RightInfoRightAlignRightGap, actuals.ItemHeight) + self:zoom(dateTextSize) + self:maxwidth((actuals.ItemWidth - actuals.RightInfoLeftAlignLeftGap - actuals.RightInfoRightAlignRightGap) / dateTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetScoreCommand = function(self) + if score ~= nil then + self:settext(score:GetDate()) + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "showReplay")) .. { + Name = "ShowReplay", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.RightInfoLeftAlignLeftGap) + self:zoomto(actuals.PlaySize, actuals.PlaySize) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + SetScoreCommand = function(self) + if score ~= nil then + if score:HasReplayData() then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + local sng = GAMESTATE:GetCurrentSteps() + DLMAN:RequestOnlineScoreReplayData( + score, + function() + local scr = SCREENMAN:GetTopScreen() + local sng2 = GAMESTATE:GetCurrentSteps() + if sng and sng2 and sng:GetChartKey() == sng2:GetChartKey() then + if scr:GetMusicWheel():SelectSong(GAMESTATE:GetCurrentSong()) then + local success = SCREENMAN:GetTopScreen():PlayReplay(score) + if success then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + end + end + ) + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Show Replay") + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end + }, + --[[ -- can't view online eval screens + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "showEval")) .. { + Name = "ShowEval", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.RightInfoLeftAlignLeftGap + actuals.TrophySize * 1.2) + self:zoomto(actuals.TrophySize, actuals.TrophySize) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + SetScoreCommand = function(self) + if score ~= nil then + if score:HasReplayData() then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + + if SCREENMAN:GetTopScreen():GetMusicWheel():SelectSong(GAMESTATE:GetCurrentSong()) then + local success = SCREENMAN:GetTopScreen():ShowEvalScreenForScore(score) + if success then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + + self:diffusealpha(1) + end + },]] + } + end + + + local function localRateFrame() + local ratecount = 9 -- really just how many items we want spots for + local ratepage = 1 + local maxratepage = 1 + local framecopy = nil + + local function moveRatePage(n) + ratepage = clamp(ratepage + n, 1, maxratepage) + if framecopy ~= nil then + framecopy:GetParent():playcommand("UpdateList") + end + end + + local function rateItem(i) + local index = i + return UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "RateButton_"..i, + InitCommand = function(self) + self:valign(0) + self:x(actuals.ScoreRateListWidth / 2) + self:y(actuals.ScoreRateListTopBuffer + actuals.ScoreRateListAllottedSpace / ratecount * (i-1)) + self:zoom(rateTextSize) + self:maxwidth(actuals.ScoreRateListWidth / rateTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + if index == localrateIndex then + self:diffusealpha(0.6) + else + self:diffusealpha(1) + end + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if localrateIndex == index then + local inc = 0 + if params.event == "DeviceButton_left mouse button" then + inc = 1 + elseif params.event == "DeviceButton_right mouse button" then + inc = -1 + end + local max = #localrtTable[localrates[localrateIndex]] + localscoreIndex = localscoreIndex + inc + if localscoreIndex > max then localscoreIndex = 1 end + if localscoreIndex < 1 then localscoreIndex = max end + else + localrateIndex = index + localscoreIndex = 1 + end + -- [this] [rateframe] [localpage] [scorelist] + self:GetParent():GetParent():GetParent():playcommand("UpdateList") + end, + UpdateListCommand = function(self) + index = i + ((ratepage-1) * ratecount) + local count = 0 + if localrtTable == nil then + self:settext("") + return + end + if localrtTable[localrates[index]] ~= nil then + count = #localrtTable[localrates[index]] + end + if index == localrateIndex then + self:diffusealpha(0.6) + else + self:diffusealpha(1) + end + if index <= #localrates then + self:settextf("%s (%d)", localrates[index], count) + else + self:settext("") + end + end + } + end + + local t = Def.ActorFrame { + Name = "RateFrame", + InitCommand = function(self) + framecopy = self + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + CONTEXTMAN:RegisterToContextSet(snm, "Main1", anm) + + local selectPressed = false + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- require context is set and the general box is set to this tab + if not CONTEXTMAN:CheckContextSet(snm, "Main1") or SCUFF.generaltab ~= SCUFF.scoretabindex then + selectPressed = false + return + end + + if event.type == "InputEventType_FirstPress" then + if event.button == "EffectUp" and localrtTable ~= nil and localrates ~= nil and #localrates > 0 then + if selectPressed then + localrateIndex = clamp(localrateIndex+1, 1, #localrates) + if localrateIndex > ratepage * ratecount then + ratepage = ratepage + 1 + end + else + local max = #localrtTable[localrates[localrateIndex]] + local beforeindex = localscoreIndex + localscoreIndex = clamp(localscoreIndex+1, 1, max) + end + self:GetParent():GetParent():playcommand("UpdateList") + elseif event.button == "EffectDown" and localrtTable ~= nil and localrates ~= nil and #localrates > 0 then + if selectPressed then + localrateIndex = clamp(localrateIndex-1, 1, #localrates) + if localrateIndex < (ratepage-1) * ratecount + 1 then + ratepage = ratepage - 1 + end + else + local max = #localrtTable[localrates[localrateIndex]] + local beforeindex = localscoreIndex + localscoreIndex = clamp(localscoreIndex-1, 1, max) + end + self:GetParent():GetParent():playcommand("UpdateList") + elseif event.button == "Select" then + selectPressed = true + end + elseif event.type == "InputEventType_Release" then + if event.button == "Select" then + selectPressed = false + end + end + end) + end, + UpdateScoresCommand = function(self) + maxratepage = 1 + ratepage = 1 + end, + UpdateListCommand = function(self) + if localrtTable ~= nil and localrates ~= nil then + maxratepage = math.ceil(#localrates / ratecount) + else + maxratepage = 1 + end + end, + + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "PageUp", + InitCommand = function(self) + self:valign(0) + self:xy(actuals.ScoreRateListWidth / 2, 0) + self:settext("^") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + UpdateListCommand = function(self) + if ratepage-1 < 1 or maxratepage == 1 then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + moveRatePage(-1) + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "PageDown", + InitCommand = function(self) + self:valign(0) + self:xy(actuals.ScoreRateListWidth / 2, actuals.ScoreRateListBottomTopGap) + self:rotationz(180) + self:settext("^") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + UpdateListCommand = function(self) + if ratepage+1 > maxratepage or maxratepage == 1 then + self:diffusealpha(0) + else + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + moveRatePage(1) + end + } + } + + for i = 1, ratecount do + t[#t+1] = rateItem(i) + end + + return t + end + + + for i = 1, itemCount do + t[#t+1] = createItem(i) + end + local extrasizing = 75 / 1920 * SCREEN_WIDTH -- extra size added to the plot for padding stuff + local graphbgalpha = 0.9 + t[#t+1] = Def.ActorFrame { + Name = "OnlineOffsetPlot", + InitCommand = function(self) + self:diffusealpha(0) + self:z(-200) + end, + DisplayOnlineOffsetsCommand = function(self, params) + -- very very very roughly the remaining width of the screen opposite of the scores tab, but lowered to 95% + local zoombias = (1 + (actuals.MainGraphicWidth + extrasizing) / (SCREEN_WIDTH - actuals.ItemWidth)) * 0.95 + -- get the x pos based on the wheel position + local finalxPos = getWheelPosition() and ((-actuals.MainGraphicWidth - extrasizing/2) * zoombias) or (actuals.ItemWidth + extrasizing) + self:finishtweening() + self:diffusealpha(0) + self:x(actuals.MainGraphicWidth) + self:y((actuals.ItemAllottedSpace / (itemCount - 1)) * (params.index-1) + actuals.ItemUpperSpacing) + self:zoom(0) + self:zoomy(0) + self:z(-200) + self:decelerate(0.3) + self:diffusealpha(1) + self:xy(finalxPos, 0) + self:z(10) + self:zoom(zoombias) + self:zoomy(zoombias) + end, + HidePlotCommand = function(self) + self:finishtweening() + self:accelerate(0.3) + self:xy(actuals.MainGraphicWidth, 0) + self:zoom(0):zoomy(0) + self:z(-200) + self:diffusealpha(0) + end, + + UIElements.QuadButton(1, 1) .. { + Name = "BG", + InitCommand = function(self) + self:valign(0):halign(0) + self:xy(-extrasizing / 2, -extrasizing) + self:zoomto(actuals.MainGraphicWidth + extrasizing, actuals.OffsetPlotHeight + extrasizing * 2) + self:diffusealpha(graphbgalpha) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end, + MouseDownCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(graphbgalpha) + self:GetParent():playcommand("HidePlot") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha * graphbgalpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(graphbgalpha) + end, + }, + LoadFont("Common Normal") .. { + Name = "TopText", + InitCommand = function(self) + self:xy(actuals.MainGraphicWidth / 2, -extrasizing / 2) + self:zoom(onlinePlotTextSize) + self:maxwidth(((actuals.MainGraphicWidth)) / onlinePlotTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + DisplayOnlineOffsetsCommand = function(self, params) + local score = params.score + if score == nil then self:settext("") return end + local ws = score:GetWifeScore() + local wifeStr = checkWifeStr(ws) + -- keeping these plots j4 because i dont want to complicate logic for stuff + --local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or table.find(ms.JudgeScalers, notShit.round(score:GetJudgeScale(), 2))) or GetTimingDifficulty() + self:settextf("Showing J4 Plot | Score by: %s | %s", score:GetName(), wifeStr) + end, + }, + LoadFont("Common Normal") .. { + Name = "BottomText", + InitCommand = function(self) + self:xy(actuals.MainGraphicWidth / 2, actuals.OffsetPlotHeight + extrasizing / 2) + self:zoom(onlinePlotTextSize) + self:maxwidth(((actuals.MainGraphicWidth)) / onlinePlotTextSize) + self:settext("Click this box or do anything to close") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + }, + LoadActorWithParams("../../offsetplot.lua", {sizing = {Width = actuals.MainGraphicWidth, Height = actuals.OffsetPlotHeight}, textsize = 0.43}) .. { + DisplayOnlineOffsetsCommand = function(self, params) + local score = params.score + if score == nil then return end + local steps = GAMESTATE:GetCurrentSteps() + -- keeping these plots j4 because i dont want to complicate logic for stuff + --local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or table.find(ms.JudgeScalers, notShit.round(score:GetJudgeScale(), 2))) or GetTimingDifficulty() + local judgeSetting = 4 + if steps ~= nil then + if score:HasReplayData() then + local offsets = score:GetOffsetVector() + -- for online offset vectors a 180 offset is a miss + for i, o in ipairs(offsets) do + if o >= 180 then + offsets[i] = 1000 + end + end + local tracks = score:GetTrackVector() + local types = score:GetTapNoteTypeVector() + local noterows = score:GetNoteRowVector() + local holds = score:GetHoldNoteVector() + local timingdata = steps:GetTimingData() + local lastSecond = steps:GetLastSecond() + + self:playcommand("LoadOffsets", { + offsetVector = offsets, + trackVector = tracks, + timingData = timingdata, + noteRowVector = noterows, + typeVector = types, + holdVector = holds, + maxTime = lastSecond, + judgeSetting = judgeSetting, + columns = steps:GetNumColumns(), + rejudged = true, + }) + self:hurrytweening(0.2) + else + self:playcommand("LoadOffsets", { + offsetVector = {}, + trackVector = {}, + timingData = nil, + noteRowVector = {}, + typeVector = {}, + holdVector = {}, + maxTime = 1, + judgeSetting = 4, + columns = steps:GetNumColumns(), + rejudged = true, + }) + self:hurrytweening(0.1) + end + end + end + }, + } + + -- we have placed the local page into its own frame for management of everything quickly + -- when local, this is visible + -- when online, this is not visible (and the score list should show up) + -- etc + t[#t+1] = Def.ActorFrame { + Name = "LocalScorePageFrame", + InitCommand = function(self) + self:y(actuals.UpperLipHeight) + end, + UpdateListCommand = function(self) + if localscore ~= nil then + self:finishtweening() + self:smooth(localPageAnimationSeconds) + self:z(2) + self:diffusealpha(1) + else + self:finishtweening() + self:smooth(localPageAnimationSeconds) + self:z(-2) + self:diffusealpha(0) + end + end, + + LoadFont("Common Normal") .. { + Name = "Grade", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.SideBufferGap, actuals.GradeUpperGap) + self:zoom(gradeTextSize) + self:maxwidth((actuals.DetailLineLeftGap - actuals.SideBufferGap) / gradeTextSize - textzoomFudge) + self:settext("") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateList") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local grade = THEME:GetString("Grade", ToEnumShortString(localscore:GetWifeGrade())) + self:diffuse(colorByGrade(localscore:GetWifeGrade())) + self:settext(grade) + end + end + }, + LoadFont("Common Normal") .. { + Name = "ClearType", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.SideBufferGap, actuals.ClearTypeUpperGap) + self:zoom(clearTypeTextSize) + self:maxwidth((actuals.DetailLineLeftGap - actuals.SideBufferGap) / clearTypeTextSize - textzoomFudge) + self:settext("") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateList") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local ct = getClearTypeFromScore(localscore, 0) + self:diffuse(getClearTypeFromScore(localscore, 2)) + self:settext(ct) + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "upload")) .. { + Name = "UploadButton", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.SideBufferGap, actuals.IconSetUpperGap) + self:zoomto(actuals.PlaySize, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + if localscore:HasReplayData() and DLMAN:IsLoggedIn() then + self:diffusealpha(1) + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + if WHEELDATA:GetCurrentSort() == 1 then + TOOLTIP:SetText("Upload Score\nShift: All in pack") + else + TOOLTIP:SetText("Upload Score") + end + TOOLTIP:Show() + end + else + self:diffusealpha(0) + if isOver(self) then + TOOLTIP:Hide() + end + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + if WHEELDATA:GetCurrentSort() == 1 then + TOOLTIP:SetText("Upload Score\nShift: All in pack") + else + TOOLTIP:SetText("Upload Score") + end + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + -- holding shift in the group sort causes a pack score upload + if INPUTFILTER:IsShiftPressed() and WHEELDATA:GetCurrentSort() == 1 then + DLMAN:UploadScoresForPack(GAMESTATE:GetCurrentSong():GetGroupName()) + ms.ok("Uploading all scores for pack: "..GAMESTATE:GetCurrentSong():GetGroupName()) + else + if localscore ~= nil and localscore:HasReplayData() then + -- important to note this is actually for uploading replays. + -- misnomer. .. bad functionality? oh no + DLMAN:SendReplayDataForOldScore(localscore:GetScoreKey()) + local steps = GAMESTATE:GetCurrentSteps() + ms.ok(string.format("Uploading Score (chart %s key %s)", steps:GetChartKey(), localscore:GetScoreKey())) + end + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "showEval")) .. { + Name = "ShowEval", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.SideBufferGap + actuals.PlaySize + actuals.IconSetSpacing, actuals.IconSetUpperGap) + self:zoomto(actuals.TrophySize, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + if localscore:HasReplayData() then + self:diffusealpha(1) + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Show Evaluation") + TOOLTIP:Show() + end + else + self:diffusealpha(0) + if isOver(self) then + TOOLTIP:Hide() + end + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Show Evaluation") + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if localscore ~= nil and localscore:HasReplayData() then + if SCREENMAN:GetTopScreen():GetMusicWheel():SelectSong(GAMESTATE:GetCurrentSong()) then + local success = SCREENMAN:GetTopScreen():ShowEvalScreenForScore(localscore) + if success then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + end + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "showReplay")) .. { + Name = "ShowReplay", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.SideBufferGap + actuals.PlaySize + actuals.TrophySize + actuals.IconSetSpacing * 2, actuals.IconSetUpperGap) + self:zoomto(actuals.PlaySize, actuals.IconHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + if localscore:HasReplayData() then + self:diffusealpha(1) + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Show Replay") + TOOLTIP:Show() + end + else + self:diffusealpha(0) + if isOver(self) then + TOOLTIP:Hide() + end + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:SetText("Show Replay") + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if localscore ~= nil and localscore:HasReplayData() then + if SCREENMAN:GetTopScreen():GetMusicWheel():SelectSong(GAMESTATE:GetCurrentSong()) then + local success = SCREENMAN:GetTopScreen():PlayReplay(localscore) + if success then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "SSRPercentWVJudge", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.DetailLineLeftGap, actuals.DetailLine1TopGap) + self:zoom(detailTextSize) + self:maxwidth((actuals.Width - actuals.SideBufferGap - actuals.DetailLineLeftGap) / detailTextSize - textzoomFudge) + self:settext("11.11 | 11.11% (Wife 0 | Judge 0)") + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateList") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local ssrstr = string.format("%5.2f", localscore:GetSkillsetSSR("Overall")) + local ssrcolr = colorByMSD(localscore:GetSkillsetSSR("Overall")) + local wife = localscore:GetWifeScore() + local wifecolr = colorByGrade(localscore:GetWifeGrade()) + local wv = "Wife "..localscore:GetWifeVers() + local judge = 4 + if PREFSMAN:GetPreference("SortBySSRNormPercent") == false then + judge = table.find(ms.JudgeScalers, notShit.round(localscore:GetJudgeScale(), 2)) + end + if not judge then judge = 4 end + if judge < 4 then judge = 4 end + local js = judge ~= 9 and judge or "Justice" + local perc = checkWifeStr(wife) + self:ClearAttributes() + self:diffuse(COLORS:getMainColor("PrimaryText")) + self:diffusealpha(1) + self:settextf("%s | %s (%s | Judge %s)", ssrstr, perc, wv, js) + self:AddAttribute(0, {Length = #ssrstr, Diffuse = ssrcolr}) + self:AddAttribute(#string.format("%s | ", ssrstr), {Length = #perc, Diffuse = wifecolr}) + end + end + }, + LoadFont("Common Normal") .. { + Name = "CBs", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.DetailLineLeftGap, actuals.DetailLine2TopGap) + self:zoom(detailTextSize) + self:maxwidth((actuals.Width - actuals.SideBufferGap - actuals.DetailLineLeftGap) / detailTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local mc = getScoreComboBreaks(localscore) + if mc ~= nil then + self:settextf("Combo Breaks: %s", mc) + else + self:settext("Combo Breaks: -") + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "MaxCombo", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.DetailLineLeftGap, actuals.DetailLine3TopGap) + self:zoom(detailTextSize) + self:maxwidth((actuals.Width - actuals.SideBufferGap - actuals.DetailLineLeftGap) / detailTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local mc = localscore:GetMaxCombo() + self:settextf("Max Combo: %d", mc) + end + end + }, + LoadFont("Common Normal") .. { + Name = "DateAchieved", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.DetailLineLeftGap, actuals.DetailLine4TopGap) + self:zoom(detailTextSize) + self:maxwidth((actuals.Width - actuals.SideBufferGap - actuals.DetailLineLeftGap) / detailTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local d = getScoreDate(localscore) + self:settextf("Date Achieved: %s", d) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Mods", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.DetailLineLeftGap, actuals.DetailLine5TopGap) + self:zoom(detailTextSize) + self:maxwidth((actuals.Width - actuals.SideBufferGap - actuals.DetailLineLeftGap) / detailTextSize - textzoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local m = getModifierTranslations(localscore:GetModifiers()) + self:settextf("Mods: %s", m) + end + end + }, + localRateFrame() .. { + InitCommand = function(self) + self:xy(actuals.ScoreRateListLeftGap, actuals.ScoreRateListTopGap) + end + }, + LoadActorWithParams("../../judgmentBars.lua", { + sizing = { + JudgmentBarHeight = actuals.JudgmentBarHeight, + JudgmentBarLength = actuals.JudgmentBarLength, + JudgmentBarSpacing = actuals.JudgmentBarSpacing, + JudgmentBarAllottedSpace = actuals.JudgmentBarAllottedSpace, + JudgmentNameLeftGap = actuals.JudgmentNameLeftGap, + JudgmentCountRightGap = actuals.JudgmentCountRightGap, + }, + textSizeMultiplier = 0.6 + }) .. { + InitCommand = function(self) + self:xy(actuals.SideBufferGap, actuals.JudgmentBarsTopGap) + end, + UpdateListCommand = function(self) + if localscore ~= nil then + local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or table.find(ms.JudgeScalers, notShit.round(localscore:GetJudgeScale(), 2))) + self:playcommand("Set", {score = localscore, judgeSetting = judgeSetting}) + end + end + }, + LoadActorWithParams("../../offsetplot.lua", {sizing = {Width = actuals.MainGraphicWidth, Height = actuals.OffsetPlotHeight}, textsize = 0.43}) .. { + InitCommand = function(self) + self:xy(actuals.SideBufferGap, actuals.OffsetPlotUpperGap) + end, + UpdateListCommand = function(self, params) + if localscore == nil then return end + local steps = GAMESTATE:GetCurrentSteps() + local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or table.find(ms.JudgeScalers, notShit.round(localscore:GetJudgeScale(), 2))) + if steps ~= nil then + if localscore:HasReplayData() then + local offsets = localscore:GetOffsetVector() + -- for online offset vectors a 180 offset is a miss + for i, o in ipairs(offsets) do + if o >= 180 then + offsets[i] = 1000 + end + end + local tracks = localscore:GetTrackVector() + local types = localscore:GetTapNoteTypeVector() + local noterows = localscore:GetNoteRowVector() + local holds = localscore:GetHoldNoteVector() + local timingdata = steps:GetTimingData() + local lastSecond = steps:GetLastSecond() + + self:playcommand("LoadOffsets", { + offsetVector = offsets, + trackVector = tracks, + timingData = timingdata, + noteRowVector = noterows, + typeVector = types, + holdVector = holds, + maxTime = lastSecond, + judgeSetting = judgeSetting, + columns = steps:GetNumColumns(), + rejudged = true, + }) + self:hurrytweening(0.2) + else + self:playcommand("LoadOffsets", { + offsetVector = {}, + trackVector = {}, + timingData = nil, + noteRowVector = {}, + typeVector = {}, + holdVector = {}, + maxTime = 1, + judgeSetting = 4, + columns = steps:GetNumColumns(), + rejudged = true, + }) + self:hurrytweening(0.1) + end + end + end + } + } + + + t[#t+1] = Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if isLocal then + if localrtTable ~= nil and localrates ~= nil and #localrates > 0 then + local max = #localrtTable[localrates[localrateIndex]] + local beforeindex = localscoreIndex + if params.direction == "Up" then + localscoreIndex = clamp(localscoreIndex-1, 1, max) + else + localscoreIndex = clamp(localscoreIndex+1, 1, max) + end + if localscoreIndex ~= beforeindex then + self:GetParent():playcommand("UpdateList") + end + end + else + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end + end + } + + t[#t+1] = LoadFont("Common Normal") .. { + Name = "LoadingText", + InitCommand = function(self) + self:y((actuals.ItemAllottedSpace / (itemCount - 1)) + actuals.ItemUpperSpacing) + self:x(actuals.Width / 2) + self:maxwidth(actuals.Width / loadingTextSize - textzoomFudge) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + self:finishtweening() + self:smooth(scoreListAnimationSeconds) + local steps = GAMESTATE:GetCurrentSteps() + + if isLocal then + if localrtTable == nil and GAMESTATE:GetCurrentSong() ~= nil then + self:diffusealpha(1) + self:settext("No local scores recorded") + else + self:diffusealpha(0) + self:settext("") + end + return + end + + if scores == nil then + self:diffusealpha(1) + self:settext("Chart is unranked") + elseif #scores == 0 and steps and fetchingScores[steps:GetChartKey()] == true then + self:diffusealpha(1) + self:settext("Fetching scores...") + elseif #scores == 0 and steps and fetchingScores[steps:GetChartKey()] == false then + self:diffusealpha(1) + self:settext("No online scores recorded") + elseif isLocal and localscore == nil then + self:diffusealpha(1) + self:settext("No local scores recorded") + else + self:diffusealpha(0) + self:settext("") + end + end + } + + -- the sizing on this button is slightly too big but hey it can never get too big + t[#t+1] = UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(1):valign(0) + bg:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.PageTextUpperGap) + txt:zoom(pageTextSize) + txt:maxwidth(actuals.Width / pageTextSize - textzoomFudge) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + UpdateListCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- nil scores = no scores + if scores == nil then + txt:settext("0-0/0") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + return + end + + if isLocal then + if localrtTable ~= nil and localrates ~= nil and #localrates > 0 then + local mx = #localrtTable[localrates[localrateIndex]] + txt:settextf("%d/%d", localscoreIndex, mx) + else + txt:settext("") + end + else + local lb = clamp((page-1) * (itemCount) + 1, 0, #scores) + local ub = clamp(page * itemCount, 0, #scores) + txt:settextf("%d-%d/%d", lb, ub, #scores) + end + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + ClickCommand = function(self, params) + if params.update ~= "OnMouseDown" then return end + local dir = 0 + if params.event == "DeviceButton_left mouse button" then + dir = 1 + elseif params.event == "DeviceButton_right mouse button" then + dir = -1 + else + return + end + if focused then + if isLocal then + if localrtTable ~= nil and localrates ~= nil and #localrates > 0 then + local max = #localrtTable[localrates[localrateIndex]] + local beforeindex = localscoreIndex + localscoreIndex = localscoreIndex + dir + if localscoreIndex > max then localscoreIndex = 1 end + if localscoreIndex < 1 then localscoreIndex = max end + if localscoreIndex ~= beforeindex then + self:GetParent():playcommand("UpdateList") + end + end + else + movePage(dir) + end + end + end, + RolloverUpdateCommand = function(self, params) + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end, + } + + -- this list defines the appearance of the lower lip buttons + -- the text can be anything + -- preferably the defaults are the first position + -- the inner lists have to be length 2 + local choiceNames = { + {"Show Online", "Show Local"}, + {"Top Scores", "All Scores"}, + {"Hide Invalid", "Show Invalid"}, + {"Current Rate", "All Rates"}, + } + + -- this list defines how each choice finds its default choiceName index + -- basically, we dont always want to pick 1 + -- and we want the indices dependent on a lot of variables + -- like maybe you want one thing to be 1 if true but something else 1 if false + -- should return true for index 1 and false for index 2 + local choiceTextIndexGetters = { + function() -- local/online + return isLocal + end, + + function() -- top/all scores + return not allScores + end, + + function() -- invalid score toggle + -- true means invalid scores are hidden + return not DLMAN:GetCCFilter() + end, + + function() -- current/all rates + return (DLMAN:GetCurrentRateFilter() and not isLocal) + end, + } + + -- what happens when you click each button corresponding to the above list + -- actor tree: + -- File, ExtraFrameInBetween, ChoiceParentFrame, self + local choiceFunctions = { + -- Local/Online + function(self) + self:GetParent():GetParent():playcommand("ToggleLocal") + end, + + -- Top/All Scores + function(self) + self:GetParent():GetParent():playcommand("ToggleAllScores") + end, + + -- Invalid Score Toggle + function(self) + self:GetParent():GetParent():playcommand("ToggleInvalid") + end, + + -- Current/All Rate + function(self) + self:GetParent():GetParent():playcommand("ToggleCurrentRate") + end, + } + + -- which choices should only appear if in the online tab + local choiceOnlineOnly = { + false, -- local/online + true, -- top/all scores + true, -- toggle invalid scores + true, -- current/all rates + } + + local choiceTextSize = 0.7 + + local function createChoices() + local function createChoice(i) + -- controls the position of the index in the choiceNames sublist + local nameIndex = (choiceTextIndexGetters[i]() and 1 or 2) + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Button_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceNames) * (i-1) + (actuals.Width / #choiceNames / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceNames / choiceTextSize - textzoomFudge) + txt:settext(choiceNames[i][nameIndex]) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceNames, actuals.UpperLipHeight) + + self.visibleOnlineCheck = function(self) + if choiceOnlineOnly[i] and isLocal or not DLMAN:IsLoggedIn() then + self:diffusealpha(0) + return false + else + self:diffusealpha(1) + return true + end + end + self.hoverAlphaCheck = function(self) + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + end, + UpdateToggleStatusCommand = function(self) + -- for online only elements, hide if not online + if not self:visibleOnlineCheck() then return end + + -- have to separate things because order of execution gets wacky + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + nameIndex = (choiceTextIndexGetters[i]() and 1 or 2) + txt:settext(choiceNames[i][nameIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if choiceFunctions[i] then + choiceFunctions[i](self) + end + self:playcommand("UpdateText") + self:hoverAlphaCheck() + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:hoverAlphaCheck() + end + } + end + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.UpperLipHeight / 2) + end, + UpdateButtonsMessageCommand = function(self) + allScores = not DLMAN:GetTopScoresOnlyFilter() + self:playcommand("UpdateToggleStatus") + end, + UpdateLoginStatusCommand = function(self) + self:playcommand("UpdateToggleStatus") + end, + } + for i = 1, #choiceNames do + t[#t+1] = createChoice(i) + end + return t + end + + t[#t+1] = createChoices() + + return t +end + +t[#t+1] = createList() + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/tags.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/tags.lua new file mode 100644 index 0000000000..13746e0081 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/generalPages/tags.lua @@ -0,0 +1,643 @@ +local focused = false +local t = Def.ActorFrame { + Name = "TagsPageFile", + InitCommand = function(self) + -- hide all general box tabs on startup + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + if params and params.tab ~= nil then + if params.tab == SCUFF.tagstabindex then + self:z(200) + self:smooth(0.2) + self:diffusealpha(1) + focused = true + self:playcommand("UpdateTagsTab") + else + self:z(-100) + self:smooth(0.2) + self:diffusealpha(0) + focused = false + end + end + end, + WheelSettledMessageCommand = function(self, params) + if not focused then return end + self:playcommand("UpdateTagsTab") + end, + ChangedStepsMessageCommand = function(self, params) + if not focused then return end + self:playcommand("UpdateTagsTab") + end +} + +local ratios = { + EdgeBuffer = 11 / 1920, -- intended buffer from leftmost edge, rightmost edge, and distance away from center of frame + UpperLipHeight = 43 / 1080, + LipSeparatorThickness = 2 / 1080, + + PageTextRightGap = 33 / 1920, -- right of frame, right of text + PageNumberUpperGap = 48 / 1080, -- bottom of upper lip to top of text + + ItemListUpperGap = 35 / 1080, -- bottom of upper lip to top of topmost item + ItemAllottedSpace = 435 / 1080, -- top of topmost item to top of bottommost item + ItemSpacing = 45 / 1080, -- top of item to top of next item +} + +local actuals = { + EdgeBuffer = ratios.EdgeBuffer * SCREEN_WIDTH, + UpperLipHeight = ratios.UpperLipHeight * SCREEN_HEIGHT, + LipSeparatorThickness = ratios.LipSeparatorThickness * SCREEN_HEIGHT, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + PageNumberUpperGap = ratios.PageNumberUpperGap * SCREEN_HEIGHT, + ItemListUpperGap = ratios.ItemListUpperGap * SCREEN_HEIGHT, + ItemAllottedSpace = ratios.ItemAllottedSpace * SCREEN_HEIGHT, + ItemSpacing = ratios.ItemSpacing * SCREEN_HEIGHT, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +local tagTextSize = 1.2 +local pageTextSize = 0.7 + +local choiceTextSize = 0.7 +local buttonHoverAlpha = 0.6 +local textzoomFudge = 5 + +local tagListAnimationSeconds = 0.03 + +local function tagList() + -- modifiable parameters + local tagsPerColumn = 10 + local columns = 2 + + -- internal var storage + local storedTags = {} -- exact tag list, keys are tags, values are {chartkeys : 1} or {chartkey : nil} + local tagNameList = {} -- just a list of all tags so it can be indexed in a consistent order + local excludedTags = {} -- a list but instead of being indexed, the keys are the tags + local requiredTags = {} -- same as above + local page = 1 + local maxPage = 1 + + -- determines the mode of the tag list thing + -- accepts "Assign", "Require", "Exclude", "Delete" + local tagListMode = "Assign" + + -- just a list of the modes that will allow for mouse hover highlighting + -- this isnt necessary at the moment, but just in case i guess + local tagListModeForClicking = { + Assign = true, + Require = true, + Exclude = true, + Delete = true, + } + + local tagsAssignedToCurrentChart = {} + + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + end + + local function tagListItem(i) + local column = math.floor((i-1) / tagsPerColumn) + local allowedWidth = (actuals.Width / columns - actuals.EdgeBuffer * 2) + local index = i + local tag = "" + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "TagButton_"..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(0) + bg:halign(0) + + -- this should make it so that the left column (0) is at EdgeBuffer and the right column (1) is in the middle-ish + -- and if the column count is changed, it should adjust accordingly + self:x(actuals.EdgeBuffer + column * actuals.Width / columns) + self:y(actuals.UpperLipHeight + actuals.ItemListUpperGap + actuals.ItemAllottedSpace / tagsPerColumn * (i-1 - column * tagsPerColumn)) + txt:zoom(tagTextSize) + txt:maxwidth(allowedWidth / tagTextSize - textzoomFudge) + txt:settext(" ") + bg:zoomto(allowedWidth, actuals.UpperLipHeight) + bg:y(txt:GetZoomedHeight() / 2) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateTagList") + end, + UpdateTagListCommand = function(self) + local txt = self:GetChild("Text") + index = (page-1) * columns * tagsPerColumn + i + tag = tagNameList[index] + self:finishtweening() + self:diffusealpha(0) + if tag ~= nil and tag ~= "" then + self:smooth(tagListAnimationSeconds * i) + self:diffuse(COLORS:getMainColor("PrimaryText")) + txt:settext(tag) + + if tagListMode == "Assign" then + -- color if assigned on this chart + local chart = GAMESTATE:GetCurrentSteps() + if chart ~= nil then + local ck = chart:GetChartKey() + if storedTags[tag][ck] then + self:diffuse(COLORS:getColor("generalBox", "AssignedTag")) + end + end + elseif tagListMode == "Require" then + -- color if required + if requiredTags[tag] then + self:diffuse(COLORS:getColor("generalBox", "RequiredTag")) + end + elseif tagListMode == "Exclude" then + -- color if excluded + if excludedTags[tag] then + self:diffuse(COLORS:getColor("generalBox", "FilteredTag")) + end + end + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if tagListModeForClicking[tagListMode] == nil then return end + + if tagListMode == "Assign" then + -- you cant assign a tag to nothing + local chart = GAMESTATE:GetCurrentSteps() + if chart ~= nil then + local ck = chart:GetChartKey() + if storedTags[tag][ck] then + TAGMAN:get_data().playerTags[tag][ck] = nil + else + TAGMAN:get_data().playerTags[tag][ck] = 1 + end + TAGMAN:set_dirty() + TAGMAN:save() + self:GetParent():playcommand("UpdateTagList") + MESSAGEMAN:Broadcast("ReassignedTags") + end + + elseif tagListMode == "Require" then + if requiredTags[tag] == nil then + requiredTags[tag] = true + else + requiredTags[tag] = nil + end + local newtable = {} + for tagname, _ in pairs(requiredTags) do + newtable[#newtable+1] = tagname + end + WHEELDATA:SetRequiredTags(newtable) + self:GetParent():playcommand("UpdateTagList") + + elseif tagListMode == "Exclude" then + if excludedTags[tag] == nil then + excludedTags[tag] = true + else + excludedTags[tag] = nil + end + local newtable = {} + for tagname, _ in pairs(excludedTags) do + newtable[#newtable+1] = tagname + end + WHEELDATA:SetExcludedTags(newtable) + self:GetParent():playcommand("UpdateTagList") + + elseif tagListMode == "Delete" then + if excludedTags[tag] then excludedTags[tag] = nil end + if requiredTags[tag] then requiredTags[tag] = nil end + + -- delete tag record + TAGMAN:get_data().playerTags[tag] = nil + TAGMAN:set_dirty() + TAGMAN:save() + + self:GetParent():playcommand("DeletedTag") + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + if tagListModeForClicking[tagListMode] ~= nil then + self:diffusealpha(buttonHoverAlpha) + end + else + self:diffusealpha(1) + end + end + } + end + + local function tagChoices() + -- keeping track of which choices are on at any moment (a list of indices) + -- setting 1 to be true initially because tagListMode is set to Assign and there needs to be consistency there + local activeChoices = {[1]=true} + + -- identify each choice using this table + -- Name: The name of the choice (NOT SHOWN TO THE USER) + -- Type: Toggle/Exclusive/Tap + -- Toggle - This choice can be clicked multiple times to scroll through choices + -- Exclusive - This choice is one out of a set of Exclusive choices. Only one Exclusive choice can be picked at once + -- Tap - This choice can only be pressed (if visible by Condition) and will only run TapFunction at that time + -- Display: The string the user sees. One option for each choice must be given if it is a Toggle choice + -- Condition: A function that returns true or false. Determines if the choice should be visible or not + -- IndexGetter: A function that returns an index for its status, according to the Displays set + -- TapFunction: A function that runs when the button is pressed + local choiceDefinitions = { + { -- Button to assign tags to charts + Name = "assign", + Type = "Exclusive", + Display = {"Assign"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + tagListMode = "Assign" + end, + }, + { -- Button to filter charts by tags (Require charts have these tags) + Name = "filter", + Type = "Exclusive", + Display = {"Require Tag"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + tagListMode = "Require" + end, + }, + { -- Button to filter charts by tags (Hide charts with these tags) + Name = "hide", + Type = "Exclusive", + Display = {"Hide Tag"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + tagListMode = "Exclude" + end, + }, + { -- Button to change filter mode AND/OR + Name = "filtermode", + Type = "Toggle", + Display = {"Mode: AND", "Mode: OR"}, + IndexGetter = function() + if tagListMode == "Require" then + return WHEELDATA:GetRequiredTagMode() and 1 or 2 + elseif tagListMode == "Exclude" then + return WHEELDATA:GetExcludedTagMode() and 1 or 2 + else + return 1 -- dont care + end + end, + Condition = function() return tagListMode == "Exclude" or tagListMode == "Require" end, + TapFunction = function() + if tagListMode == "Require" then + WHEELDATA:SetRequiredTagMode() + elseif tagListMode == "Exclude" then + WHEELDATA:SetExcludedTagMode() + else + -- nothing + end + end, + }, + { -- Button to delete tags + Name = "delete", + Type = "Exclusive", + Display = {"Delete", "Deleting Tag"}, + IndexGetter = function() + if tagListMode == "Delete" then + return 2 + else + return 1 + end + end, + Condition = function() return #tagNameList > 0 end, + TapFunction = function() + if tagListMode == "Delete" then + tagListMode = "Assign" + else + tagListMode = "Delete" + end + end, + }, + { -- Button to create tags + Name = "new", + Type = "Tap", + Display = {"New"}, + Condition = function() return true end, + IndexGetter = function() return 1 end, + TapFunction = function() + local redir = SCREENMAN:get_input_redirected(PLAYER_1) + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + off() + -- input redirects are controlled here because we want to be careful not to break any prior redirects + askForInputStringWithFunction( + "Enter New Tag Name", + 128, + false, + function(answer) + -- success if the answer isnt blank + if answer:gsub("^%s*(.-)%s*$", "%1") ~= "" then + MESSAGEMAN:Broadcast("CreateNewTag", {name = answer}) + else + on() + end + end, + function() return true, "" end, + function() + on() + end + ) + end, + }, + { -- Button to apply tag filter changes to music wheel + Name = "apply", + Type = "Tap", + Display = {"Apply"}, + Condition = function() return true end, + IndexGetter = function() return 1 end, + TapFunction = function() + -- really all this does is trigger a search + -- since the filter is always set to what you visually see, you just have to reload the wheel + local scr = SCREENMAN:GetTopScreen() + local w = scr:GetChild("WheelFile") + if w ~= nil then + w:sleep(0.01):queuecommand("ApplyFilter") + end + -- but we dont change the input context to keep it from being too jarring + end, + }, + { -- Button to reset tag filters + Name = "reset", + Type = "Tap", + Display = {"Reset"}, + Condition = function() return true end, + IndexGetter = function() return 1 end, + TapFunction = function() + -- resets tags and triggers a search + local scr = SCREENMAN:GetTopScreen() + local w = scr:GetChild("WheelFile") + if w ~= nil then + WHEELDATA:ResetExcludedTags() + WHEELDATA:ResetRequiredTags() + w:sleep(0.01):queuecommand("ApplyFilter") + end + -- but we dont change the input context to keep it from being too jarring + end, + }, + } + + local function createChoice(i) + local definition = choiceDefinitions[i] + local displayIndex = 1 + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ChoiceButton_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceDefinitions) * (i-1) + (actuals.Width / #choiceDefinitions / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceDefinitions / choiceTextSize - textzoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceDefinitions, actuals.UpperLipHeight) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- update index + displayIndex = definition.IndexGetter() + + -- update visibility by condition + if definition.Condition() then + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + else + self:diffusealpha(0) + end + + if activeChoices[i] then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.75)) + else + txt:strokecolor(color("0,0,0,0")) + end + + -- update display + txt:settext(definition.Display[displayIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- exclusive choices cause activechoices to be forced to this one + if definition.Type == "Exclusive" then + activeChoices = {[i]=true} + else + -- uhh i didnt implement any other type that would ... be used for.. this + end + + -- run the tap function + if definition.TapFunction ~= nil then + definition.TapFunction() + end + self:GetParent():GetParent():playcommand("UpdateTagList") + self:GetParent():playcommand("UpdateText") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.UpperLipHeight / 2) + end, + DeletedTagCommand = function(self) + -- reset choice to Assign + activeChoices = {[1]=true} + self:playcommand("UpdateText") + end, + } + + for i = 1, #choiceDefinitions do + t[#t+1] = createChoice(i) + end + + return t + end + + local t = Def.ActorFrame { + Name = "TagListFrame", + BeginCommand = function(self) + self:playcommand("UpdateTagList") + self:playcommand("UpdateText") + end, + UpdateTagsTabCommand = function(self) + page = 1 + self:playcommand("UpdateTagList") + self:playcommand("UpdateText") + end, + UpdateTagListCommand = function(self) + -- this sets all the data things over and over and over + -- but its all in one place and is only called once every time you touch the tag list stuff + -- ... so its probably slow but only as slow as it needs to be + -- (make sure you dont let this get called if you arent looking at the tag tab because thats a waste) + -- (but if you then decide to look at the tag tab you should probably run this) + storedTags = TAGMAN:get_data().playerTags + tagNameList = {} + for k, _ in pairs(storedTags) do + tagNameList[#tagNameList+1] = k + end + table.sort( + tagNameList, + function(a,b) return a:lower() < b:lower() end + ) + maxPage = math.ceil(#tagNameList / (columns * tagsPerColumn)) + + requiredTags = {} + excludedTags = {} + for _, t in ipairs(WHEELDATA:GetRequiredTags()) do + requiredTags[t] = true + end + for _, t in ipairs(WHEELDATA:GetExcludedTags()) do + excludedTags[t] = true + end + end, + CreateNewTagMessageCommand = function(self, params) + -- only create tag if the name isnt blank + if params ~= nil and params.name ~= nil and params.name ~= "" then + -- and dont make duplicate tags + -- (but allow alternate capitalization ...) + if storedTags[params.name] == nil then + TAGMAN:get_data().playerTags[params.name] = {} + TAGMAN:set_dirty() + TAGMAN:save() + self:playcommand("UpdateTagList") + end + end + end, + DeletedTagCommand = function(self) + -- just a little hacky but im putting this bit here so its all in one place + -- on tag deletions we want to reset back to the assign state so you dont keep deleting things + -- its better to be forced to be slow to delete than to accidentally delete tags over and over + tagListMode = "Assign" + self:playcommand("UpdateTagList") + -- from here the next thing that happens is the choice frame will take this Command + -- it will reset the choice to Assign to match + -- and that part is hardcoded because ive unfortunately run out of patience + -- (fortunately im like 95% done with this) + end, + + tagChoices(), + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + self:GetParent():playcommand("UpdateTagList") + end + end + }, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.PageNumberUpperGap) + self:zoom(pageTextSize) + self:maxwidth(actuals.Width / pageTextSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateTagListCommand = function(self) + local lb = clamp((page-1) * (columns * tagsPerColumn) + 1, 0, #tagNameList) + local ub = clamp(page * columns * tagsPerColumn, 0, #tagNameList) + self:settextf("%d-%d/%d", lb, ub, #tagNameList) + end + } + } + + for i = 1, tagsPerColumn * columns do + t[#t+1] = tagListItem(i) + end + + return t +end + +t[#t+1] = Def.Quad { + Name = "UpperLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.UpperLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "LipTop", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.LipSeparatorThickness) + self:diffusealpha(0.3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = tagList() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/stepsdisplay.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/stepsdisplay.lua new file mode 100644 index 0000000000..62fcebc1d1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/stepsdisplay.lua @@ -0,0 +1,331 @@ +local itsOn = false +local thesteps +local numshown = 5 +local currentindex = 1 +local displayindexoffset = 0 + +local ratios = { + DiffItemWidth = 60 / 1920, + DiffItemHeight = 40 / 1080, + DiffFrameUpperGap = 282 / 1080, -- from frame top edge to top diff edge + --DiffFrameLeftGap = 407 / 1920, -- this number is provided by the parent at this time + --DiffFrameRightGap = 22 / 1920, -- same + DiffFrameSpacing = 11 / 1920, -- spacing between items + DiffItemGlowVerticalSpan = 14 / 1080, -- measurement of the visible portion of the glow, doubled + DiffItemGlowHorizontalSpan = 14 / 1920, -- same as above +} + +local actuals = { + DiffItemWidth = ratios.DiffItemWidth * SCREEN_WIDTH, + DiffItemHeight = ratios.DiffItemHeight * SCREEN_HEIGHT, + DiffFrameUpperGap = ratios.DiffFrameUpperGap * SCREEN_HEIGHT, + --DiffFrameLeftGap = ratios.DiffFrameLeftGap * SCREEN_WIDTH, -- this number is provided by the parent at this time + --DiffFrameRightGap = ratios.DiffFrameRightGap * SCREEN_WIDTH, -- same + DiffFrameSpacing = ratios.DiffFrameSpacing * SCREEN_WIDTH, + DiffItemGlowVerticalSpan = ratios.DiffItemGlowVerticalSpan * SCREEN_HEIGHT, + DiffItemGlowHorizontalSpan = ratios.DiffItemGlowHorizontalSpan * SCREEN_WIDTH, +} + +-- scoping magic +do + -- copying the provided ratios and actuals tables to have access to the sizing for the overall frame + local rt = Var("ratios") + for k,v in pairs(rt) do + ratios[k] = v + end + local at = Var("actuals") + for k,v in pairs(at) do + actuals[k] = v + end +end + +local textSize = 0.75 +local textzoomFudge = 5 + +-- this will return an index which is offset depending on certain conditions +-- basically we want the difficulties to be aligned to the right of the box +-- highest on the right +-- the highest diff is the highest index, but the highest index is not a consistent number +-- to avoid update order shenanigans we can do this math and logic instead +local function pushIndexByBound(index) + if #thesteps < numshown then + return index - numshown + #thesteps + else + return index + end +end + +-- based on the amount of difficulties displayed we can allow more room for the song information +-- assuming that we are aligning difficulties to the right +local function setMaxWidthForSongInfo() + local curSongBox = SCREENMAN:GetTopScreen():safeGetChild("RightFrame", "CurSongBoxFile") + if not curSongBox then return end + + local diffSlotsOpen = clamp(numshown - #thesteps, 0, numshown) + -- exactly the width of items including the space between plus an additional 2 gaps worth of space for buffer + local widthallowed = actuals.DiffFrameLeftGap - actuals.LeftTextLeftGap + diffSlotsOpen * (actuals.DiffItemWidth + actuals.DiffFrameSpacing) - actuals.DiffFrameSpacing * 2 + + local title = curSongBox:GetChild("Frame"):GetChild("TitleAuthor") + local subtitle = curSongBox:GetChild("Frame"):GetChild("SubTitle") + + if title then + title:maxwidth(widthallowed / title:GetZoom() - textzoomFudge) + end + + if subtitle then + subtitle:maxwidth(widthallowed / subtitle:GetZoom() - textzoomFudge) + end + +end + +local t = Def.ActorFrame { + Name = "StepsDisplayFile", + InitCommand = function(self) + -- all positions are relative to the right of the rightmost item + -- align everything to the right from there + self:xy(actuals.Width - actuals.DiffFrameRightGap, actuals.DiffFrameUpperGap) + end, + BeginCommand = function(self) + local scrn = SCREENMAN:GetTopScreen() + local snm = scrn:GetName() + local anm = self:GetName() + CONTEXTMAN:RegisterToContextSet(snm, "Main1", anm) + -- input handling for changing difficulty with keyboard + -- this timeout is basically just a timer to make sure that you press buttons fast enough + local comboTimeout = nil + local comboTimeoutSeconds = 1 + local pressqueue = {} + + local function clearTimeout() + if comboTimeout ~= nil then + scrn:clearInterval(comboTimeout) + comboTimeout = nil + end + end + + local function resetTimeout() + clearTimeout() + -- we use setInterval because setTimeout doesnt do what I actually want + -- wow very intuitive + comboTimeout = scrn:setInterval(function() + pressqueue = {} + clearTimeout() + end, + comboTimeoutSeconds) + end + + scrn:AddInputCallback(function(event) + if not CONTEXTMAN:CheckContextSet(snm, "Main1") then + resetTimeout() + return + end + + if event.type == "InputEventType_FirstPress" then + if event.button == "MenuUp" or event.button == "Up" then + pressqueue[#pressqueue+1] = "Up" + elseif event.button == "MenuDown" or event.button == "Down" then + pressqueue[#pressqueue+1] = "Down" + end + + resetTimeout() + + -- potentially have the combo we want + if #pressqueue >= 2 and pressqueue[#pressqueue-1] == pressqueue[#pressqueue] then + if pressqueue[#pressqueue] == "Up" then + currentindex = clamp(currentindex - 1, 1, #thesteps) + local newsteps = thesteps[currentindex] + self:GetChild("Cursor"):playcommand("ChangeSteps", {steps = newsteps}) + pressqueue = {} + elseif pressqueue[#pressqueue] == "Down" then + currentindex = clamp(currentindex + 1, 1, #thesteps) + local newsteps = thesteps[currentindex] + self:GetChild("Cursor"):playcommand("ChangeSteps", {steps = newsteps}) + pressqueue = {} + end + end + end + end) + end, + SetCommand = function(self, params) + if params.song then + thesteps = WHEELDATA:GetChartsMatchingFilter(params.song) + self:visible(true) + else + thesteps = {} + self:visible(false) + end + setMaxWidthForSongInfo() + end +} + +local function stepsRows(i) + local steps = nil + local index = i + + local o = Def.ActorFrame { + Name = "StepsFrame", + InitCommand = function(self) + -- to place indices 1-numshown left to right from the right to the left place them in reverse order + self:x(-actuals.DiffItemWidth * (numshown - i) - actuals.DiffFrameSpacing * (numshown - i)) + self:visible(false) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("UpdateStepsRows") + end, + UpdateStepsRowsCommand = function(self) + -- to get them to align right + index = pushIndexByBound(i) + steps = thesteps[index + displayindexoffset] + if steps then + self:playcommand("SetStepsRows") + self:visible(true) + else + self:visible(false) + end + end, + + UIElements.QuadButton(1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoomto(actuals.DiffItemWidth, actuals.DiffItemHeight) + end, + SetStepsRowsCommand = function(self) + local diff = steps:GetDifficulty() + self:diffuse(colorByDifficulty(diff)) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if steps and params.event == "DeviceButton_left mouse button" then + -- tree: + -- StepsDisplayFile, StepsRows, StepsFrame, self + self:GetParent():GetParent():GetParent():GetChild("Cursor"):playcommand("ChangeSteps", {steps = steps}) + end + end + }, + Def.Quad { + Name = "Lip", + InitCommand = function(self) + self:halign(1):valign(0) + self:y(actuals.DiffItemHeight / 2) + self:zoomto(actuals.DiffItemWidth, actuals.DiffItemHeight / 2) + end, + SetStepsRowsCommand = function(self) + self:visible(true) + self:diffuse(COLORS:getMainColor("SecondaryBackground")) + self:diffusealpha(0.2) + end + }, + LoadFont("Common Normal") .. { + Name = "StepsType", + InitCommand = function(self) + self:xy(-actuals.DiffItemWidth / 2, actuals.DiffItemHeight / 4) + self:zoom(textSize) + self:maxwidth(actuals.DiffItemWidth / textSize - textzoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetStepsRowsCommand = function(self) + local st = THEME:GetString("StepsDisplay StepsType", ToEnumShortString(steps:GetStepsType())) + self:settext(st) + end + }, + LoadFont("Common Normal") .. { + Name = "NameAndMeter", + InitCommand = function(self) + self:xy(-actuals.DiffItemWidth / 2, actuals.DiffItemHeight / 4 * 3) + self:maxwidth(actuals.DiffItemWidth / textSize - textzoomFudge) + self:zoom(textSize) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetStepsRowsCommand = function(self) + local meter = steps:GetMeter() + local diff = getShortDifficulty(steps:GetDifficulty()) + self:settextf("%s %s", diff, meter) + end + } + + } + + return o +end + +local sdr = Def.ActorFrame {Name = "StepsRows"} + +for i = 1, numshown do + sdr[#sdr + 1] = stepsRows(i) +end +t[#t + 1] = sdr + +local center = math.ceil(numshown / 2) + +t[#t + 1] = Def.Sprite { + Texture = THEME:GetPathG("", "stepsdisplayGlow"), + Name = "Cursor", + InitCommand = function(self) + self:halign(1):valign(0) + self:y(-actuals.DiffItemGlowVerticalSpan / 2) + self:zoomto(actuals.DiffItemWidth + actuals.DiffItemGlowHorizontalSpan, actuals.DiffItemHeight + actuals.DiffItemGlowVerticalSpan) + self:diffusealpha(1) + end, + ChangeStepsCommand = function(self, params) + -- just to make sure nothing funny happens make sure that we cant get into an impossible chart + if GAMESTATE:GetCurrentSong() == nil then return end + + -- actually do the work to set all game variables to make sure this diff plays if you press enter + GAMESTATE:SetPreferredDifficulty(PLAYER_1, params.steps:GetDifficulty()) + GAMESTATE:SetCurrentSteps(PLAYER_1, params.steps) + self:playcommand("Set", params) + MESSAGEMAN:Broadcast("ChangedSteps", params) + end, + SetCommand = function(self, params) + for i, chart in ipairs(thesteps) do + if chart == params.steps then + currentindex = i + break + end + end + + local cursorindex = currentindex + if cursorindex <= center then + displayindexoffset = clamp(displayindexoffset - 1, 0, #thesteps) + elseif #thesteps - displayindexoffset > numshown then + displayindexoffset = currentindex - center + cursorindex = center + else + cursorindex = currentindex - displayindexoffset + end + + if #thesteps > numshown and #thesteps - displayindexoffset < numshown then + displayindexoffset = #thesteps - numshown + end + + -- we have to offset the cursor to take into account the right alignment for lower numbers of diffs + if #thesteps < numshown then + local toOffset = pushIndexByBound(currentindex) + cursorindex = numshown - #thesteps + cursorindex + end + + if cursorindex < center and #thesteps > numshown then + local newoff = displayindexoffset - 1 + if newoff >= 0 then + displayindexoffset = math.max(displayindexoffset - 1, 0) + cursorindex = cursorindex + 1 + end + end + + -- find the left edge of the desired item, consider item width and gap width + -- then offset by half the glow span (which is doubled for sizing) + if thesteps[currentindex] then + + self:diffusealpha(1) + -- positions relative to the right of the rightmost item + -- rightmost index is numshown, move in reverse order + self:x(-actuals.DiffItemWidth * (numshown - cursorindex) + -actuals.DiffFrameSpacing * (numshown - cursorindex) + actuals.DiffItemGlowHorizontalSpan / 2) + else + self:diffusealpha(0) + end + self:GetParent():GetChild("StepsRows"):queuecommand("UpdateStepsRows") + end +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/wheel.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/wheel.lua new file mode 100644 index 0000000000..f3b33ad843 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic decorations/wheel.lua @@ -0,0 +1,1670 @@ + -- 11 visible items (top is a group header) + -- an unfortunate amount of code is reliant on the fact that there are 11 items + -- but thankfully everything works fine if you change it + -- ... the header wont look very good if you push it off the screen though + -- (retrospective comment: wtf i changed this to 14 and it still works) +local numWheelItems = 14 + +local ratios = { + LeftGap = 16 / 1920, + UpperGap = 219 / 1080, -- distance from top of screen, not info frame + LowerGap = 0 / 1080, -- expected, maybe unused + Width = 867 / 1920, + Height = 861 / 1080, -- does not include the header + ItemHeight = 86.5 / 1080, -- 85 + 2 to account for half of the upper and lower item dividers + ItemDividerThickness = 2 / 1080, + ItemDividerLength = 584 / 1920, + ItemTextUpperGap = 20 / 1080, -- distance from top of item to center of title text + ItemTextLowerGap = 18 / 1080, -- distance from center of divider to center of author text + ItemTextCenterDistance = 40 / 1080, -- distance from lower (divider center) to center of subtitle + ItemGradeTextRightGap = 24 / 1920, -- distance from right of item to right edge of text + ItemGradeTextMaxWidth = 86 / 1920, -- approximation of width of the AAAAA grade + ItemFavoriteIconRightGap = 18 / 1920, -- from right edge of banner to middle of favorite icon + ItemFavoriteIconSize = 36 / 1080, -- width and height of the icon + ItemPermamirrorIconRightGap = 40 / 1920, -- from right edge of banner to middle of favorite icon + ItemPermamirrorIconSize = 39 / 1080, -- width and height of the icon + BannerWidth = 265 / 1920, + BannerItemGap = 18 / 1920, -- gap between banner and item text/dividers + HeaderHeight = 110 / 1080, + HeaderUpperGap = 109 / 1080, -- top of screen to top of frame (same as playerinfo height) + wtffudge = 45 / 1080, -- this random number fixes the weird offset of the wheel vertically so that it fits perfectly with the header + -- effective measurements for group specific information + HeaderBannerWidth = 336 / 1920, + HeaderText1UpperGap = 21 / 1080, -- distance from top edge to top text top edge + HeaderText2UpperGap = 68 / 1080, -- distance from top edge to top edge of lower text + HeaderTextLeftGap = 21 / 1920, -- distance from edge of banner to left of text + -- effective measurements for the header lines when not in group specific info mode + HeaderMText1UpperGap = 16 / 1080, + HeaderMText2UpperGap = 47 / 1080, + HeaderMText3UpperGap = 78 / 1080, + HeaderMTextLeftGap = 0 / 1920, -- text width will be the same as the banner width + HeaderGraphWidth = 573 / 1920, -- very approximate + + -- controls the width of the mouse wheel scroll box, should be the same number as the general box X position + -- (found in generalBox.lua) + GeneralBoxLeftGap = 1140 / 1920, -- distance from left edge to the left edge of the general box + + ScrollBarWidth = 18 / 1920, + ScrollBarHeight = 971 / 1080, +} + +local actuals = { + LeftGap = ratios.LeftGap * SCREEN_WIDTH, + UpperGap = ratios.UpperGap * SCREEN_HEIGHT, + LowerGap = ratios.LowerGap * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + ItemHeight = ratios.ItemHeight * SCREEN_HEIGHT, + ItemDividerThickness = 2, -- maybe needs to be constant + ItemDividerLength = ratios.ItemDividerLength * SCREEN_WIDTH, + ItemTextUpperGap = ratios.ItemTextUpperGap * SCREEN_HEIGHT, + ItemTextLowerGap = ratios.ItemTextLowerGap * SCREEN_HEIGHT, + ItemTextCenterDistance = ratios.ItemTextCenterDistance * SCREEN_HEIGHT, + ItemGradeTextRightGap = ratios.ItemGradeTextRightGap * SCREEN_WIDTH, + ItemGradeTextMaxWidth = ratios.ItemGradeTextMaxWidth * SCREEN_WIDTH, + ItemFavoriteIconRightGap = ratios.ItemFavoriteIconRightGap * SCREEN_WIDTH, + ItemFavoriteIconSize = ratios.ItemFavoriteIconSize * SCREEN_HEIGHT, + ItemPermamirrorIconRightGap = ratios.ItemPermamirrorIconRightGap * SCREEN_WIDTH, + ItemPermamirrorIconSize = ratios.ItemPermamirrorIconSize * SCREEN_HEIGHT, + BannerWidth = ratios.BannerWidth * SCREEN_WIDTH, + BannerItemGap = ratios.BannerItemGap * SCREEN_WIDTH, + HeaderHeight = ratios.HeaderHeight * SCREEN_HEIGHT, + HeaderUpperGap = ratios.HeaderUpperGap * SCREEN_HEIGHT, + wtffudge = ratios.wtffudge * SCREEN_HEIGHT, + HeaderBannerWidth = ratios.HeaderBannerWidth * SCREEN_WIDTH, + HeaderText1UpperGap = ratios.HeaderText1UpperGap * SCREEN_HEIGHT, + HeaderText2UpperGap = ratios.HeaderText2UpperGap * SCREEN_HEIGHT, + HeaderTextLeftGap = ratios.HeaderTextLeftGap * SCREEN_WIDTH, + HeaderMText1UpperGap = ratios.HeaderMText1UpperGap * SCREEN_HEIGHT, + HeaderMText2UpperGap = ratios.HeaderMText2UpperGap * SCREEN_HEIGHT, + HeaderMText3UpperGap = ratios.HeaderMText3UpperGap * SCREEN_HEIGHT, + HeaderMTextLeftGap = ratios.HeaderMTextLeftGap * SCREEN_WIDTH, + HeaderGraphWidth = ratios.HeaderGraphWidth * SCREEN_WIDTH, + GeneralBoxLeftGap = ratios.GeneralBoxLeftGap * SCREEN_WIDTH, + ScrollBarWidth = ratios.ScrollBarWidth * SCREEN_WIDTH, + ScrollBarHeight = ratios.ScrollBarHeight * SCREEN_HEIGHT, +} + +local wheelItemTextSize = 0.62 +local wheelItemGradeTextSize = 1 +local wheelItemTitleTextSize = 0.82 +local wheelItemSubTitleTextSize = 0.62 +local wheelItemArtistTextSize = 0.62 +local wheelItemGroupTextSize = 0.82 +local wheelItemGroupInfoTextSize = 0.62 +local wheelHeaderTextSize = 1.2 +local wheelHeaderMTextSize = 0.6 +local textzoomfudge = 5 -- used in maxwidth to allow for gaps when squishing text + +local graphLineColor = COLORS:getWheelColor("GraphLine") +local primaryTextColor = COLORS:getMainColor("PrimaryText") + +----- +-- header related things +local headerTransitionSeconds = 0.2 +local graphBoundTextSize = 0.4 +local graphBoundOffset = 10 / 1080 * SCREEN_HEIGHT -- offset the graph bounds diagonally by this much for alignment reasons +local graphWidth = actuals.ItemDividerLength - actuals.ItemGradeTextRightGap / 2 +local playsThisSession = SCOREMAN:GetNumScoresThisSession() +local scoresThisSession = SCOREMAN:GetScoresThisSession() +local accThisSession = 0 +-- the vertical bounds for the graph +-- need to keep them respectable ... dont allow below 50 or above 100 +local graphUpperBound = 100 +local graphLowerBound = 50 +local hoverAlpha = 0.9 + +-- calculate average wife percent for scores set this session +-- ignores negative percents +local function calcAverageWifePercentThisSession() + local sum = 0 + for i, s in ipairs(scoresThisSession) do + sum = sum + clamp(s:GetWifeScore() * 100, 0, 100) + end + + -- prevent division by 0 + if playsThisSession == 0 then + return 0 + else + return sum / playsThisSession + end +end + +-- figure out the graph bounds in a very slightly more intelligent way than normal +local function calculateGraphBounds() + local max = -10000 + local min = 10000 + + local sum = 0 + local sd = 0 + local mean = 0 + for _, s in ipairs(scoresThisSession) do + local w = clamp(s:GetWifeScore() * 100, 0, 100) + if w > max then + max = w + end + if w < min then + min = w + end + sum = sum + w + end + + -- prevent division by 0 + if playsThisSession == 0 then + mean = 85 + sd = 15 + min = 0 + max = 100 + else + mean = sum / playsThisSession + -- 2nd pass for sd + for _, s in ipairs(scoresThisSession) do + local w = clamp(s:GetWifeScore() * 100, 0, 100) + sd = sd + (w - mean) ^ 2 + end + sd = math.sqrt(sd / playsThisSession) + end + + max = clamp(mean + sd, min, 100) + min = clamp(mean - sd / 2, 0, max) + + -- probably possible if your only score is outside the 0-100 range somehow + -- allow impossible bounds here (negative and +100%) + if min == max and playsThisSession > 1 then + max = max + 2.5 + min = min - 2.5 + end + graphUpperBound = max + graphLowerBound = min +end + +-- convert x value to x position in graph +local function graphXPos(x, width) + -- dont hit the edge + local buffer = 0.01 + local minX = width * buffer + local maxX = width * (1 - buffer) + + local count = playsThisSession + -- the left end of segments should be where the points go + -- for 1, this is the middle + -- 2 points makes 3 segments + -- ...etc + local xsegmentsize = (width - buffer * 2) / (count + 1) + + -- remember offset by minX and then push it over by the segmentsize + return minX + xsegmentsize * x +end + +-- convert y value to y position in graph +local function graphYPos(y, height) + -- dont quite allow hitting the edges + local buffer = 1 + local minY = graphLowerBound - buffer + local maxY = graphUpperBound + buffer + y = clamp(y, graphLowerBound, graphUpperBound) + + local percentage = (y - minY) / (maxY - minY) + + -- negate the output, the graph line position is relative to bottom left corner of graph + -- negative numbers go upward on the screen + return -1 * percentage * height +end + +-- generate vertices for 1 dot in the graph +local function createVertices(vt, x, y, c) + vt[#vt + 1] = {{x, y, 0}, c} +end + +-- generate the vertices to put into the ActorFrameTexture for the MiscPage graph +local function generateRecentWifeScoreGraph() + local v = {} + -- update color if it happened to update before now + graphLineColor = COLORS:getWheelColor("GraphLine") + + for i, s in ipairs(scoresThisSession) do + local w = s:GetWifeScore() * 100 + local x = graphXPos(i, graphWidth) + local y = graphYPos(w, actuals.HeaderHeight / 8 * 6) + createVertices(v, x, y, graphLineColor) + end + + return v +end + +accThisSession = calcAverageWifePercentThisSession() +calculateGraphBounds() +----- + +-- the currently opened group (not necessarily the one hovered) +local openedGroup = "" +-- the hovered item (can be a group or a song) +-- if a song: has GetDisplayMainTitle +local hoveredItem = nil + +-- wheel horizontal movement animation speed +local animationSeconds = 0.1 +local wheelVisibleX = 0 +local wheelHiddenX = -actuals.Width +local visible = true +local t = Def.ActorFrame { + Name = "WheelFile", + InitCommand = function(self) + self:playcommand("SetThePositionForThisFrameNothingElse") + end, + HideWheelMessageCommand = function(self) + if not visible then return end + visible = false + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(wheelHiddenX) + end, + ShowWheelMessageCommand = function(self) + if visible then return end + visible = true + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(1) + self:x(wheelVisibleX) + end, + ShowSettingsAltMessageCommand = function(self, params) + if params and params.name then + self:playcommand("HideWheel") + else + self:playcommand("ShowWheel") + end + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Music Wheel Banners" then + self:playcommand("UpdateWheelBanners") + end + end, + SetThePositionForThisFrameNothingElseCommand = function(self) + if getWheelPosition() then + wheelVisibleX = 0 + wheelHiddenX = -actuals.Width + else + wheelVisibleX = SCREEN_WIDTH - actuals.Width - actuals.LeftGap - actuals.ScrollBarWidth + wheelHiddenX = SCREEN_WIDTH + end + if visible then + self:x(wheelVisibleX) + else + self:x(wheelHiddenX) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetThePositionForThisFrameNothingElse") + end, + WheelSettledMessageCommand = function(self, params) + -- just here to update hovered item for controlling the reload stuff + if params then + hoveredItem = params.hovered + end + end, + ReloadWheelCommand = function(self) + -- reloads the wheel data and places you back on the item you were on + -- updates any changed filters + local group = openedGroup + local chartkey = nil + if hoveredItem.GetDisplayMainTitle then + chartkey = GAMESTATE:GetCurrentSteps():GetChartKey() + else + group = hoveredItem + end + WHEELDATA:ReloadWheelData() + self:playcommand("ReloadFilteredSongs", { + group = group, + chartkey = chartkey, + }) + end, + ApplyFilterCommand = function(self) + -- reloads the wheel data and places you on the first good match based on the search + + -- figure out if we entered the name of a group + -- local searchterm = WHEELDATA:GetSearch().Title + local findGroup = false + --[[ + if searchterm ~= nil and searchterm ~= "" then + -- WHEELDATA:ReloadWheelData() -- this is needed at least once before now + findGroup = WHEELDATA:FindIndexOfFolder(searchterm:lower()) ~= -1 + end + ]] + + if findGroup then + WHEELDATA:ReloadWheelData() + + -- entered an exact group, go to that group + self:playcommand("FindGroup", { + group = searchterm:lower() + }) + else + if WHEELDATA:IsSearchFilterEmpty() then + -- no search term? either search was removed or only a tag was applied. + -- in that case, make an attempt to keep the current position + -- if the current file got filtered out by a new tag, you are sent to position 1 + self:playcommand("ReloadWheel") + else + WHEELDATA:ReloadWheelData() + local exactMatchSong = WHEELDATA:FindExactSearchMatchSong() + if exactMatchSong == nil then + exactMatchSong = WHEELDATA:FindTheOnlySearchResult() + end + if exactMatchSong ~= nil then + -- there is an exact match maybe we want to go to + self:playcommand("FindSong", { + song = exactMatchSong, + }) + else + -- there isnt a precise match, just reset the wheel + self:playcommand("ReloadFilteredSongs") + end + end + end + end, + DFRFinishedMessageCommand = function(self, params) + if params and params.newsongs then + if params.newsongs > 0 then + self:playcommand("ReloadWheel") + ms.ok(params.newsongs .. " new songs loaded") + else + ms.ok("No new songs loaded") + end + end + end, + ReloadedCurrentPackMessageCommand = function(self) + self:playcommand("ReloadWheel") + end, + ReloadedCurrentSongMessageCommand = function(self) + self:playcommand("ReloadWheel") + end, +} + + +-- functionally create each item base because they are identical (BG and divider) +local function wheelItemBase() + return Def.ActorFrame { + Name = "WheelItemBase", + + Def.Quad { + Name = "ItemBG", + InitCommand = function(self) + self:zoomto(actuals.Width, actuals.ItemHeight) + -- coloring is somehow handled by the song/group stuff + end, + }, + Def.Quad { + Name = "Divider", + InitCommand = function(self) + self:valign(0) + self:halign(0) + self:playcommand("SetPosition") + self:diffusealpha(1) + registerActorToColorConfigElement(self, "musicWheel", "ItemDivider") + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:zoomto(actuals.ItemDividerLength, actuals.ItemDividerThickness) + self:xy(actuals.Width / 2 - actuals.ItemDividerLength, -actuals.ItemHeight/2) + else + self:zoomto(actuals.Width, actuals.ItemDividerThickness) + self:xy(actuals.Width / 2 - actuals.Width, -actuals.ItemHeight/2) + end + else + if useWheelBanners() then + self:zoomto(actuals.ItemDividerLength, actuals.ItemDividerThickness) + self:xy(-actuals.Width / 2, -actuals.ItemHeight/2) + else + self:zoomto(actuals.Width, actuals.ItemDividerThickness) + self:xy(-actuals.Width / 2, -actuals.ItemHeight/2) + end + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + } +end + +-- responsible for setting song banner for wheelitem updates +local function songBannerSetter(self, song, isCurrentItem) + if not useWheelBanners() then + self:visible(false) + return + end + + if isCurrentItem and useVideoBanners() then + self:SetDecodeMovie(true) + else + self:SetDecodeMovie(false) + end + + if song then + local bnpath = song:GetBannerPath() + -- we load the fallback banner but for aesthetic purpose at the moment, invisible + if not bnpath then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + if self.bnpath ~= bnpath then + self:Load(bnpath) + end + self.bnpath = bnpath + end +end + +-- responsible for setting group banner for wheelitem updates +local function groupBannerSetter(self, group, isCurrentItem) + if not useWheelBanners() then + self:visible(false) + return + end + + if isCurrentItem and useVideoBanners() then + self:SetDecodeMovie(true) + else + self:SetDecodeMovie(false) + end + + local bnpath = WHEELDATA:GetFolderBanner(group) + -- we load the fallback banner but for aesthetic purpose at the moment, invisible + if not bnpath or bnpath == "" then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + if self.bnpath ~= bnpath then + self:Load(bnpath) + end + self.bnpath = bnpath +end + +-- updates all information for a song wheelitem +local function songActorUpdater(songFrame, song, isCurrentItem) + songFrame.Title:settext(song:GetDisplayMainTitle()) + songFrame.SubTitle:settext(song:GetDisplaySubTitle()) + songFrame.Artist:settext("~"..song:GetDisplayArtist()) + songFrame.Grade:playcommand("SetGrade", {grade = song:GetHighestGrade()}) + songFrame.Favorited:diffusealpha(song:IsFavorited() and 1 or 0) + songFrame.Permamirror:diffusealpha(song:IsPermaMirror() and 1 or 0) + songBannerSetter(songFrame.Banner, song, isCurrentItem) +end + +-- updates all information for a group wheelitem +local function groupActorUpdater(groupFrame, packName, isCurrentItem) + local packCount = WHEELDATA:GetFolderCount(packName) + local packAverageDiff = WHEELDATA:GetFolderAverageDifficulty(packName) + local clearstats = WHEELDATA:GetFolderClearStats(packName) + + groupFrame.Title:settext(packName) + groupFrame.GroupInfo:playcommand("SetInfo", {count = packCount, avg = packAverageDiff[1]}) + groupFrame.ClearStats:playcommand("SetInfo", {stats = clearstats}) + groupFrame.ScoreStats:playcommand("SetInfo", {stats = clearstats, count = packCount}) + groupBannerSetter(groupFrame.Banner, packName, isCurrentItem) +end + +-- to offer control of the actors specifically to us instead of the scripts +-- we have make separate local functions for the song/group builders +local function songActorBuilder() + return Def.ActorFrame { + Name = "SongFrame", + wheelItemBase(), + LoadFont("Common Normal") .. { + Name = "Title", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemTitleTextSize) + self:halign(0) + self:maxheight(actuals.ItemHeight / 3 / wheelItemTitleTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText", 0.65) + -- hack to color the ItemBG later + local itembg = self:GetParent():GetChild("WheelItemBase"):GetChild("ItemBG") + itembg:diffusealpha(0.6) + registerActorToColorConfigElement(itembg, "musicWheel", "SongBackground") + end, + BeginCommand = function(self) + self:GetParent().Title = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemTitleTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemTitleTextSize - textzoomfudge) + end + else + self:x((-actuals.Width / 2) + actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) + if useWheelBanners() then + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemTitleTextSize - textzoomfudge) + else + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemTitleTextSize - textzoomfudge) + end + end + self:y(-actuals.ItemHeight / 2 + actuals.ItemTextUpperGap) + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + LoadFont("Common Normal") .. { + Name = "SubTitle", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemSubTitleTextSize) + self:halign(0) + self:maxheight(actuals.ItemHeight / 3 / wheelItemSubTitleTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + BeginCommand = function(self) + self:GetParent().SubTitle = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemSubTitleTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemSubTitleTextSize - textzoomfudge) + end + else + self:x((-actuals.Width / 2) + actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) + if useWheelBanners() then + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemSubTitleTextSize - textzoomfudge) + else + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemSubTitleTextSize - textzoomfudge) + end + end + self:y(actuals.ItemHeight / 2 - actuals.ItemTextCenterDistance) + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + LoadFont("Common Normal") .. { + Name = "Artist", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemArtistTextSize) + self:maxheight(actuals.ItemHeight / 3 / wheelItemArtistTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + BeginCommand = function(self) + self:GetParent().Artist = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(0) + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemArtistTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemArtistTextSize - textzoomfudge) + end + else + self:halign(1) + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.BannerWidth - actuals.ItemGradeTextRightGap) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap * 2) / wheelItemArtistTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemGradeTextRightGap) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemArtistTextSize - textzoomfudge) + end + end + self:y(actuals.ItemHeight / 2 - actuals.ItemTextLowerGap) + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + LoadFont("Common Normal") .. { + Name = "Grade", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemGradeTextSize) + end, + BeginCommand = function(self) + self:GetParent().Grade = self + end, + SetGradeCommand = function(self, params) + if params.grade and params.grade ~= "Grade_Invalid" then + self:settext(THEME:GetString("Grade", params.grade:sub(#"Grade_T"))) + self:diffuse(colorByGrade(params.grade)) + else + self:settext("") + end + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(1) + self:x(actuals.Width / 2 - actuals.ItemGradeTextRightGap) + self:maxwidth(actuals.ItemGradeTextMaxWidth / wheelItemGradeTextSize) + else + self:halign(0.5) + self:x(-actuals.Width / 2 + (actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) / 2) + self:maxwidth((actuals.ItemGradeTextMaxWidth - (actuals.ItemGradeTextRightGap)) / wheelItemGradeTextSize) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + }, + Def.Sprite { + Name = "Banner", + InitCommand = function(self) + -- y is already set: relative to "center" + self:playcommand("SetPosition") + self:scaletoclipped(actuals.BannerWidth, actuals.ItemHeight) + -- dont play movies because they lag the wheel so much like wow please dont ever use those (for now) + self:SetDecodeMovie(false) + end, + BeginCommand = function(self) + self:GetParent().Banner = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:x(-actuals.Width / 2):halign(0) + else + self:x(actuals.Width / 2):halign(1) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + }, + Def.Sprite { + Name = "FavoriteIcon", + Texture = THEME:GetPathG("", "round_star"), + InitCommand = function(self) + -- same y line as the artist text + self:y(actuals.ItemHeight / 2 - actuals.ItemTextLowerGap) + self:playcommand("SetPosition") + self:zoomto(actuals.ItemFavoriteIconSize, actuals.ItemFavoriteIconSize) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "musicWheel", "Favorite") + self:wag() + end, + BeginCommand = function(self) + self:GetParent().Favorited = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(-actuals.Width / 2 + actuals.BannerWidth - actuals.ItemFavoriteIconRightGap) + else + self:x(actuals.Width / 2) + end + else + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.BannerWidth + actuals.ItemFavoriteIconRightGap) + else + self:x(-actuals.Width / 2) + end + end + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + }, + Def.Sprite { + Name = "PermamirrorIcon", + Texture = THEME:GetPathG("", "mirror"), + InitCommand = function(self) + -- same y line as the artist text + self:y(actuals.ItemHeight / 2 - actuals.ItemTextLowerGap) + self:playcommand("SetPosition") + self:zoomto(actuals.ItemPermamirrorIconSize, actuals.ItemPermamirrorIconSize) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "musicWheel", "Permamirror") + self:wag() + end, + BeginCommand = function(self) + self:GetParent().Permamirror = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(-actuals.Width / 2 + actuals.BannerWidth - actuals.ItemPermamirrorIconRightGap) + else + self:x(actuals.Width / 2 - actuals.ItemPermamirrorIconRightGap) + end + else + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.BannerWidth + actuals.ItemPermamirrorIconRightGap) + else + self:x(-actuals.Width / 2 + actuals.ItemPermamirrorIconRightGap) + end + end + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + } + } +end + +-- generates the clear stats bar for each group +local function scoreStatsFrame() + + -- list of grades to consider (midgrades will be converted appropriately based on the table below) + local gradesToUse = { + "Grade_Tier01", -- AAAAA + "Grade_Tier04", -- AAAA + "Grade_Tier07", -- AAA + "Grade_Tier10", -- AA + "Grade_Tier13", -- A + "Grade_Tier14", -- B + "Grade_Tier15", -- C + "Grade_Tier20", -- mysterious Clear grade (only referred to by WHEELDATA) + } + + -- lists of grades that are equivalent to the current grade + -- all keys of this should match the above table + local expandedGrades = { + Grade_Tier01 = {"Grade_Tier01"}, + Grade_Tier04 = {"Grade_Tier02", "Grade_Tier03", "Grade_Tier04"}, + Grade_Tier07 = {"Grade_Tier05", "Grade_Tier06", "Grade_Tier07"}, + Grade_Tier10 = {"Grade_Tier08", "Grade_Tier09", "Grade_Tier10"}, + Grade_Tier13 = {"Grade_Tier11", "Grade_Tier12", "Grade_Tier13"}, + Grade_Tier14 = {"Grade_Tier14"}, + Grade_Tier15 = {"Grade_Tier15"}, + Grade_Tier20 = {"Grade_Tier20"}, + } + + -- determines the size of the outline quad + local function framelength() return useWheelBanners() and actuals.ItemDividerLength * (3/4) or actuals.Width * (3/4) end + local frameheight = 11 / 1080 * SCREEN_HEIGHT + -- determines how much to shave off to make the size fit + local outlineThickness = 2 / 1080 * SCREEN_HEIGHT + local function maxbarlength() return framelength() - outlineThickness * 2 end + local barheight = frameheight - outlineThickness * 2 + + -- a colorful bar for each grade to represent + local function makeBar(i) + local grade = gradesToUse[i] + return Def.Quad { + Name = "Bar_"..grade, + InitCommand = function(self) + self:halign(0) + self:zoomto(0, barheight) + self:diffusealpha(1) + if grade ~= "Grade_Tier20" then + registerActorToColorConfigElement(self, "grades", grade) + else + registerActorToColorConfigElement(self, "clearType", "Clear") + end + end, + } + end + + local t = Def.ActorFrame { + Name = "ScoreStatsFrame", + SetInfoCommand = function(self, params) + if params == nil and self.storedparams ~= nil then + params = self.storedparams + end + + if params ~= nil and params.stats ~= nil then + -- if there are no scores in this pack, dont show the bar + if params.stats.totalScores == 0 then + self:diffusealpha(0) + else + self:diffusealpha(1) + end + + self.storedparams = params + + local barcounts = {} + -- determine how many scores count for each bar + for gkey, gradeTable in pairs(expandedGrades) do + barcounts[gkey] = 0 + for _, grade in ipairs(gradeTable) do + local c = params.stats.clearPerGrade[grade] + if c ~= nil then + barcounts[gkey] = barcounts[gkey] + c + end + end + end + -- update each bar + local runningsum = 0 + for i = #gradesToUse, 1, -1 do + local grade = gradesToUse[i] + local child = self:GetChild("Bar_"..grade) + local percentSoFar = runningsum / params.count + local percentForThisBar = (barcounts[grade] or 0) / params.count + runningsum = runningsum + (barcounts[grade] or 0) + child:x(outlineThickness + maxbarlength() * percentSoFar) + child:zoomx(maxbarlength() * percentForThisBar) + end + end + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetInfo") + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetInfo") + end, + Def.Quad { + Name = "Outline", + InitCommand = function(self) + self:halign(0) + self:playcommand("UpdateWheelBanners") + end, + UpdateWheelBannersCommand = function(self) + self:zoomto(framelength(), frameheight) + end, + }, + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:x(outlineThickness) + self:playcommand("UpdateWheelBanners") + self:diffuse(color("0,0,0,1")) + end, + UpdateWheelBannersCommand = function(self) + self:zoomto(maxbarlength(), barheight) + end, + } + } + + for i = 1, #gradesToUse do + t[#t+1] = makeBar(i) + end + + return t +end + +-- return a transformer function for the x position of the wheel items +-- changes parameters based on the getWheelPosition result +local function getFrameTransformer() + local direction = getWheelPosition() and 1 or -1 + + return function(frame, offsetFromCenter, index, total) + -- this stuff makes the x position of the item go way off screen for the end indices + -- should induce less of a feeling of items materializing from nothing + local bias = -actuals.Width * 3 + local ofc = math.ceil(total / 2) + offsetFromCenter + -- the power of 50 and the rounding here are kind of specific for our application + -- if you mess with overall parameters to the wheel size or count, you will want to mess with this + -- maybe + local result = math.round(math.pow(ofc / ((total - 2) / 2) - (((total + 2) / 2) / ((total - 2) / 2)), 50), 2) + local xp = bias * result * direction + frame:xy(xp, offsetFromCenter * actuals.ItemHeight) + end +end + +-- see songActorBuilder comment +local function groupActorBuilder() + return Def.ActorFrame { + Name = "GroupFrame", + wheelItemBase(), + LoadFont("Common Normal") .. { + Name = "GroupName", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemGroupTextSize) + self:halign(0) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + -- we make the background of groups fully opaque to distinguish them from songs + local itembg = self:GetParent():GetChild("WheelItemBase"):GetChild("ItemBG") + itembg:diffusealpha(1) + registerActorToColorConfigElement(itembg, "musicWheel", "FolderBackground") + end, + BeginCommand = function(self) + self:GetParent().Title = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupTextSize - textzoomfudge) + end + else + self:x((-actuals.Width / 2) + actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) + if useWheelBanners() then + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupTextSize - textzoomfudge) + else + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupTextSize - textzoomfudge) + end + end + self:y(-actuals.ItemHeight / 2 + actuals.ItemTextUpperGap) + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + LoadFont("Common Normal") .. { + Name = "GroupInfo", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemGroupInfoTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + self.avg = 0 + self.count = 0 + end, + BeginCommand = function(self) + self:GetParent().GroupInfo = self + end, + SetInfoCommand = function(self, params) + self.count = params.count + self.avg = params.avg + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + self:visible(not WHEELDATA:inSortModeMenu()) + self:settextf("%d Songs (Avg %5.2f)", self.count, self.avg) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(0) + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupInfoTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupInfoTextSize - textzoomfudge) + end + else + self:halign(1) + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.BannerWidth - actuals.ItemGradeTextRightGap) + self:maxwidth((actuals.ItemDividerLength - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap * 2) / wheelItemGroupInfoTextSize - textzoomfudge) + else + self:x(actuals.Width / 2 - actuals.ItemGradeTextRightGap) + self:maxwidth(((actuals.ItemDividerLength + actuals.BannerWidth) - actuals.ItemGradeTextMaxWidth - actuals.ItemGradeTextRightGap) / wheelItemGroupInfoTextSize - textzoomfudge) + end + end + self:y(actuals.ItemHeight / 2 - actuals.ItemTextLowerGap) + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + }, + LoadFont("Common Normal") .. { + Name = "ClearStats", + InitCommand = function(self) + self:playcommand("SetPosition") + self:zoom(wheelItemGradeTextSize) + self.lamp = nil + self.scores = 0 + end, + BeginCommand = function(self) + self:GetParent().ClearStats = self + end, + SetInfoCommand = function(self, params) + self.lamp = params.stats.lamp + self.scores = params.stats.totalScores + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local lstr = "" + if self.lamp ~= nil then + if self.lamp ~= "Grade_Tier20" then + lstr = THEME:GetString("Grade", self.lamp:sub(#"Grade_T")) + self:diffuse(colorByGrade(self.lamp)) + else + lstr = "Clear" + -- color for a clear + self:diffuse(colorByClearType("Clear")) + end + end + self:visible(not WHEELDATA:inSortModeMenu()) + self:settext(lstr) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(1) + self:x(actuals.Width / 2 - actuals.ItemGradeTextRightGap) + self:maxwidth(actuals.ItemGradeTextMaxWidth / wheelItemGradeTextSize) + else + self:halign(0.5) + self:x(-actuals.Width / 2 + (actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) / 2) + self:maxwidth((actuals.ItemGradeTextMaxWidth - (actuals.ItemGradeTextRightGap)) / wheelItemGradeTextSize) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + }, + scoreStatsFrame() .. { + InitCommand = function(self) + self:playcommand("UpdateWheelBanners") + self:y(actuals.ItemHeight / 30) + end, + BeginCommand = function(self) + self:GetParent().ScoreStats = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(actuals.Width / 2 - actuals.ItemDividerLength) + else + self:x(actuals.Width / 2 - actuals.ItemDividerLength - actuals.BannerWidth) + end + else + self:x((-actuals.Width / 2) + actuals.ItemGradeTextMaxWidth + actuals.ItemGradeTextRightGap) + end + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + }, + Def.Sprite { + Name = "Banner", + InitCommand = function(self) + -- y is already set: relative to "center" + self:playcommand("SetPosition") + self:scaletoclipped(actuals.BannerWidth, actuals.ItemHeight) + -- dont play movies because they lag the wheel so much like wow please dont ever use those (for now) + self:SetDecodeMovie(false) + end, + BeginCommand = function(self) + self:GetParent().Banner = self + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:x(-actuals.Width / 2):halign(0) + else + self:x(actuals.Width / 2):halign(1) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + } + } +end + +t[#t+1] = Def.ActorFrame { + Name = "WheelContainer", + InitCommand = function(self) + -- push from top left of screen, this position is CENTER of the wheel X/Y + -- also for some odd reason we have to move down by half a wheelItem.... + self:xy(actuals.LeftGap + actuals.Width / 2, actuals.UpperGap + actuals.Height / 2 - actuals.ItemHeight + actuals.wtffudge) + SCREENMAN:set_input_redirected(PLAYER_1, true) + end, + BeginCommand = function(self) + -- hide the old musicwheel + SCREENMAN:GetTopScreen():GetMusicWheel():visible(false) + end, + OpenedGroupMessageCommand = function(self, params) + openedGroup = params.group + end, + ClosedGroupMessageCommand = function(self) + openedGroup = "" + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("UpdateWheel") + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetFrameTransformer", {f = getFrameTransformer()}) + end, + + + -- because of the above, all of the X/Y positions are "relative" to center of the wheel + -- ugh + MusicWheel:new({ + count = numWheelItems, + startOnPreferred = true, + songActorBuilder = songActorBuilder, + groupActorBuilder = groupActorBuilder, + highlightBuilder = function() return Def.ActorFrame { + Name = "HighlightFrame", + Def.Quad { + Name = "Highlight", + InitCommand = function(self) + -- the highlighter should not cover the banner + -- move it by half the size and make it that much smaller + self:playcommand("SetPosition") + self:diffusealpha(0.2) + self:diffuseramp() + self:effectclock("beat") + registerActorToColorConfigElementForDiffuseRamp(self, "musicWheel", "HighlightColor", 0.5, 0.8) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + if useWheelBanners() then + self:x(actuals.BannerWidth / 2) + self:zoomto(actuals.Width - actuals.BannerWidth, actuals.ItemHeight) + else + self:x(0) + self:zoomto(actuals.Width, actuals.ItemHeight) + end + else + if useWheelBanners() then + self:x(-actuals.BannerWidth / 2) + self:zoomto(actuals.Width - actuals.BannerWidth, actuals.ItemHeight) + else + self:x(0) + self:zoomto(actuals.Width, actuals.ItemHeight) + end + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + UpdateWheelBannersCommand = function(self) + self:playcommand("SetPosition") + end, + } + } + end, + songActorUpdater = songActorUpdater, + groupActorUpdater = groupActorUpdater, + frameTransformer = getFrameTransformer(), + frameBuilder = function() + local f + f = Def.ActorFrame { + Name = "ItemFrame", + InitCommand = function(self) + f.actor = self + end, + UIElements.QuadButton(1) .. { + Name = "WheelItemClickBox", + InitCommand = function(self) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.ItemHeight) + end, + MouseDownCommand = function(self, params) + if not visible then return end + if params.event == "DeviceButton_left mouse button" then + local index = self:GetParent().index + -- subtract 1 here BASED ON numWheelItems + -- ... i know its dumb but it works for the params i set myself + -- if you mess with numWheelItems YOU NEED TO MAKE SURE THIS WORKS + local distance = math.floor(index - numWheelItems / 2) - 1 + local wheel = self:GetParent():GetParent() + if distance ~= 0 then + -- clicked a nearby item + wheel:playcommand("Move", {direction = distance}) + wheel:playcommand("OpenIfGroup") + else + -- clicked the current item + wheel:playcommand("SelectCurrent") + end + end + end + }, + + groupActorBuilder() .. { + BeginCommand = function(self) + f.actor.g = self + end + }, + songActorBuilder() .. { + BeginCommand = function(self) + f.actor.s = self + end + } + } + return f + end, + frameUpdater = function(frame, songOrPack, offset, isCurrentItem) + if songOrPack.GetAllSteps then + -- This is a song + local s = frame.s + s:visible(true) + local g = frame.g + g:visible(false) + songActorUpdater(s, songOrPack, isCurrentItem) + else + -- This is a group + local s = frame.s + s:visible(false) + local g = (frame.g) + g:visible(true) + groupActorUpdater(g, songOrPack, isCurrentItem) + end + end + }), + + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:diffusealpha(0) + -- the sizing here should make everything left of the wheel a mousewheel region + -- and also just a bit above and below it + -- and also the empty region to the right + -- the wheel positioning is not as clear as it could be + self:halign(0) + self:y(-(actuals.HeaderHeight + actuals.ItemHeight) / 2) + self:playcommand("SetPosition") + self:zoomto(actuals.GeneralBoxLeftGap, actuals.Height + actuals.HeaderHeight * 2.45) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(0) + self:x(-actuals.LeftGap - actuals.Width / 2) + else + self:halign(1) + self:x(actuals.LeftGap + actuals.Width / 2) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and visible then + if params.direction == "Up" then + self:GetParent():GetChild("Wheel"):playcommand("Move", {direction = -1}) + else + self:GetParent():GetChild("Wheel"):playcommand("Move", {direction = 1}) + end + end + end, + MouseClickPressMessageCommand = function(self, params) + if params ~= nil and params.button ~= nil and visible then + if params.button == "DeviceButton_right mouse button" then + if isOver(self) then + SCREENMAN:GetTopScreen():PauseSampleMusic() + end + end + end + end + }, + + Def.ActorFrame { + Name = "ScrollBar", + InitCommand = function(self) + self:playcommand("SetWPosition") + -- places the frame at the top of the wheel + -- positions will be relative to that + self:y(-actuals.ItemHeight * numWheelItems / 2 + actuals.ItemHeight * 1.242) + end, + SetWPositionCommand = function(self) + if getWheelPosition() then + self:x(-actuals.LeftGap / 2 - actuals.Width / 2) + else + self:x(actuals.Width / 2 + actuals.ScrollBarWidth / 2) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetWPosition") + end, + + Def.Sprite { + Name = "BG", + Texture = THEME:GetPathG("", "roundedCapsBar"), + InitCommand = function(self) + self:valign(0) + self:diffuse(color("0,0,0")) + self:diffusealpha(0.6) + self:zoomto(actuals.ScrollBarWidth, actuals.ScrollBarHeight) + end, + }, + UIElements.QuadButton(1) .. { + Name = "ClickBox", + InitCommand = function(self) + self:valign(0) + self:diffusealpha(0) + self:zoomto(actuals.ScrollBarWidth * 2.5, actuals.ScrollBarHeight) + end, + MouseDownCommand = function(self, params) + if not visible then return end + if params.event == "DeviceButton_left mouse button" then + local max = self:GetZoomedHeight() + local dist = params.MouseY + self:GetParent():GetParent():GetChild("Wheel"):playcommand("Move", {percent = dist / max}) + end + end, + MouseDragCommand = function(self, params) + if not visible then return end + if params.event == "DeviceButton_left mouse button" then + local max = self:GetZoomedHeight() + local dist = params.MouseY + self:GetParent():GetParent():GetChild("Wheel"):playcommand("Move", {percent = dist / max}) + end + end, + }, + Def.Sprite { + Name = "Position", + Texture = THEME:GetPathG("", "marker"), + InitCommand = function(self) + self:zoomto(actuals.ScrollBarWidth, actuals.ScrollBarWidth) + end, + SetPositionCommand = function(self, params) + -- something really quirky here is that last element of the wheel is considered the first + -- its a side effect of the currentSelection index being moved in the wheel so that the + -- highlight is centered. + -- this is a bad thing, but at least thats the explanation + local maxY = self:GetParent():GetChild("BG"):GetZoomedHeight() + local dist = params.index / params.maxIndex * maxY + self:finishtweening() + self:linear(0.05) + self:y(dist) + end, + WheelIndexChangedMessageCommand = function(self, params) + self:playcommand("SetPosition", params) + end, + WheelSettledMessageCommand = function(self, params) + self:playcommand("SetPosition", params) + end, + ModifiedGroupsMessageCommand = function(self, params) + self:playcommand("SetPosition", params) + end, + } + }, +} + +t[#t+1] = Def.ActorFrame { + Name = "WheelHeader", + InitCommand = function(self) + self:xy(actuals.LeftGap,actuals.HeaderUpperGap) + end, + ClosedGroupMessageCommand = function(self) + self:playcommand("ScrolledOutOfGroup") + end, + ScrolledIntoGroupMessageCommand = function(self) + self:GetChild("MiscPage"):playcommand("Out") + self:GetChild("GroupPage"):playcommand("In") + end, + ScrolledOutOfGroupMessageCommand = function(self) + self:GetChild("GroupPage"):playcommand("Out") + self:GetChild("MiscPage"):playcommand("In") + end, + OnCommand = function(self) + if openedGroup == nil or openedGroup == "" then + self:GetChild("GroupPage"):playcommand("Out") + self:GetChild("MiscPage"):playcommand("In") + end + end, + + UIElements.QuadButton(1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.HeaderHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "musicWheel", "HeaderBackground") + end, + MouseDownCommand = function(self, params) + if not visible then return end + if params.event == "DeviceButton_left mouse button" then + if not self:GetParent():GetChild("GroupPage"):IsInvisible() then + -- left clicking the group header gives a random song in the group + local song = WHEELDATA:GetRandomSongInFolder(openedGroup) + self:GetParent():GetParent():GetChild("WheelContainer"):playcommand("FindSong", {song = song, group = openedGroup}) + elseif not self:GetParent():GetChild("MiscPage"):IsInvisible() then + -- left clicking the normal header gives a random group (???) + local group = WHEELDATA:GetRandomFolder() + self:GetParent():GetParent():GetChild("WheelContainer"):playcommand("FindGroup", {group = group}) + end + end + end, + MouseOverCommand = function(self) + self:GetParent():diffusealpha(hoverAlpha) + MESSAGEMAN:Broadcast("HoverWheelHeader", {on = true}) + end, + MouseOutCommand = function(self) + self:GetParent():diffusealpha(1) + MESSAGEMAN:Broadcast("HoverWheelHeader", {off = true}) + end, + }, + Def.ActorFrame { + Name = "GroupPage", + InitCommand = function(self) + self:diffusealpha(0) + end, + InCommand = function(self) + self:finishtweening() + self:smooth(headerTransitionSeconds) + self:diffusealpha(1) + self:playcommand("Set") + end, + OutCommand = function(self) + self:finishtweening() + self:smooth(headerTransitionSeconds) + self:diffusealpha(0) + end, + + Def.Sprite { + Name = "Banner", + InitCommand = function(self) + self:halign(0):valign(0) + self:scaletoclipped(actuals.HeaderBannerWidth, actuals.HeaderHeight) + self:SetDecodeMovie(useVideoBanners()) + end, + SetCommand = function(self) + local bnpath = WHEELDATA:GetFolderBanner(openedGroup) + if not bnpath or bnpath == "" then + bnpath = THEME:GetPathG("Common", "fallback banner") + self:visible(false) + else + self:visible(true) + end + self:Load(bnpath) + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Video Banners" then + self:SetDecodeMovie(useVideoBanners()) + end + end, + }, + LoadFont("Common Normal") .. { + Name = "GroupTitle", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.HeaderBannerWidth + actuals.HeaderTextLeftGap, actuals.HeaderText1UpperGap) + self:zoom(wheelHeaderTextSize) + self:maxwidth((actuals.Width - actuals.HeaderTextLeftGap * 2 - actuals.HeaderBannerWidth) / wheelHeaderTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + self:settext(openedGroup) + end + }, + LoadFont("Common Normal") .. { + Name = "GroupInfo", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.HeaderBannerWidth + actuals.HeaderTextLeftGap, actuals.HeaderText2UpperGap) + self:zoom(wheelHeaderTextSize) + self:maxwidth((actuals.Width - actuals.HeaderTextLeftGap * 2 - actuals.HeaderBannerWidth) / wheelHeaderTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetCommand = function(self) + local files = WHEELDATA:GetFolderCount(openedGroup) + local avg = WHEELDATA:GetFolderAverageDifficulty(openedGroup)[1] + self:settextf("%d Songs (Average MSD: %5.2f)", files, avg) + end + } + }, + Def.ActorFrame { + Name = "MiscPage", + InitCommand = function(self) + self:diffusealpha(0) + self:SetUpdateFunction(function(self) + if not self:IsInvisible() then + self:GetChild("SessionTime"):playcommand("Set") + end + end) + self:SetUpdateFunctionInterval(0.5) + end, + InCommand = function(self) + self:finishtweening() + self:smooth(headerTransitionSeconds) + self:diffusealpha(1) + self:playcommand("Set") + end, + OutCommand = function(self) + self:finishtweening() + self:smooth(headerTransitionSeconds) + self:diffusealpha(0) + end, + + LoadFont("Common Normal") .. { + Name = "SessionTime", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.HeaderMTextLeftGap, actuals.HeaderMText1UpperGap) + self:zoom(wheelHeaderMTextSize) + self:maxwidth((actuals.BannerWidth - actuals.HeaderMTextLeftGap) / wheelHeaderMTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + local sesstime = GAMESTATE:GetSessionTime() + self:settextf("Session Time: %s", SecondsToHHMMSS(sesstime)) + end + }, + LoadFont("Common Normal") .. { + Name = "SessionPlays", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.HeaderMTextLeftGap, actuals.HeaderMText2UpperGap) + self:zoom(wheelHeaderMTextSize) + self:maxwidth((actuals.BannerWidth - actuals.HeaderMTextLeftGap) / wheelHeaderMTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + self:settextf("Session Plays: %d", playsThisSession) + end + }, + LoadFont("Common Normal") .. { + Name = "AverageAccuracy", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.HeaderMTextLeftGap, actuals.HeaderMText3UpperGap) + self:zoom(wheelHeaderMTextSize) + self:maxwidth((actuals.BannerWidth - actuals.HeaderMTextLeftGap) / wheelHeaderMTextSize) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + self:settextf("Average Accuracy: %5.2f%%", accThisSession) + end + }, + Def.ActorFrame { + Name = "Graph", + InitCommand = function(self) + -- the graph is placed relative to the top of the header and left aligned the same as the wheel items left alignment + self:x(actuals.Width - actuals.ItemDividerLength) + end, + -- the "vertical space" of the graph is essentially 6/8 of the height of the header + -- 1/8 from the top and 1/8 from the bottom + -- the "width" of the graph is (the width - banner width - distance from left banner - distance from right) + -- not a clear number + + Def.Quad { + Name = "YAxisLine", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(actuals.HeaderMTextLeftGap) + self:y(actuals.HeaderHeight / 8) + self:zoomto(actuals.ItemDividerThickness, actuals.HeaderHeight / 8 * 6) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "musicWheel", "GraphLine") + end, + }, + Def.Quad { + Name = "XAxisLine", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(actuals.HeaderMTextLeftGap) + self:y(actuals.HeaderHeight - actuals.HeaderHeight / 8) + self:zoomto(graphWidth, actuals.ItemDividerThickness) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "musicWheel", "GraphLine") + end, + }, + LoadFont("Common Normal") .. { + Name = "YMax", + InitCommand = function(self) + self:halign(1) + self:x(actuals.HeaderMTextLeftGap - graphBoundOffset / 2) + self:y(actuals.HeaderHeight / 8 - graphBoundOffset) + self:rotationz(-45) + self:zoom(graphBoundTextSize) + self:settextf("%d%%", notShit.round(graphUpperBound)) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + }, + LoadFont("Common Normal") .. { + Name = "YMin", + InitCommand = function(self) + self:halign(1) + self:x(actuals.HeaderMTextLeftGap - graphBoundOffset / 2) + self:y(actuals.HeaderHeight / 8 * 7 - graphBoundOffset) + self:rotationz(-45) + self:zoom(graphBoundTextSize) + self:settextf("%d%%", notShit.round(graphLowerBound)) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + }, + + Def.ActorMultiVertex { + Name = "Line", + InitCommand = function(self) + self:x(actuals.HeaderMTextLeftGap) + self:y(actuals.HeaderHeight / 8 * 7) + self:playcommand("SetGraph") + end, + SetGraphCommand = function(self) + local v = generateRecentWifeScoreGraph() + if #v > 1 then + self:SetVertices(v) + self:SetDrawState {Mode = "DrawMode_LineStrip", First = 1, Num = #v} + else + self:SetVertices({}) + self:SetDrawState {Mode = "DrawMode_LineStrip", First = 1, Num = 0} + end + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetGraph") + end, + } + } + } +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic overlay/default.lua new file mode 100644 index 0000000000..ea90e5da54 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic overlay/default.lua @@ -0,0 +1,8 @@ +local t = Def.ActorFrame {} + +t[#t+1] = LoadActor("../_mouse.lua") + +-- header +t[#t+1] = LoadActorWithParams("../playerInfoFrame/main.lua", {visualizer = themeConfig:get_data().global.ShowVisualizer, screen = "ScreenSelectMusic"}) + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectMusic underlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenSelectMusic underlay/default.lua new file mode 100644 index 0000000000..b5596f0781 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectMusic underlay/default.lua @@ -0,0 +1,88 @@ +local showbg = function() return themeConfig:get_data().global.ShowBackgrounds end +local t = Def.ActorFrame { + Name = "UnderlayFile", + WheelSettledMessageCommand = function(self, params) + -- cascade visual update to everything + self:playcommand("Set", { + song = params.song, + group = params.group, + hovered = params.hovered, + steps = params.steps + }) + end, +} + +local function loadbg(self) + self:finishtweening() + self:visible(true) + if self.bgpath == self.loadedpath then return end + self:LoadBackground(self.bgpath) + self.loadedpath = self.bgpath + self:scale_or_crop_background() + self:smooth(0.5) + self:diffusealpha(0.3) +end + +-- reset context manager as early as possible in the selectmusic init process +-- this should be a safe place to do it, between all context manager registrations +CONTEXTMAN:Reset() + +t[#t+1] = Def.Sprite { + Name = "BG", + InitCommand = function(self) + self:diffusealpha(0) + end, + SetCommand = function(self, params) + self:finishtweening() + self.bgpath = nil + if params.song and params.song:GetBackgroundPath() then + self.bgpath = params.song:GetBackgroundPath() + if not showbg() then self:visible(false) return end + loadbg(self) + else + self:visible(false) + end + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Show Backgrounds" then + if self.bgpath then + if showbg() then + self:visible(true) + loadbg(self) + else + self:visible(false) + end + end + end + end, +} + +t[#t+1] = Def.Quad { + Name = "AverageColor", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(SCREEN_WIDTH, SCREEN_BOTTOM) + self:diffuse(color("0,0,0")) + self:diffusealpha(0) + end, + SetCommand = function(self, params) + self:finishtweening() + if params.song and params.song:GetBackgroundPath() then + self:visible(false) + end + end, + SetAverageColorMessageCommand = function(self, params) + self:finishtweening() + local bn = params.actor + if bn:GetVisible() then + self:visible(true) + local c = bn:GetTexture():GetAverageColor(14) + self:diffuse(c) + self:diffusealpha(0.7) + else + self:visible(false) + end + end, +} + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenSelectProfile overlay.lua b/Themes/Rebirth/BGAnimations/ScreenSelectProfile overlay.lua new file mode 100644 index 0000000000..2089f3ed59 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSelectProfile overlay.lua @@ -0,0 +1,197 @@ +local translated_info = { + Title = "Select Profile", + SongPlayed = "Song Played", + SongsPlayed = "Songs Played", + NoProfile = "No Profile", + PressStart = "Press Start" +} + +function GetLocalProfiles() + local t = {} + + for p = 0, PROFILEMAN:GetNumLocalProfiles() - 1 do + local profileID = PROFILEMAN:GetLocalProfileIDFromIndex(p) + local profile = PROFILEMAN:GetLocalProfileFromIndex(p) + local ProfileCard = + Def.ActorFrame { + Name = p, + LoadFont("Common Large") .. + { + Text = string.format("%s: %.2f", profile:GetDisplayName(), profile:GetPlayerRating()), + InitCommand = function(self) + self:y(-10):zoom(0.4):ztest(true, maxwidth, (200 - 34 - 4) / 0.4) + end + }, + LoadFont("Common Normal") .. + { + InitCommand = function(self) + self:y(8):zoom(0.5):vertspacing(-8):ztest(true):maxwidth((200 - 34 - 4) / 0.5) + end, + BeginCommand = function(self) + local numSongsPlayed = profile:GetNumTotalSongsPlayed() + local s = numSongsPlayed == 1 and translated_info["SongPlayed"] or translated_info["SongsPlayed"] + self:settext(numSongsPlayed .. " " .. s) + end + } + } + t[#t + 1] = ProfileCard + end + + return t +end + +function LoadPlayerStuff(Player) + local t = {} + t[#t + 1] = + Def.ActorFrame { + Name = "SmallFrame", + InitCommand = function(self) + self:y(-2) + end, + Def.Quad { + InitCommand = function(self) + self:zoomto(200, 40 + 2) + end, + OnCommand = function(self) + self:diffusealpha(0.3) + end + } + } + + t[#t + 1] = + Def.ActorScroller { + Name = "Scroller", + NumItemsToDraw = 6, + OnCommand = function(self) + self:y(1):SetFastCatchup(true):SetMask(200, 58):SetSecondsPerItem(0.15) + end, + TransformFunction = function(self, offset, itemIndex, numItems) + local focus = scale(math.abs(offset), 0, 2, 1, 0) + self:visible(false) + self:y(math.floor(offset * 40)) + end, + children = GetLocalProfiles() + } + + return t +end + +function UpdateInternal3(self, Player) + local pn = (Player == PLAYER_1) and 1 + local frame = self:GetChild(string.format("P%uFrame", pn)) + local scroller = frame:GetChild("Scroller") + local smallframe = frame:GetChild("SmallFrame") + + if GAMESTATE:IsHumanPlayer() then + frame:visible(true) + smallframe:visible(true) + scroller:visible(true) + local ind = SCREENMAN:GetTopScreen():GetProfileIndex(Player) + if ind > 0 then + scroller:SetDestinationItem(ind - 1) + else + if SCREENMAN:GetTopScreen():SetProfileIndex(Player, 1) then + scroller:SetDestinationItem(0) + self:queuecommand("UpdateInternal2") + else + smallframe:visible(false) + scroller:visible(false) + end + end + else + scroller:visible(false) + smallframe:visible(false) + end +end + +local theThingVeryImportant +local startSound + +local function input(event) + if event.type == "InputEventType_FirstPress" then + if event.button == "Back" then + SCREENMAN:GetTopScreen():Cancel() + elseif event.button == "Start" then + startSound:queuecommand("StartButton") + elseif event.button == "Down" or event.button == "MenuDown" then + local ind = SCREENMAN:GetTopScreen():GetProfileIndex(PLAYER_1) + if ind > 0 then + if SCREENMAN:GetTopScreen():SetProfileIndex(PLAYER_1, ind + 1) then + MESSAGEMAN:Broadcast("DirectionButton") + theThingVeryImportant:queuecommand("UpdateInternal2") + end + end + elseif event.button == "Up" or event.button == "MenuUp" then + local ind = SCREENMAN:GetTopScreen():GetProfileIndex(PLAYER_1) + if ind > 1 then + if SCREENMAN:GetTopScreen():SetProfileIndex(PLAYER_1, ind - 1) then + MESSAGEMAN:Broadcast("DirectionButton") + theThingVeryImportant:queuecommand("UpdateInternal2") + end + end + end + end + return false +end + +local t = Def.ActorFrame {} + +t[#t + 1] = + Def.ActorFrame { + InitCommand = function(self) + theThingVeryImportant = self + end, + OnCommand = function(self) + SCREENMAN:GetTopScreen():SetProfileIndex(PLAYER_1, 0) + end, + OnCommand = function(self, params) + self:queuecommand("UpdateInternal2") + end, + UpdateInternal2Command = function(self) + UpdateInternal3(self, PLAYER_1) + end, + children = { + Def.ActorFrame { + Name = "P1Frame", + InitCommand = function(self) + self:x(SCREEN_CENTER_X):y(SCREEN_CENTER_Y) + end, + OnCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback(input) + self:zoom(0):bounceend(0.2):zoom(1) + end, + OffCommand = function(self) + self:bouncebegin(0.2):zoom(0) + end, + PlayerJoinedMessageCommand = function(self, param) + if param.Player == PLAYER_1 then + self:zoom(1.15):bounceend(0.175):zoom(1.0) + end + end, + children = LoadPlayerStuff(PLAYER_1) + }, + -- sounds + LoadActor(THEME:GetPathS("Common", "start")) .. + { + InitCommand = function(self) + startSound = self + end, + StartButtonCommand = function(self) + self:play() + self:sleep(0.2) + self:queuecommand("Done") + end, + DoneCommand = function(self) + SCREENMAN:GetTopScreen():Finish() + end + }, + LoadActor(THEME:GetPathS("Common", "value")) .. + { + DirectionButtonMessageCommand = function(self) + self:play() + end + } + } +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenSystemLayer overlay.lua b/Themes/Rebirth/BGAnimations/ScreenSystemLayer overlay.lua new file mode 100644 index 0000000000..020003bc50 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenSystemLayer overlay.lua @@ -0,0 +1,97 @@ +local t = Def.ActorFrame {} +-- Controls the overlay ScreenSystemLayer +-- meant more for critical messages +-- this is mostly identical to the Til Death instance of this file +-- but is not stored in fallback in this condition due to the possibility of not supporting some features of this overlay +-- for example, we may not want to display downloads on the overlay or something else + +-- Text +t[#t + 1] = Def.ActorFrame { + Def.Quad { + InitCommand = function(self) + self:zoomtowidth(SCREEN_WIDTH) + self:zoomtoheight(30) + self:halign(0):valign(0) + self:y(SCREEN_TOP) + self:diffuse(color("0,0,0,0")) + end, + OnCommand = function(self) + self:finishtweening() + self:diffusealpha(0.85) + end, + OffCommand = function(self) + self:sleep(3):linear(0.5) + self:diffusealpha(0) + end + }, + Def.BitmapText { + Font = "Common Normal", + Name = "Text", + InitCommand = function(self) + self:maxwidth(SCREEN_WIDTH * 0.8) + self:halign(0):valign(0) + self:xy(SCREEN_LEFT + 10, SCREEN_TOP + 10) + self:diffusealpha(0) + end, + OnCommand = function(self) + self:finishtweening() + self:diffusealpha(1) + self:zoom(0.5) + end, + OffCommand = function(self) + self:sleep(3):linear(0.5) + self:diffusealpha(0) + end + }, + SystemMessageMessageCommand = function(self, params) + self:GetChild("Text"):settext(params.Message) + self:playcommand("On") + if params.NoAnimate then + self:finishtweening() + end + self:playcommand("Off") + end, + HideSystemMessageMessageCommand = function(self) + self:finishtweening() + end +} + +-- song reload +local www = 1366 * 0.8 +local hhh = SCREEN_HEIGHT * 0.8 +local rtzoom = 0.6 + +t[#t + 1] = + Def.ActorFrame { + DFRStartedMessageCommand = function(self) + self:visible(true) + end, + DFRFinishedMessageCommand = function(self, params) + self:visible(false) + end, + BeginCommand = function(self) + self:visible(false) + self:x(www / 8 + 10) + self:y(SCREEN_BOTTOM - hhh / 8 - 70) + end, + Def.Quad { + InitCommand = function(self) + self:zoomto(www / 4, hhh / 4) + self:diffuse(color("0.1,0.1,0.1,0.8")) + end + }, + Def.BitmapText { + Font = "Common Normal", + InitCommand = function(self) + self:diffusealpha(0.9) + self:zoom(rtzoom) + self:settext("") + self:maxwidth((www / 4 - 40) / rtzoom) + end, + DFRUpdateMessageCommand = function(self, params) + self:settext(params.txt) + end + } +} + +return t diff --git a/Themes/_fallback/Graphics/BeginnerHelper background.redir b/Themes/Rebirth/BGAnimations/ScreenTextEntry out.redir similarity index 100% rename from Themes/_fallback/Graphics/BeginnerHelper background.redir rename to Themes/Rebirth/BGAnimations/ScreenTextEntry out.redir diff --git a/Themes/Rebirth/BGAnimations/ScreenTextEntry overlay.lua b/Themes/Rebirth/BGAnimations/ScreenTextEntry overlay.lua new file mode 100644 index 0000000000..53c9e0e6d5 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenTextEntry overlay.lua @@ -0,0 +1,10 @@ +return Def.ActorFrame { + Name = "TextEntryOverlayFile", + + LoadFont("Common Normal") .. { + Name = "Failure", + InitCommand = function(self) + self:visible(false) + end + } +} \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenTextEntry underlay.lua b/Themes/Rebirth/BGAnimations/ScreenTextEntry underlay.lua new file mode 100644 index 0000000000..60eb7c7d65 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenTextEntry underlay.lua @@ -0,0 +1,124 @@ +local boxWidth = SCREEN_WIDTH / 2.5 +local boxHeight = SCREEN_HEIGHT / 3 + +local sideMargin = 15 / 1920 * SCREEN_WIDTH +local bottomMargin = 15 / 1080 * SCREEN_HEIGHT +local INFORMATIONupperGap = 9 / 1080 * SCREEN_HEIGHT +local explainTextUpperGap = 20 / 1080 * SCREEN_HEIGHT +local wtf = 1 / 1080 * SCREEN_HEIGHT -- wtf +local INFORMATIONBOXHEIGHT = 50 / 1080 * SCREEN_HEIGHT +local exitwidth = 70 / 1920 * SCREEN_WIDTH +local exitheight = 50 / 1080 * SCREEN_HEIGHT + +local dimAlpha = 0.6 +local boxAlpha = 0.5 +local hoverAlpha = 0.6 +local bgColor = color("0,0,0") +local boxColor = color("0,0,0") + +local exittextsize = 0.86 +local explaintextsize = 1 +local INFORMATIONtextsize = 1 + +return Def.ActorFrame { + Name = "TextEntryUnderlayFile", + InitCommand = function(self) + self:diffusealpha(0) + end, + OnCommand = function(self) + local question = self:GetParent():GetChild("Question") + local answer = self:GetParent():GetChild("Answer") + + self:smooth(0.5) + self:diffusealpha(1) + if question then + question:maxwidth(boxWidth / question:GetZoom()) + end + if answer then + answer:maxwidth(boxWidth / answer:GetZoom()) + end + end, + + Def.Quad { + Name = "DimBG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + self:diffuse(bgColor) + self:diffusealpha(dimAlpha) + end + }, + Def.ActorFrame { + InitCommand = function(self) + self:xy(SCREEN_CENTER_X, SCREEN_CENTER_Y) + end, + BeginCommand = function(self) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type == "InputEventType_FirstPress" then + if event.DeviceInput.button == "DeviceButton_left mouse button" then + self:playcommand("PressyMyMouseButton") + end + end + end) + self:SetUpdateFunction(function(self) self:playcommand("HighlightyMyMouseHovering") end) + end, + + Def.Quad { + Name = "MainBG", + InitCommand = function(self) + self:zoomto(boxWidth, boxHeight) + self:diffuse(boxColor) + self:diffusealpha(boxAlpha) + end + }, + Def.Sprite { + Texture = THEME:GetPathG("", "dialogTop"), + Name = "HeaderBox", + InitCommand = function(self) + self:valign(0) + self:y(-wtf - boxHeight/2) + self:zoomto(boxWidth, INFORMATIONBOXHEIGHT) + end + }, + Def.Sprite { + Texture = THEME:GetPathG("", "dialogExit"), + Name = "ExitButton", + InitCommand = function(self) + self:halign(1):valign(1) + self:zoomto(exitwidth, exitheight) + self:xy(boxWidth/2 - sideMargin, boxHeight/2 - bottomMargin) + end, + PressyMyMouseButtonCommand = function(self) + if isOver(self) then + SCREENMAN:GetTopScreen():Cancel() + end + end, + HighlightyMyMouseHoveringCommand = function(self) + if isOver(self) then + self:diffusealpha(hoverAlpha) + else + self:diffusealpha(1) + end + end + }, + LoadFont("Common Normal") .. { + Name = "INFORMATION", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(-boxWidth/2 + sideMargin, -boxHeight/2 + INFORMATIONupperGap) + self:zoom(INFORMATIONtextsize) + self:maxwidth(boxWidth / INFORMATIONtextsize) + self:settext("Information") + end + }, + LoadFont("Common Normal") .. { + Name = "Exit", + InitCommand = function(self) + self:zoom(exittextsize) + self:maxwidth(60 / 1920 * SCREEN_WIDTH / exittextsize) + self:xy(boxWidth/2 - sideMargin - exitwidth/2, boxHeight/2 - bottomMargin - exitheight/1.7) + self:settext("Exit") + end + } + } +} \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/default.lua b/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/default.lua new file mode 100644 index 0000000000..9ee63cf196 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/default.lua @@ -0,0 +1,8 @@ +local t = Def.ActorFrame {Name = "OverlayFile"} + +t[#t+1] = LoadActor("profileSelect") +t[#t+1] = LoadActor(THEME:GetPathG("", "_crashUploadOptIn")) +t[#t+1] = LoadActor("../_mouse.lua") +t[#t+1] = EGGMAN.snowyboy() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/profileSelect.lua b/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/profileSelect.lua new file mode 100644 index 0000000000..601f364c9b --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenTitleMenu overlay/profileSelect.lua @@ -0,0 +1,682 @@ +local t = Def.ActorFrame { + Name = "ProfileSelectFile", + InitCommand = function(self) + self:x(SCREEN_WIDTH) + end, + BeginCommand = function(self) + self:smooth(0.5) + self:x(0) + end, +} + +local ratios = { + FrameLeftGap = 1303 / 1920, -- left of screen to left of item bg + FrameUpperGap = 50 / 1080, -- from top of screen to top of first item bg + Width = 555 / 1920, + ItemHeight = 109 / 1080, + + ItemGlowVerticalSpan = 12 / 1080, -- measurement of visible portion of the glow doubled + ItemGlowHorizontalSpan = 12 / 1920, -- same + ItemGap = 57 / 1080, -- distance between the bg of items + + AvatarWidth = 109 / 1920, + + NameLeftGap = 8 / 1920, -- from edge of avatar to left edge of words + NameUpperGap = 11 / 1080, -- top edge to top edge + InfoLeftGap = 9 / 1920, -- from edge of avatar to left edge of lines below profile name + Info1UpperGap = 39 / 1080, -- top edge to top edge + Info2UpperGap = 62 / 1080, -- middle line + Info3UpperGap = 85 / 1080, -- bottom line + NameToRatingRequiredGap = 25 / 1920, -- we check this amount on player name lengths + -- if the distance is not at least this much, truncate the name until it works + + RatingLeftGap = 393 / 1920, -- left edge to left edge of text + -- this means the allowed space for this text is Width - RatingLeftGap + RatingInfoUpperGap = 11 / 1080, + OnlineUpperGap = 43 / 1080, + OfflineUpperGap = 79 / 1080, +} + +local actuals = { + FrameLeftGap = ratios.FrameLeftGap * SCREEN_WIDTH, + FrameUpperGap = ratios.FrameUpperGap * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + ItemHeight = ratios.ItemHeight * SCREEN_HEIGHT, + ItemGlowVerticalSpan = ratios.ItemGlowVerticalSpan * SCREEN_HEIGHT, + ItemGlowHorizontalSpan = ratios.ItemGlowHorizontalSpan * SCREEN_WIDTH, + ItemGap = ratios.ItemGap * SCREEN_HEIGHT, + AvatarWidth = ratios.AvatarWidth * SCREEN_WIDTH, + NameLeftGap = ratios.NameLeftGap * SCREEN_WIDTH, + NameUpperGap = ratios.NameUpperGap * SCREEN_HEIGHT, + InfoLeftGap = ratios.InfoLeftGap * SCREEN_WIDTH, + Info1UpperGap = ratios.Info1UpperGap * SCREEN_HEIGHT, + Info2UpperGap = ratios.Info2UpperGap * SCREEN_HEIGHT, + Info3UpperGap = ratios.Info3UpperGap * SCREEN_HEIGHT, + NameToRatingRequiredGap = ratios.NameToRatingRequiredGap * SCREEN_WIDTH, + RatingLeftGap = ratios.RatingLeftGap * SCREEN_WIDTH, + RatingInfoUpperGap = ratios.RatingInfoUpperGap * SCREEN_HEIGHT, + OnlineUpperGap = ratios.OnlineUpperGap * SCREEN_HEIGHT, + OfflineUpperGap = ratios.OfflineUpperGap * SCREEN_HEIGHT, +} + +local profileIDs = PROFILEMAN:GetLocalProfileIDs() +local renameNewProfile = false +local focused = false + +-- how many items to put on screen -- will fit for any screen height +local numItems = #profileIDs > 1 and math.floor(SCREEN_HEIGHT / (actuals.ItemHeight + actuals.ItemGap)) or 1 + +local nameTextSize = 0.75 +local playcountTextSize = 0.5 +local arrowsTextSize = 0.5 +local playTimeTextSize = 0.5 +local playerRatingsTextSize = 0.75 +local onlineTextSize = 0.6 +local offlineTextSize = 0.6 + +local textzoomFudge = 5 + +-- reset fadeout state +TITLE.triggeredFadeOut = false + +-- if there are no profiles, make a new one +if #profileIDs == 0 then + local new = PROFILEMAN:CreateDefaultProfile() + profileIDs = PROFILEMAN:GetLocalProfileIDs() + renameNewProfile = true +end + +local primaryTextColor = COLORS:getTitleColor("PrimaryText") +local secondaryTextColor = COLORS:getTitleColor("SecondaryText") +local itemBGColor = COLORS:getTitleColor("ProfileBackground") + +-- convenience to control the delete profile dialogue logic and input redir scope +local function deleteProfileDialogue(id) + if id == nil then id = "(SOMETHING WENT WRONG)" end + + local redir = SCREENMAN:get_input_redirected(PLAYER_1) + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + off() + + + askForInputStringWithFunction( + string.format("To delete this profile, navigate to Save/LocalProfiles\nDelete the folder '%s'\nRestart the game", id), + 0, + false, + function(answer) on() end, + function(answer) return true, "Response invalid." end, + function() on() end + ) +end + +local function generateItems() + -- add 1 to number of profiles so we can have a button to add profiles always as the last item + local maxPage = math.ceil((#profileIDs) / numItems) + local page = 1 + local selectionIndex = 1 + + local function createProfileDialogue(listframe) + -- uhh this shouldnt be hard... + -- make profile, update id list, rename new profile + local new = PROFILEMAN:CreateDefaultProfile() + profileIDs = PROFILEMAN:GetLocalProfileIDs() + maxPage = math.ceil((#profileIDs) / numItems) + renameProfileDialogue(new, true) + end + + -- select current option with keyboard or mouse double click + local function selectCurrent() + -- if holding control when this happens, trigger the delete profile dialogue + -- (secret functionality but just for the keyboard user convenience) + if INPUTFILTER:IsControlPressed() then + deleteProfileDialogue(profileIDs[selectionIndex]) + return + end + + PROFILEMAN:SetProfileIDToUse(profileIDs[selectionIndex]) + -- the sound should not play an additional time if we never allowed the profiles to be selected in the first place + -- this function is used to force immediate selection of the first profile when only 1 profile is present + if #profileIDs > 1 then + SCREENMAN:GetTopScreen():PlaySelectSound() + end + SCREENMAN:GetTopScreen():SetSelectionIndex(0) -- instantly select Game Start + TITLE:HandleFinalGameStart() + end + + -- move page with keyboard or mouse + local function movePage(n) + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + + MESSAGEMAN:Broadcast("MovedPage") + end + + -- move current selection using keyboard + local function move(n) + local beforeindex = selectionIndex + selectionIndex = clamp(selectionIndex + n, 1, #profileIDs) + local lowerbound = numItems * (page-1) + 1 + local upperbound = numItems * page + if lowerbound > selectionIndex or upperbound < selectionIndex then + page = clamp(math.floor((selectionIndex-1) / numItems) + 1, 1, maxPage) + MESSAGEMAN:Broadcast("MovedPage") + else + MESSAGEMAN:Broadcast("MovedIndex") + end + if beforeindex ~= selectionIndex then + SCREENMAN:GetTopScreen():PlayChangeSound() + end + end + + -- change focus back to scroller options with keyboard + local function backOut() + TITLE:ChangeFocus() + end + + + local function generateItem(i) + local index = i + local profile = nil + local id = nil + + return Def.ActorFrame { + Name = "Choice_"..i, + InitCommand = function(self) + self:y((i-1) * (actuals.ItemHeight + actuals.ItemGap)) + self:diffusealpha(0) + end, + BeginCommand = function(self) + self:playcommand("Set") + end, + ProfileRenamedMessageCommand = function(self) + self:playcommand("Set") + end, + MovedPageMessageCommand = function(self) + index = (page-1) * numItems + i + self:playcommand("Set") + end, + SetCommand = function(self) + if profileIDs[index] then + id = profileIDs[index] + profile = PROFILEMAN:GetLocalProfile(id) + self:finishtweening() + self:smooth(0.1) + self:diffusealpha(1) + else + id = nil + profile = nil + self:finishtweening() + self:smooth(0.1) + self:diffusealpha(0) + end + end, + + UIElements.QuadButton(3) .. { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.ItemHeight) + self:diffuse(itemBGColor) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then + -- handle profile creation here + if index == #profileIDs + 1 then + createProfileDialogue(self:GetParent():GetParent()) + return + end + + -- because of button layering, pretend we are clicking through this button when it is invisible + -- (this stuff immediately below is the expected behavior) + if focused then + TITLE:ChangeFocus() + end + else + if params.event == "DeviceButton_left mouse button" then + if INPUTFILTER:IsControlPressed() then + -- if holding ctrl, throw delete profile dialogue + -- ... well, I would do that if the game could delete profiles (thanks mina) + -- so instead I'll just give the necessary info + deleteProfileDialogue(id) + else + -- otherwise, move cursor or select current profile + if selectionIndex == index then + selectCurrent() + else + selectionIndex = index + MESSAGEMAN:Broadcast("MovedIndex") + end + end + elseif params.event == "DeviceButton_right mouse button" then + -- right clicking allows profile name change + -- the Actor parameter is asking for the frame that holds the whole list + renameProfileDialogue(profile) + end + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then + if index == #profileIDs + 1 then + -- show New Profile box (only on bottommost empty profile) + MESSAGEMAN:Broadcast("NewProfileToggle", {index = i}) + end + end + end, + MouseOutCommand = function(self) + if self:IsInvisible() then + if index == #profileIDs + 1 then + -- hide New Profile box + MESSAGEMAN:Broadcast("NewProfileToggle", {index = nil}) + end + end + end, + SetCommand = function(self) + if self:IsInvisible() then + if isOver(self) then + if index == #profileIDs + 1 then + -- show New Profile box + MESSAGEMAN:Broadcast("NewProfileToggle", {index = i}) + end + end + else + if isOver(self) then + -- hide New Profile box + MESSAGEMAN:Broadcast("NewProfileToggle", {index = nil}) + end + end + end + }, + Def.Sprite { + Name = "Avatar", + InitCommand = function(self) + self:halign(0):valign(0) + end, + SetCommand = function(self) + self:Load(getAssetPathFromProfileID("avatar", id)) + self:zoomto(actuals.AvatarWidth, actuals.ItemHeight) + end + }, + Def.ActorFrame { + Name = "LeftText", + InitCommand = function(self) + self:x(actuals.AvatarWidth) + end, + + LoadFont("Common Normal") .. { + Name = "NameRank", + InitCommand = function(self) + self:x(actuals.NameLeftGap) + self:y(actuals.NameUpperGap) + self:valign(0):halign(0) + self:zoom(nameTextSize) + -- this maxwidth probably wont cause issues + -- .... but if it does..... + self:maxwidth((actuals.RatingLeftGap - actuals.AvatarWidth - actuals.NameLeftGap) / nameTextSize - textzoomFudge) + self:diffuse(primaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + if profile then + local name = profile:GetDisplayName() + self:visible(false) + self:truncateToWidth(name, (actuals.RatingLeftGap - actuals.AvatarWidth - actuals.NameLeftGap) - textzoomFudge - 25) + self:visible(true) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Playcount", + InitCommand = function(self) + self:x(actuals.InfoLeftGap) + self:y(actuals.Info1UpperGap) + self:valign(0):halign(0) + self:zoom(playcountTextSize) + self:maxwidth((actuals.RatingLeftGap - actuals.AvatarWidth - actuals.InfoLeftGap) / playcountTextSize - textzoomFudge) + self:diffuse(secondaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + if profile then + local scores = profile:GetTotalNumSongsPlayed() + self:settextf("%d plays", scores) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Arrows", + InitCommand = function(self) + self:x(actuals.InfoLeftGap) + self:y(actuals.Info2UpperGap) + self:valign(0):halign(0) + self:zoom(arrowsTextSize) + self:maxwidth((actuals.RatingLeftGap - actuals.AvatarWidth - actuals.InfoLeftGap) / arrowsTextSize - textzoomFudge) + self:diffuse(secondaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + if profile then + local taps = profile:GetTotalTapsAndHolds() + self:settextf("%d arrows smashed", taps) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Playtime", + InitCommand = function(self) + self:x(actuals.InfoLeftGap) + self:y(actuals.Info3UpperGap) + self:valign(0):halign(0) + self:zoom(playTimeTextSize) + self:maxwidth((actuals.RatingLeftGap - actuals.AvatarWidth - actuals.InfoLeftGap) / playTimeTextSize - textzoomFudge) + self:diffuse(secondaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + if profile then + local secs = profile:GetTotalSessionSeconds() + self:settextf("%s playtime", SecondsToHHMMSS(secs)) + end + end + } + }, + Def.ActorFrame { + Name = "RightText", + InitCommand = function(self) + self:x(actuals.RatingLeftGap) + end, + + LoadFont("Common Normal") .. { + Name = "PlayerRatings", + InitCommand = function(self) + self:y(actuals.RatingInfoUpperGap) + self:valign(0):halign(0) + self:zoom(playerRatingsTextSize) + self:maxwidth((actuals.Width - actuals.RatingLeftGap) / playerRatingsTextSize - textzoomFudge) + self:settext("Player Ratings:") + self:diffuse(primaryTextColor) + self:diffusealpha(1) + end + }, + --[[-- online ratings for individual profiles have no direct api + LoadFont("Common Normal") .. { + Name = "Online", + InitCommand = function(self) + self:y(actuals.OnlineUpperGap) + self:valign(0):halign(0) + self:zoom(onlineTextSize) + self:maxwidth((actuals.Width - actuals.RatingLeftGap) / onlineTextSize - textzoomFudge) + self:diffuse(primaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + self:settext("Online - 00.00") + end + },]] + LoadFont("Common Normal") .. { + Name = "Offline", + InitCommand = function(self) + self:y(actuals.OfflineUpperGap) + self:valign(0):halign(0) + self:zoom(offlineTextSize) + self:maxwidth((actuals.Width - actuals.RatingLeftGap) / offlineTextSize - textzoomFudge) + self:diffuse(primaryTextColor) + self:diffusealpha(1) + end, + SetCommand = function(self) + if profile then + local rating = profile:GetPlayerRating() + self:settextf("Offline - %5.2f", rating) + end + end + } + } + } + end + + local t = Def.ActorFrame { + Name = "ItemList", + InitCommand = function(self) + self:xy(actuals.FrameLeftGap, actuals.FrameUpperGap) + end, + BeginCommand = function(self) + -- make sure the focus is set on the scroller options + -- false means that we are focused on the profile choices + TITLE:SetFocus(true) + SCREENMAN:set_input_redirected(PLAYER_1, false) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if event.type == "InputEventType_FirstPress" then + if INPUTFILTER:IsControlPressed() and event.DeviceInput.button == "DeviceButton_n" then + createProfileDialogue(self) + return + end + if focused then + if event.button == "MenuUp" or event.button == "Up" + or event.button == "MenuLeft" or event.button == "Left" then + move(-1) + elseif event.button == "MenuDown" or event.button == "Down" + or event.button == "MenuRight" or event.button == "Right" then + move(1) + elseif event.button == "Start" then + selectCurrent() + elseif event.button == "Back" then + backOut() + end + end + end + end) + end, + FirstUpdateCommand = function(self) + if renameNewProfile then + local profile = PROFILEMAN:GetLocalProfile(profileIDs[1]) + local function f(answer) + profile:RenameProfile(answer) + self:playcommand("ProfileRenamed") + end + local question = "No Profiles detected! A new one was made for you.\nPlease enter a new profile name." + askForInputStringWithFunction( + question, + 64, + false, + f, + function(answer) + local result = answer ~= nil and answer:gsub("^%s*(.-)%s*$", "%1") ~= "" and not answer:match("::") and answer:gsub("^%s*(.-)%s*$", "%1"):sub(-1) ~= ":" + if not result then + SCREENMAN:GetTopScreen():GetChild("Question"):settext(question .. "\nDo not leave this space blank. Do not use ':'") + end + return result, "Response invalid." + end, + function() + -- do nothing + -- the profile name is Default Profile + -- cringe name tbh + end + ) + end + end, + ToggledTitleFocusMessageCommand = function(self, params) + focused = not params.scrollerFocused + -- focused means we must pay attention to the profiles instead of the left scroller + if focused then + if #profileIDs == 1 then + -- there is only 1 choice, no need to care about picking a profile + -- skip forward + TITLE:HandleFinalGameStart() + else + -- consider our options... + -- (locking input here because of a race condition that counts our enter button press twice) + SCREENMAN:GetTopScreen():lockinput(0.05) + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + else + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + self:GetChild("FocusBG"):playcommand("FocusChange") + self:GetChild("InfoText"):playcommand("FocusChange") + end, + + UIElements.QuadButton(1, 1) .. { + Name = "FocusBG", + InitCommand = function(self) + self:diffuse(color("0,0,0")) + self:diffusealpha(0) + end, + BeginCommand = function(self) + -- offset position to fill whole screen + self:xy(-self:GetParent():GetX(), -self:GetParent():GetY()) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + self:halign(0):valign(0) + end, + FocusChangeCommand = function(self) + if focused then + self:hurrytweening(0.5) + self:smooth(0.4) + self:diffusealpha(0.75) + self:z(2) + else + self:hurrytweening(0.5) + self:smooth(0.4) + self:diffusealpha(0) + self:z(1) + end + end, + MouseDownCommand = function(self, params) + -- only allow clicking when focused on profile select + -- though this button covers the whole screen, the button system should + -- properly handle the behavior of overlapping the profile buttons and this + -- Button Layers -- + -- -- (Focus On) + -- [Scroller Buttons] [THIS] [Profile Buttons] + -- -- (Focus Off) + -- [THIS] [Scroller Buttons] [Profile Buttons] + -- Because depth alone wont help, we have to employ Z coordinates in focus changes to assist. + -- Also, the invisible buttons still take priority because we don't account for it in the implementation + -- So invisible buttons do exactly the same as this: + -- (why is this comment so long) + if focused then + TITLE:ChangeFocus() + end + end + }, + LoadFont("Common Large") .. { + Name = "InfoText", + InitCommand = function(self) + self:halign(1) + self:xy(actuals.Width, -actuals.FrameUpperGap/2) + self:zoom(0.5) + self:settext("Select a Profile") + self:diffusealpha(0) + end, + FocusChangeCommand = function(self) + if focused then + self:hurrytweening(0.5) + self:smooth(0.4) + self:diffusealpha(1) + else + self:hurrytweening(0.5) + self:smooth(0.4) + self:diffusealpha(0) + end + end, + }, + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:diffusealpha(0) + end, + BeginCommand = function(self) + -- offset position to fill whole screen + self:xy(-self:GetParent():GetX(), -self:GetParent():GetY()) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + self:halign(0):valign(0) + end, + MouseScrollMessageCommand = function(self, params) + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + }, + + + Def.Sprite { + Name = "Cursor", + Texture = THEME:GetPathG("", "profileselectorGlow"), + InitCommand = function(self) + self:xy(-actuals.ItemGlowHorizontalSpan / 2, -actuals.ItemGlowVerticalSpan / 2) + self:halign(0):valign(0) + self:zoomto(actuals.Width + actuals.ItemGlowHorizontalSpan, actuals.ItemHeight + actuals.ItemGlowVerticalSpan) + end, + MovedPageMessageCommand = function(self) + local lowerbound = numItems * (page-1) + 1 + local upperbound = math.min(numItems * page, #profileIDs) + if lowerbound > selectionIndex or upperbound < selectionIndex then + local cursorpos = (selectionIndex-1) % numItems + local newpos = cursorpos + (page-1) * numItems + 1 + if profileIDs[newpos] == nil then + -- dont let the cursor get into an impossible position + selectionIndex = clamp(newpos, lowerbound, upperbound) + else + selectionIndex = newpos + end + end + self:playcommand("MovedIndex") + end, + MovedIndexMessageCommand = function(self) + local cursorindex = (selectionIndex-1) % numItems + self:finishtweening() + self:linear(0.05) + self:y(cursorindex * (actuals.ItemHeight + actuals.ItemGap) - actuals.ItemGlowVerticalSpan / 2) + end + }, + + Def.Sprite { + Name = "NewProfileButton", + Texture = THEME:GetPathG("", "newProfile"), + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.ItemHeight) + end, + NewProfileToggleMessageCommand = function(self, params) + -- provide an index to place the button in a spot + -- otherwise, dont and it will disappear + if params.index then + self:y((params.index-1) * (actuals.ItemHeight + actuals.ItemGap)) + self:finishtweening() + self:x(0) + self:smooth(0.075) + self:diffusealpha(1) + else + self:x(1000) + self:diffusealpha(0) + end + end + } + } + + for i = 1, numItems do + t[#t+1] = generateItem(i) + end + return t +end + +t[#t+1] = generateItems() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/ScreenTitleMenu underlay.lua b/Themes/Rebirth/BGAnimations/ScreenTitleMenu underlay.lua new file mode 100644 index 0000000000..0c594817e1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenTitleMenu underlay.lua @@ -0,0 +1,340 @@ +local t = Def.ActorFrame { + Name = "UnderlayFile", + BeginCommand = function(self) + -- if theres no songs loaded send them to the bundle screen once + if SONGMAN:GetNumSongGroups() == 0 and not SCUFF.visitedCoreBundleSelect then + SCUFF.visitedCoreBundleSelect = true + SCREENMAN:SetNewScreen("ScreenCoreBundleSelect") + end + end, +} + +t[#t+1] = LoadActor(THEME:GetPathG("Title", "BG")) + +local gradientwidth = 1104 / 1920 * SCREEN_WIDTH +local gradientheight = SCREEN_HEIGHT +local separatorxpos = 814 / 1920 * SCREEN_WIDTH -- basically the top right edge of the gradient +local separatorthickness = 22 / 1920 * SCREEN_WIDTH -- very slightly fudged due to measuring a diagonal line +local separatorlength = math.sqrt(SCREEN_HEIGHT * SCREEN_HEIGHT + (gradientwidth - separatorxpos) * (gradientwidth - separatorxpos)) + 10 -- hypotenuse + +local logoFrameUpperGap = 39 / 1080 * SCREEN_HEIGHT -- from top edge to logo +local logoFrameLeftGap = 61 / 1920 * SCREEN_WIDTH -- from left edge to logo +local logoNameLeftGap = 33 / 1920 * SCREEN_WIDTH -- from end of logo to left of text +local logoThemeNameLeftGap = 33 / 1920 * SCREEN_WIDTH -- from end of logo to left of text +local logoThemeNameUpperGap = 67 / 1080 * SCREEN_HEIGHT -- from top of name text to top of theme text +local logosourceHeight = 133 +local logosourceWidth = 102 +local logoratio = math.min(1920 / SCREEN_WIDTH, 1080 / SCREEN_HEIGHT) +local logoH, logoW = getHWKeepAspectRatio(logosourceHeight, logosourceWidth, logosourceWidth / logosourceWidth) + +local versionNumberLeftGap = 5 / 1920 * SCREEN_WIDTH +local versionNumberUpperGap = 980 / 1080 * SCREEN_HEIGHT +local themeVersionUpperGap = 1015 / 1080 * SCREEN_HEIGHT + +local nameTextSize = 0.9 +local themenameTextSize = 0.8 +local versionTextSize = 0.5 +local versionTextSizeSmall = 0.25 +local animationSeconds = 0.5 -- the intro animation +local updateDownloadIconSize = 30 / 1080 * SCREEN_HEIGHT + +-- information for the update button +local latest = tonumber((DLMAN:GetLastVersion():gsub("[.]", "", 1))) +local current = tonumber((GAMESTATE:GetEtternaVersion():gsub("[.]", "", 1))) +if latest ~= nil and current ~= nil and latest > current then + updateRequired = true +end + +-- if you go to the help screen this puts you back on the main menu +SCUFF.helpmenuBackout = "ScreenTitleMenu" + +-- for the secret jukebox button +local playingMusic = {} +local playingMusicCounter = 1 + +local buttonHoverAlpha = 0.6 +local function hoverfunc(self) + if self:IsInvisible() then return end + if isOver(self) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end +end + +local function clickDownload(self, params) + if self:IsInvisible() then return end + if not params or params.event ~= "DeviceButton_left mouse button" then return end + GAMESTATE:ApplyGameCommand("urlnoexit,https://github.com/etternagame/etterna/releases;text,GitHub") +end + +t[#t+1] = Def.ActorFrame { + Name = "LeftSide", + InitCommand = function(self) + self:x(-SCREEN_WIDTH) + end, + BeginCommand = function(self) + self:smooth(animationSeconds) + self:x(0) + end, + + Def.Sprite { + Name = "LeftBG", + Texture = THEME:GetPathG("", "title-gradient"), + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(gradientwidth, gradientheight) + end + }, + Def.Quad { + Name = "SeparatorShadow", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(separatorthickness * 3, separatorlength) + self:x(separatorxpos - separatorthickness) + self:diffuse(color("0,0,0,1")) + self:fadeleft(1) + self:faderight(1) + local ang = math.atan((gradientwidth - separatorxpos) / separatorlength) + self:rotationz(-math.deg(ang)) + end + }, + Def.Quad { + Name = "Separator", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(separatorthickness, separatorlength) + self:x(separatorxpos) + local ang = math.atan((gradientwidth - separatorxpos) / separatorlength) + self:rotationz(-math.deg(ang)) + self:diffuse(COLORS:getTitleColor("Separator")) + self:diffusealpha(1) + end + }, + + Def.ActorFrame { + Name = "LogoFrame", + InitCommand = function(self) + self:xy(logoFrameLeftGap, logoFrameUpperGap) + end, + + UIElements.SpriteButton(100, 1, THEME:GetPathG("", "Logo")) .. { + Name = "Logo", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(logoW, logoH) + end, + MouseOverCommand = function(self) + self:diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + local function startSong() + local sngs = SONGMAN:GetAllSongs() + if #sngs == 0 then ms.ok("No songs to play") return end + + local s = sngs[math.random(#sngs)] + local p = s:GetMusicPath() + local l = s:MusicLengthSeconds() + local top = SCREENMAN:GetTopScreen() + + local thisSong = playingMusicCounter + playingMusic[thisSong] = true + + SOUND:StopMusic() + SOUND:PlayMusicPart(p, 0, l) + + ms.ok("NOW PLAYING: "..s:GetMainTitle() .. " | LENGTH: "..SecondsToMMSS(l)) + + top:setTimeout( + function() + if not playingMusic[thisSong] then return end + playingMusicCounter = playingMusicCounter + 1 + startSong() + end, + l + ) + + end + + SCREENMAN:GetTopScreen():setTimeout(function() + playingMusic[playingMusicCounter] = false + playingMusicCounter = playingMusicCounter + 1 + startSong() + end, + 0.1) + else + SOUND:StopMusic() + playingMusic = {} + playingMusicCounter = playingMusicCounter + 1 + ms.ok("Stopped music") + end + end, + }, + LoadFont("Menu Bold") .. { + Name = "GameName", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(logoNameLeftGap + logoW) + self:zoom(nameTextSize) + self:maxwidth((separatorxpos - (logoNameLeftGap + logoW) - logoNameLeftGap) / nameTextSize) + self:settext("ETTERNA") + self:diffuse(COLORS:getTitleColor("PrimaryText")) + self:diffusealpha(1) + end + }, + LoadFont("Menu Normal") .. { + Name = "ThemeName", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(logoThemeNameLeftGap + logoW, logoThemeNameUpperGap) + self:zoom(themenameTextSize) + self:maxwidth((separatorxpos - (logoNameLeftGap + logoW) - logoThemeNameLeftGap) / themenameTextSize) + self:settext(getThemeName()) + self:diffuse(COLORS:getTitleColor("PrimaryText")) + self:diffusealpha(1) + end + }, + LoadFont("Menu Normal") .. { + Name = "VersionNumber", + InitCommand = function(self) -- happens first + self:halign(0):valign(0) + self:xy(versionNumberLeftGap, versionNumberUpperGap) + self:zoom(versionTextSize) + self:settext("V "..GAMESTATE:GetEtternaVersion()) + self:diffuse(COLORS:getTitleColor("PrimaryText")) + self:diffusealpha(1) + end + }, + UIElements.TextToolTip(100, 1, "Menu Normal") .. { + Name = "VersionUpdate", + BeginCommand = function(self) -- happens second + self:halign(0):valign(0) + local vnc = self:GetParent():GetChild("VersionNumber") + local bufferspace = 5 / 1920 * SCREEN_WIDTH + self:xy(vnc:GetX() + vnc:GetZoomedWidth() + bufferspace, versionNumberUpperGap) + self:zoom(versionTextSize) + self:maxwidth(((gradientwidth - vnc:GetX() - vnc:GetZoomedWidth() - logoFrameLeftGap - separatorthickness) / versionTextSize)) + self:settextf("- Update Available (%s)", DLMAN:GetLastVersion()) + self:diffuse(COLORS:getTitleColor("UpdateText")) + self:diffusealpha(1) + self:visible(false) + + if updateRequired then + self:visible(true) + end + end, + MouseOutCommand = hoverfunc, + MouseOverCommand = hoverfunc, + MouseDownCommand = clickDownload, + }, + UIElements.SpriteButton(100, 1, THEME:GetPathG("", "updatedownload")) .. { + Name = "VersionUpdateDownload", + OnCommand = function(self) -- happens third + self:halign(0):valign(0) + local vuc = self:GetParent():GetChild("VersionUpdate") + local bufferspace = 5 / 1920 * SCREEN_WIDTH + self:xy(vuc:GetX() + vuc:GetZoomedWidth() + bufferspace, versionNumberUpperGap) + self:zoomto(updateDownloadIconSize, updateDownloadIconSize) + self:diffuse(COLORS:getTitleColor("UpdateText")) + self:diffusealpha(1) + self:visible(false) + + if updateRequired then + self:visible(true) + end + end, + MouseOutCommand = hoverfunc, + MouseOverCommand = hoverfunc, + MouseDownCommand = clickDownload, + }, + + LoadFont("Menu Normal") .. { + Name = "ThemeVersionAndCredits", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(versionNumberLeftGap, themeVersionUpperGap) + self:maxwidth((gradientwidth - versionNumberLeftGap - logoFrameLeftGap - separatorthickness) / versionTextSizeSmall) + self:zoom(versionTextSizeSmall) + self:settext("("..getThemeName().." v"..getThemeVersion().."@"..getThemeDate().." by "..getThemeAuthor()..")") + self:diffuse(COLORS:getTitleColor("SecondaryText")) + self:diffusealpha(1) + end + } + } +} + + + + +local scrollerX = 99 / 1920 * SCREEN_WIDTH +local scrollerY = 440 / 1920 * SCREEN_HEIGHT +local selectorHeight = 37 / 1080 * SCREEN_HEIGHT +local selectorWidth = 574 / 1920 * SCREEN_WIDTH +local choiceTable = strsplit(THEME:GetMetric("ScreenTitleMenu", "ChoiceNames"), ",") + +t[#t+1] = Def.ActorFrame { + Name = "SelectionFrame", + BeginCommand = function(self) + -- i love hacks. + -- this makes all positioning and everything relative to the scroller actorframe + -- because we cant actually make the scroller we want + -- so we do this way + -- and this is the actorframe responsible for the highlight on the title screen choices. + local scr = SCREENMAN:GetTopScreen():GetChild("Scroller") + self:SetFakeParent(scr) + + -- move choices on screen + -- we set it to start off screen using the metrics + scr:smooth(animationSeconds) + scr:x(scrollerX) + scr:y(scrollerY) + end, + MenuSelectionChangedMessageCommand = function(self) + local i = self:GetFakeParent():GetDestinationItem() + 1 + local actorScroller = self:GetFakeParent():GetChild("ScrollChoice"..choiceTable[i]) + + self:finishtweening() + self:smooth(0.05) + self:y(actorScroller:GetY()) + end, + + Def.Sprite { + Name = "Fade", + Texture = THEME:GetPathG("", "selectorFade"), + BeginCommand = function(self) + self:x(-selectorHeight) + self:halign(0) + self:zoomto(selectorWidth, selectorHeight) + self:queuecommand("UpdateWidth") + end, + UpdateWidthCommand = function(self) + -- the minimum width is going to be about 10% longer than the longest choice text + local widest = selectorWidth + for name, child in pairs(self:GetParent():GetFakeParent():GetChildren()) do + local w = child:GetChild("ScrollerText"):GetZoomedWidth() + if w > widest - (selectorHeight * 2) then + widest = w + selectorHeight * 2 + end + end + if widest ~= selectorWidth then + widest = widest * 1.1 + end + self:zoomto(widest, selectorHeight) + end + }, + Def.Sprite { + Name = "Triangle", + Texture = THEME:GetPathG("", "Triangle"), + BeginCommand = function(self) + self:x(-selectorHeight) + self:halign(0) + self:zoomto(selectorHeight, selectorHeight) + end + } +} + +return t diff --git a/Themes/Rebirth/BGAnimations/ScreenToolTipOverlay overlay.lua b/Themes/Rebirth/BGAnimations/ScreenToolTipOverlay overlay.lua new file mode 100644 index 0000000000..9586ae4b22 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/ScreenToolTipOverlay overlay.lua @@ -0,0 +1,21 @@ +local function UpdateLoop() + local mouseX = INPUTFILTER:GetMouseX() + local mouseY = INPUTFILTER:GetMouseY() + TOOLTIP:SetPosition(mouseX, mouseY) + BUTTON:UpdateMouseState() +end + +local t = Def.ActorFrame { + InitCommand = function(self) + self:SetUpdateFunction(UpdateLoop) + self:SetUpdateFunctionInterval(0.01) + end +} + +local tooltip, pointer, clickwave = TOOLTIP:New() +t[#t+1] = tooltip +t[#t+1] = pointer +t[#t+1] = clickwave + + +return t; \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/_fadefast.lua b/Themes/Rebirth/BGAnimations/_fadefast.lua new file mode 100644 index 0000000000..52d122efb6 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/_fadefast.lua @@ -0,0 +1,10 @@ +-- meant to define the outward screen transition for a screen +-- this is a full screen fade into black (0.1 seconds long) after waiting 0.1 seconds +return Def.Quad { + InitCommand = function(self) + self:FullScreen():diffuse(color("#00000000")) + end, + OnCommand = function(self) + self:sleep(0.1):linear(0.1):diffusealpha(1) + end +} diff --git a/Themes/Rebirth/BGAnimations/_fadein.lua b/Themes/Rebirth/BGAnimations/_fadein.lua new file mode 100644 index 0000000000..0772fa87b0 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/_fadein.lua @@ -0,0 +1,10 @@ +-- meant to define the inward screen transition for a screen +-- this is a full screen fade out of black (0.1 seconds long) after waiting 0.1 seconds +return Def.Quad { + InitCommand = function(self) + self:FullScreen():diffuse(color("#000000FF")) + end, + OnCommand = function(self) + self:sleep(0.1):linear(0.1):diffusealpha(0) + end +} diff --git a/Themes/Rebirth/BGAnimations/_fadelong.lua b/Themes/Rebirth/BGAnimations/_fadelong.lua new file mode 100644 index 0000000000..8048447db6 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/_fadelong.lua @@ -0,0 +1,10 @@ +-- meant to define the outward screen transition for a screen +-- this is a full screen fade for 2 seconds into black after waiting 3 seconds +return Def.Quad { + InitCommand = function(self) + self:FullScreen():diffuse(color("#00000000")) + end, + OnCommand = function(self) + self:sleep(3):linear(2):diffusealpha(1) + end +} diff --git a/Themes/Rebirth/BGAnimations/_fadeout.lua b/Themes/Rebirth/BGAnimations/_fadeout.lua new file mode 100644 index 0000000000..1461754eea --- /dev/null +++ b/Themes/Rebirth/BGAnimations/_fadeout.lua @@ -0,0 +1,10 @@ +-- meant to define the outward screen transition for a screen +-- this is a full screen fade into black (2 seconds long) +return Def.Quad { + InitCommand = function(self) + self:FullScreen():diffuse(color("#00000000")) + end, + OnCommand = function(self) + self:linear(2):diffusealpha(1) + end +} diff --git a/Themes/Rebirth/BGAnimations/_mouse.lua b/Themes/Rebirth/BGAnimations/_mouse.lua new file mode 100644 index 0000000000..b0b81f2172 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/_mouse.lua @@ -0,0 +1,41 @@ +local screenName = Var("LoadingScreen") or ... +local topScreen + +assert(type(screenName) == "string", "Screen Name was missing when loading _mouse.lua") +BUTTON:ResetButtonTable(screenName) + + +-- this file actually only just resets the mouse button functionality for the current screen +-- it also controls the visibility of the mouse cursor +-- if this file is loaded at all, it immediately checks to see if making the cursor visible is needed +-- the visible mouse is in Tooltip.lua, part of the TOOLTIP table + + +local function cursorCheck() + -- show cursor if in fullscreen + if not PREFSMAN:GetPreference("Windowed") and not PREFSMAN:GetPreference("FullscreenIsBorderlessWindow") then + TOOLTIP:ShowPointer() + else + TOOLTIP:HidePointer() + end +end + +local t = Def.ActorFrame{ + OnCommand = function(self) + topScreen = SCREENMAN:GetTopScreen() + topScreen:AddInputCallback(BUTTON.InputCallback) + cursorCheck() + end, + OffCommand = function(self) + BUTTON:ResetButtonTable(screenName) + TOOLTIP:Hide() + end, + CancelCommand = function(self) + self:playcommand("Off") + end, + WindowedChangedMessageCommand = function(self) + cursorCheck() + end +} + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/chordDensityGraph.lua b/Themes/Rebirth/BGAnimations/chordDensityGraph.lua new file mode 100644 index 0000000000..223874a094 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/chordDensityGraph.lua @@ -0,0 +1,321 @@ +local sizing = Var("sizing") -- specify init sizing +if sizing == nil then sizing = {} end +--[[ + We are expecting the sizing table to be provided on file load. + It should contain these attributes: + Width + Height + NPSThickness + TextSize +]] +-- the bg is placed relative to top left: 0,0 alignment +-- the bars are placed relative to bottom left: 1 valign 0 halign +local stepsinuse = nil +local lowDensityColor = COLORS:getColor("chartPreview", "GraphLowestDensityBar") +local highDensityColor = COLORS:getColor("chartPreview", "GraphHighestDensityBar") +local t = Def.ActorFrame { + Name = "ChordDensityGraphFile", + InitCommand = function(self) + self:playcommand("UpdateSizing", {sizing = sizing}) + self:finishtweening() + end, + CurrentRateChangedMessageCommand = function(self) + self:playcommand("LoadDensityGraph", {steps = stepsinuse}) + end, + LoadDensityGraphCommand = function(self, params) + stepsinuse = params.steps or stepsinuse + end, + UpdateSizingCommand = function(self, params) + local sz = params.sizing + if sz ~= nil then + if sz.Height ~= nil then + sizing.Height = sz.Height + end + if sz.Width ~= nil then + sizing.Width = sz.Width + end + if sz.NPSThickness ~= nil then + sizing.NPSThickness = sz.NPSThickness + end + if sz.TextSize ~= nil then + sizing.TextSize = sz.TextSize + end + end + local bar = self:GetChild("Progress") + local bg = self:GetChild("BG") + local seek = self:GetChild("SeekBar") + local tipOn = false + self:SetUpdateFunction(function(self) + if self:IsInvisible() then return end + + local top = SCREENMAN:GetTopScreen() + local song = GAMESTATE:GetCurrentSong() + local musicpositionratio = 1 + local r = getCurRateValue() + if stepsinuse ~= nil and top ~= nil and song then + local length = stepsinuse:GetLengthSeconds() + musicpositionratio = (stepsinuse:GetFirstSecond() / r + length) / sizing.Width * r + if top.GetSampleMusicPosition then + local pos = top:GetSampleMusicPosition() / musicpositionratio + bar:zoomx(clamp(pos, 0, sizing.Width)) + elseif top.GetSongPosition then + local pos = top:GetSongPosition() / musicpositionratio + bar:zoomx(clamp(pos, 0, sizing.Width)) + else + bar:zoomx(0) + end + else + bar:zoomx(0) + end + if song and isOver(bg) then + local mx = INPUTFILTER:GetMouseX() + local my = INPUTFILTER:GetMouseY() + local lx, ly = bg:GetLocalMousePos(mx, my, 0) + seek:diffusealpha(1) + seek:x(lx) + + if stepsinuse ~= nil then + local perc = lx / bg:GetZoomedWidth() + local dist = perc * (stepsinuse:GetLengthSeconds()) + local postext = SecondsToHHMMSS(dist) + local stro = postext + if self.npsVector ~= nil and #self.npsVector > 0 then + local percent = clamp(lx / bg:GetZoomedWidth(), 0, 1) + local hoveredIndex = clamp(math.ceil(self.finalNPSVectorIndex * percent), math.min(1, self.finalNPSVectorIndex), self.finalNPSVectorIndex) + local hoveredNPS = self.npsVector[hoveredIndex] + local td = stepsinuse:GetTimingData() + local bpm = td:GetBPMAtBeat(td:GetBeatFromElapsedTime(dist * r)) * r + stro = string.format("%s\n%d NPS\n%d BPM", postext, hoveredNPS, bpm) + end + TOOLTIP:SetText(stro) + TOOLTIP:Show() + tipOn = true + end + + else + seek:diffusealpha(0) + + -- have to micromanage the state of the tooltip here + -- turning it off over and over will override any other use of the tooltip + if tipOn then + tipOn = false + TOOLTIP:Hide() + end + end + end) + end +} + +local function getColorForDensity(density, nColumns) + -- Generically (generally? intelligently? i dont know) set a range + -- The value var describes the level of density. + -- Beginning at lowVal for 0, to highVal for nColumns. + local interval = 1 / nColumns + local value = density * interval + return lerp_color(value, lowDensityColor, highDensityColor) +end + +local function makeABar(vertices, x, y, barWidth, barHeight, thecolor) + -- These bars are vertical, progressively going across the screen + -- Their corners are: (x,y), (x, y-barHeight), (x-barWidth, y-barHeight), (x-barWidth, y) + vertices[#vertices + 1] = {{x,y-barHeight,0},thecolor} + vertices[#vertices + 1] = {{x-barWidth,y-barHeight,0},thecolor} + vertices[#vertices + 1] = {{x-barWidth,y,0},thecolor} + vertices[#vertices + 1] = {{x,y,0},thecolor} +end + +local function updateGraphMultiVertex(parent, self, steps) + if steps then + local ncol = steps:GetNumColumns() + local rate = math.max(1, getCurRateValue()) + local graphVectors = steps:GetCDGraphVectors(rate) + local txt = parent:GetChild("NPSText") + if graphVectors == nil then + -- reset everything if theres nothing to show + self:SetVertices({}) + self:SetDrawState( {Mode = "DrawMode_Quads", First = 0, Num = 0} ) + txt:settext("") + return + end + + local npsVector = graphVectors[1] -- refers to the cps vector for 1 (tap notes) + local numberOfColumns = #npsVector + local columnWidth = sizing.Width / numberOfColumns * rate + + -- set height scale of graph relative to the max nps + local heightScale = 0 + for i=1,#npsVector do + if npsVector[i] * 2 > heightScale then + heightScale = npsVector[i] * 2 + end + end + + txt:settext(heightScale / 2 * 0.7 .. "NPS") + heightScale = sizing.Height / heightScale + + local verts = {} -- reset the vertices for the graph + local yOffset = 0 -- completely unnecessary, just a Y offset from the graph + local lastIndex = 1 + for density = 1,ncol do + for column = 1,numberOfColumns do + if graphVectors[density][column] > 0 then + local barColor = getColorForDensity(density, ncol) + makeABar(verts, math.min(column * columnWidth, sizing.Width), yOffset, columnWidth, graphVectors[density][column] * 2 * heightScale, barColor) + if column > lastIndex then + lastIndex = column + end + end + end + end + + parent.npsVector = npsVector + parent.finalNPSVectorIndex = lastIndex -- massive hack because npsVector is padded with 0s on uprates + + self:SetVertices(verts) + self:SetDrawState( {Mode = "DrawMode_Quads", First = 1, Num = #verts} ) + else + -- reset everything if theres nothing to show + self:SetVertices({}) + self:SetDrawState( {Mode = "DrawMode_Quads", First = 0, Num = 0} ) + parent:GetChild("NPSText"):settext("") + end +end + +local textzoomFudge = 5 +local resizeAnimationSeconds = 0.1 + +t[#t+1] = UIElements.QuadButton(1, 1) .. { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + registerActorToColorConfigElement(self, "chartPreview", "GraphBackground") + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:zoomto(sizing.Width, sizing.Height) + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + local lx = params.MouseX - self:GetParent():GetX() + local top = SCREENMAN:GetTopScreen() + if top and top.SetSongPosition then + local song = GAMESTATE:GetCurrentSong() + -- logic for gameplay chord density graph + if stepsinuse and song then + local r = getCurRateValue() + local length = stepsinuse:GetLengthSeconds() + local musicpositionratio = (stepsinuse:GetFirstSecond() / r + length) / sizing.Width * r + if params.event == "DeviceButton_left mouse button" then + local withCtrl = INPUTFILTER:IsControlPressed() + if withCtrl then + -- this command is defined remotely by _gameplaypractice.lua + self:playcommand("HandleRegionSetting", {positionGiven = lx * musicpositionratio}) + else + top:SetSongPosition(lx * musicpositionratio, 0, false) + end + elseif params.event == "DeviceButton_right mouse button" then + -- this command is defined remotely by _gameplaypractice.lua + self:playcommand("HandleRegionSetting", {positionGiven = lx * musicpositionratio}) + end + end + elseif top and top.SetSampleMusicPosition then + -- logic for selectmusic chord density graph + if params.event == "DeviceButton_left mouse button" then + local song = GAMESTATE:GetCurrentSong() + if stepsinuse and song then + local r = getCurRateValue() + local length = stepsinuse:GetLengthSeconds() + local musicpositionratio = (stepsinuse:GetFirstSecond() / r + length) / sizing.Width * r + top:SetSampleMusicPosition(lx * musicpositionratio) + end + else + top:PauseSampleMusic() + end + end + end +} + +t[#t+1] = Def.Quad { + Name = "Progress", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0.5) + registerActorToColorConfigElement(self, "chartPreview", "GraphProgress") + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:zoomto(0, sizing.Height) + end +} + +t[#t+1] = Def.ActorMultiVertex { + Name = "ChordDensityGraphAMV", + UpdateSizingCommand = function(self) + -- this will position the plot relative to the bottom left of the area + -- less math, more easy, progarming fun + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:y(sizing.Height) + end, + ColorConfigUpdatedMessageCommand = function(self) + lowDensityColor = COLORS:getColor("chartPreview", "GraphLowestDensityBar") + highDensityColor = COLORS:getColor("chartPreview", "GraphHighestDensityBar") + updateGraphMultiVertex(self:GetParent(), self, self.s) + end, + LoadDensityGraphCommand = function(self, params) + self.s = params.steps + updateGraphMultiVertex(self:GetParent(), self, params.steps) + end, +} + +t[#t+1] = Def.Quad { + Name = "NPSLine", + InitCommand = function(self) + self:halign(0) + registerActorToColorConfigElement(self, "chartPreview", "GraphNPSLine") + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + -- the NPS Line represents 75% of the actual NPS + -- the position is relative to top left, so move it 25% of the way down + -- do not valign this (this means the center of this line is 75%) + self:y(sizing.Height * 0.25) + self:zoomto(sizing.Width, sizing.NPSThickness) + end, +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "NPSText", + InitCommand = function(self) + self:halign(0):valign(1) + registerActorToColorConfigElement(self, "chartPreview", "GraphNPSText") + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + local zs = sizing.TextSize or 1 + self:zoom(zs) + -- this text is positioned above the NPS line, basically half the thickness above it + self:y(sizing.Height * 0.25 - sizing.NPSThickness * 0.75) + self:maxwidth(sizing.Width / zs - textzoomFudge) + end, +} + +t[#t+1] = Def.Quad { + Name = "SeekBar", + InitCommand = function(self) + self:valign(0) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "chartPreview", "GraphSeekBar") + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:zoomto(sizing.NPSThickness, sizing.Height) + end +} + +return t diff --git a/Themes/Rebirth/BGAnimations/footer.lua b/Themes/Rebirth/BGAnimations/footer.lua new file mode 100644 index 0000000000..4d5e2d6fd5 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/footer.lua @@ -0,0 +1,78 @@ +local t = Def.ActorFrame { + Name = "FooterFile", + InitCommand = function(self) + -- all positions should be relative to the bottom left corner of the screen + self:y(SCREEN_HEIGHT) + + -- update current time + self:SetUpdateFunction(function(self) + self:GetChild("CurrentTime"):playcommand("UpdateTime") + end) + -- update once per second + self:SetUpdateFunctionInterval(1) + end +} + +local ratios = { + TextHorizontalPadding = 15 / 1920, -- distance to move text to align left or right from edge +} + +local actuals = { + TextHorizontalPadding = ratios.TextHorizontalPadding * SCREEN_WIDTH, +} + +actuals.Width = Var("Width") +actuals.Height = Var("Height") +if actuals.Width == nil then actuals.Width = SCREEN_WIDTH end +if actuals.Height == nil then actuals.Height = 50 end + +-- how much of the visible area can the quote take up before being restricted in width? +local allowedPercentageForQuote = 0.80 +local textSize = 0.95 +local textZoomFudge = 5 + + +t[#t+1] = Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(1) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.75) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "CurrentTime", + InitCommand = function(self) + self:halign(1) + self:xy(actuals.Width - actuals.TextHorizontalPadding, -actuals.Height / 2) + self:zoom(textSize) + self:maxwidth(actuals.Width * (1 - allowedPercentageForQuote) / textSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + self:playcommand("UpdateTime") + end, + UpdateTimeCommand = function(self) + local year = Year() + local month = MonthOfYear() + 1 + local day = DayOfMonth() + local hour = Hour() + local minute = Minute() + local second = Second() + self:settextf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "QuoteOrTip", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.TextHorizontalPadding, -actuals.Height / 2) + self:zoom(textSize) + self:maxwidth(actuals.Width * allowedPercentageForQuote / textSize - textZoomFudge) + self:settext(getRandomQuote(themeConfig:get_data().global.TipType)) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end +} + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/judgmentBars.lua b/Themes/Rebirth/BGAnimations/judgmentBars.lua new file mode 100644 index 0000000000..8985152a13 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/judgmentBars.lua @@ -0,0 +1,158 @@ +-- for portability +-- we use this file in evaluation and in the local section of the scores tab in selectmusic +-- requires sizing to be provided on init as tables +local sizing = Var("sizing") +if sizing == nil then sizing = {} end +local textSizeMultiplier = Var("textSizeMultiplier") or 1 + +-- list of judgments to display the bar/counts for +local judgmentsChosen = { + "TapNoteScore_W1", -- marvelous + "TapNoteScore_W2", -- perfect + "TapNoteScore_W3", -- great + "TapNoteScore_W4", -- good + "TapNoteScore_W5", -- bad + "TapNoteScore_Miss", -- miss +} + +local judgmentTextZoom = 0.6 * textSizeMultiplier +local judgmentCountZoom = 0.6 * textSizeMultiplier +local judgmentPercentZoom = 0.3 * textSizeMultiplier +local judgmentCountPercentBump = 1 -- a bump in position added to the Count and Percent for spacing + +local textzoomFudge = 5 +-- this number should stay the same as ApproachSeconds under metrics.ini [RollingNumbersJudgmentPercentage] +-- (or the associated RollingNumbers classes used in this file) +local animationSeconds = 0.5 + +-- make this not invisible to ... you know +local textEmbossColor = color("0,0,0,0") + +local totalTaps = 0 +local t = Def.ActorFrame { + Name = "JudgmentBarParentFrame", + SetCommand = function(self, params) + totalTaps = 0 + for i, j in ipairs(judgmentsChosen) do + totalTaps = totalTaps + params.score:GetTapNoteScore(j) + end + end +} +local function makeJudgment(i) + local jdg = judgmentsChosen[i] + local count = 0 + + return Def.ActorFrame { + Name = "Judgment_"..i, + InitCommand = function(self) + -- finds the top of every bar given the requested spacing and the height of each bar within the allotted space + self:y((((i-1) * sizing.JudgmentBarHeight + (i-1) * sizing.JudgmentBarSpacing) / sizing.JudgmentBarAllottedSpace) * sizing.JudgmentBarAllottedSpace) + end, + SetCommand = function(self, params) + if params.score ~= nil then + if params.judgeSetting ~= nil and params.score:HasReplayData() then + count = getRescoredJudge(params.score:GetOffsetVector(), params.judgeSetting, i) + else + count = params.score:GetTapNoteScore(jdg) + end + else + count = 0 + end + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(sizing.JudgmentBarLength, sizing.JudgmentBarHeight) + self:diffusealpha(0.5) + registerActorToColorConfigElement(self, "judgment", jdg) + end + }, + Def.Quad { + Name = "Fill", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(0, sizing.JudgmentBarHeight) + self:diffusealpha(0.5) + registerActorToColorConfigElement(self, "judgment", jdg) + end, + SetCommand = function(self, params) + self:finishtweening() + self:smooth(animationSeconds) + if params.score == nil then + self:zoomx(0) + return + end + local percent = count / totalTaps + self:zoomx(sizing.JudgmentBarLength * percent) + end + }, + LoadFont("Common Large") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0) + self:xy(sizing.JudgmentNameLeftGap, sizing.JudgmentBarHeight / 2) + self:zoom(judgmentTextZoom) + self:strokecolor(textEmbossColor) + -- allow 3/4 of the judgment area between the number alignment and the name alignment + self:maxwidth((sizing.JudgmentBarLength - sizing.JudgmentNameLeftGap - sizing.JudgmentCountRightGap - judgmentCountPercentBump) / 4 * 3 / judgmentTextZoom) + self:settext(getJudgeStrings(ms.JudgeCount[i])) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.RollingNumbers { + Name = "Count", + Font = "Common Large", + InitCommand = function(self) + self:Load("RollingNumbersJudgmentNoLead") + self:halign(1) + self:xy(sizing.JudgmentBarLength - sizing.JudgmentCountRightGap - judgmentCountPercentBump, sizing.JudgmentBarHeight / 2) + self:zoom(judgmentCountZoom) + self:strokecolor(textEmbossColor) + -- allow 1/4 of the judgment area between the number alignment and the name alignment + self:maxwidth((sizing.JudgmentBarLength - sizing.JudgmentNameLeftGap - sizing.JudgmentCountRightGap - judgmentCountPercentBump) / 4 / judgmentTextZoom) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.score == nil then + self:targetnumber(0) + return + end + self:targetnumber(count) + end + }, + Def.RollingNumbers { + Name = "Percentage", + Font = "Common Large", + InitCommand = function(self) + self:Load("RollingNumbersJudgmentPercentage") + self:halign(0) + self:xy(sizing.JudgmentBarLength - sizing.JudgmentCountRightGap + judgmentCountPercentBump, sizing.JudgmentBarHeight / 2) + self:zoom(judgmentPercentZoom) + self:maxwidth((sizing.JudgmentCountRightGap - judgmentCountPercentBump) / judgmentPercentZoom - textzoomFudge) + self:strokecolor(textEmbossColor) + self:targetnumber(0) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self, params) + if params.score == nil then + self:targetnumber(0) + return + end + if totalTaps == 0 then + self:targetnumber(100) + else + local percent = count / totalTaps * 100 + self:targetnumber(percent) + end + end + } + } +end +for i = 1, #judgmentsChosen do + t[#t+1] = makeJudgment(i) +end + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/offsetplot.lua b/Themes/Rebirth/BGAnimations/offsetplot.lua new file mode 100644 index 0000000000..f85f219db9 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/offsetplot.lua @@ -0,0 +1,561 @@ +local sizing = Var("sizing") -- specify init sizing +if sizing == nil then sizing = {} end +local extraFeatures = Var("extraFeatures") -- toggle offset hovering and input events for highlighting +if extraFeatures == nil then extraFeatures = false end +--[[ + We are expecting the sizing table to be provided on file load. + It should contain these attributes: + Width + Height +]] +-- all elements are placed relative to default valign - 0 halign +-- this means relatively to center vertically and relative to the left end horizontally + +local judgeSetting = (PREFSMAN:GetPreference("SortBySSRNormPercent") and 4 or GetTimingDifficulty()) +local timingScale = ms.JudgeScalers[judgeSetting] + +-- cap the graph to this +local maxOffset = 180 +local lineThickness = 2 +local lineAlpha = 0.2 +local textPadding = 5 +local textSize = Var("textsize") or 0.65 +local instructionTextSize = 0.55 + +local dotAnimationSeconds = 1 +local resizeAnimationSeconds = 0.1 +local unHighlightedAlpha = 0.2 + +-- the dot sizes +-- the "classic" default is 1.0 +local dotLineLength = 0.75 +local dotLineUpperBound = 1.2 +local dotLineLowerBound = 0.7 +-- length of the dot lines for the mine X +local mineXSize = 3 +local mineXThickness = 1 + +-- judgment windows to display on the plot +local barJudgments = { + "TapNoteScore_W2", + "TapNoteScore_W3", + "TapNoteScore_W4", + "TapNoteScore_W5", +} + +-- tracking the index of dot highlighting for each column +local highlightIndex = 1 +-- each index corresponds to a type of column setup to highlight +local highlightTable = {} + +local function columnIsHighlighted(column) + return column == nil or #highlightTable == 0 or highlightTable[highlightIndex][column] == true +end + +-- allow moving the highlightIndex in either direction and loop around if under/overflow +local function moveHighlightIndex(direction) + local newg = (((highlightIndex) + direction) % (#highlightTable + 1)) + if newg == 0 then + newg = direction > 0 and 1 or #highlightTable + end + highlightIndex = newg +end + +local function getMineColor(column) + local mineColor = COLORS:getColor("offsetPlot", "MineHit") + -- cant highlight or currently set to highlight this column + if columnIsHighlighted(column) then + return mineColor + else + -- not highlighting this column + local c = {} + for i,v in ipairs(mineColor) do + c[i] = v + end + c[4] = unHighlightedAlpha + return c + end +end + +local function getHoldColor(column, type) + local color = color("#FFFFFF") + if type == "TapNoteSubType_Roll" then + color = COLORS:getColor("offsetPlot", "RollDrop") + elseif type == "TapNoteSubType_Hold" then + color = COLORS:getColor("offsetPlot", "HoldDrop") + end + + -- cant highlight or currently set to highlight this column + if columnIsHighlighted(column) then + return color + else + -- not highlighting this column + local c = {} + for i,v in ipairs(color) do + c[i] = v + end + c[4] = unHighlightedAlpha + return c + end +end + +-- produces the highlightTable in the format: +-- { {x,y,z...}, {x,y,z...} ... } where each subtable is a list of columns to highlight, the keys are the columns +local function calcDotHighlightTable(tracks, columns) + local out = {} + if tracks ~= nil and #tracks ~= 0 then + -- all columns + out = {{}} + for i = 1, columns do + out[1][i] = true + end + + if columns % 2 == 0 then + out[#out+1] = {} + out[#out+1] = {} + -- even columns, 1 per hand + for i = 1, columns / 2 do + out[2][i] = true + end + for i = columns / 2 + 1, columns do + out[3][i] = true + end + + else + out[#out+1] = {} + out[#out+1] = {} + out[#out+1] = {} + -- odd columns, 1 left - 1 middle - 1 right + for i = 1, math.floor(columns / 2) do + out[2][i] = true + end + out[3][math.ceil(columns / 2)] = true + for i = math.ceil(columns / 2) + 1, columns do + out[4][i] = true + end + end + -- add single highlights for each column + for i = 1, columns do + out[#out+1] = {[i] = true} + end + end + return out +end + +-- convert number to another number out of a given width +-- relative to left side of the graph +local function fitX(x, maxX) + -- dont let the x go way off the end of the graph + x = clamp(x, x, maxX) + return x / maxX * sizing.Width +end + +-- convert millisecond values to a y position in the graph +-- relative to vertical center +local function fitY(y, maxY) + return -1 * y / maxY * sizing.Height / 2 + sizing.Height / 2 +end + +-- 4 xyz coordinates are given to make up the 4 corners of a quad to draw +local function placeDotVertices(vertList, x, y, color) + vertList[#vertList + 1] = {{x - dotLineLength, y + dotLineLength, 0}, color} + vertList[#vertList + 1] = {{x + dotLineLength, y + dotLineLength, 0}, color} + vertList[#vertList + 1] = {{x + dotLineLength, y - dotLineLength, 0}, color} + vertList[#vertList + 1] = {{x - dotLineLength, y - dotLineLength, 0}, color} +end + +-- 2 pairs of 4 coordinates to draw a big X +local function placeMineVertices(vertList, x, y, color) + vertList[#vertList + 1] = {{x - mineXSize - mineXThickness / 2, y - mineXSize, 0}, color} + vertList[#vertList + 1] = {{x + mineXSize - mineXThickness / 2, y + mineXSize, 0}, color} + vertList[#vertList + 1] = {{x - mineXSize + mineXThickness / 2, y - mineXSize, 0}, color} + vertList[#vertList + 1] = {{x + mineXSize + mineXThickness / 2, y + mineXSize, 0}, color} + + vertList[#vertList + 1] = {{x + mineXSize + mineXThickness / 2, y - mineXSize, 0}, color} + vertList[#vertList + 1] = {{x - mineXSize + mineXThickness / 2, y + mineXSize, 0}, color} + vertList[#vertList + 1] = {{x + mineXSize - mineXThickness / 2, y - mineXSize, 0}, color} + vertList[#vertList + 1] = {{x - mineXSize - mineXThickness / 2, y + mineXSize, 0}, color} +end + +-- 2 pairs of 4 coordinates to draw a ^ +local function placeNoodleVertices(vertList, x, y, color) + vertList[#vertList + 1] = {{x - mineXThickness / 2, y + mineXSize, 0}, color} + vertList[#vertList + 1] = {{x + mineXThickness / 2, y, 0}, color} + vertList[#vertList + 1] = {{x + mineXThickness / 2, y + mineXSize, 0}, color} + vertList[#vertList + 1] = {{x - mineXThickness / 2, y, 0}, color} +end + +local t = Def.ActorFrame { + Name = "OffsetPlotFile", + InitCommand = function(self) + local hid = false + if not extraFeatures then return end -- no extra features: dont add the hover + self:SetUpdateFunction(function() + local bg = self:GetChild("BG") + if isOver(bg) then + local top = SCREENMAN:GetTopScreen() + -- dont break if it will break (we can only do this from the eval screen) + if not top.GetReplaySnapshotJudgmentsForNoterow or not top.GetReplaySnapshotWifePercentForNoterow then + return + end + + TOOLTIP:Show() + + local x, y = bg:GetLocalMousePos(INPUTFILTER:GetMouseX(), INPUTFILTER:GetMouseY(), 0) + local percent = clamp(x / bg:GetZoomedWidth(), 0, 1) + -- 48 rows per beat, multiply the current beat by 48 to get the current row + local td = GAMESTATE:GetCurrentSteps():GetTimingData() + local lastsec = GAMESTATE:GetCurrentSteps():GetLastSecond() + local row = td:GetBeatFromElapsedTime(percent * lastsec) * 48 + + local judgments = top:GetReplaySnapshotJudgmentsForNoterow(row) + local wifescore = top:GetReplaySnapshotWifePercentForNoterow(row) * 100 + local time = SecondsToHHMMSS(td:GetElapsedTimeFromNoteRow(row)) + local mean = top:GetReplaySnapshotMeanForNoterow(row) + local sd = top:GetReplaySnapshotSDForNoterow(row) + + local marvCount = judgments[10] + local perfCount = judgments[9] + local greatCount = judgments[8] + local goodCount = judgments[7] + local badCount = judgments[6] + local missCount = judgments[5] + + -- excessively long string format for translation support + local txt = string.format( + "%5.6f%%\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n%s: %d\n%s: %0.2fms\n%s: %0.2fms\n%s: %s", + wifescore, + "Marvelous", marvCount, + "Perfect", perfCount, + "Great", greatCount, + "Good", goodCount, + "Bad", badCount, + "Miss", missCount, + "Std. Dev", sd, + "Mean", mean, + "Time", time + ) + + local mp = self:GetChild("MousePosition") + mp:visible(true) + mp:x(x) + TOOLTIP:SetText(txt) + hid = false + else + if not hid then + self:GetChild("MousePosition"):visible(false) + TOOLTIP:Hide() + hid = true + end + end + end) + end, + BeginCommand = function(self) + if not extraFeatures then return end -- no extra features: dont add the input highlight + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + if #highlightTable ~= 0 then + if event.type == "InputEventType_FirstPress" then + if event.button == "MenuDown" or event.button == "Down" then + moveHighlightIndex(1) + self:playcommand("DrawOffsets") + self:hurrytweening(0.2) + elseif event.button == "MenuUp" or event.button == "Up" then + moveHighlightIndex(-1) + self:playcommand("DrawOffsets") + self:hurrytweening(0.2) + end + end + end + end) + end, + UpdateSizingCommand = function(self, params) + if params.sizing ~= nil then + sizing = params.sizing + end + if params.judgeSetting ~= nil then + judgeSetting = params.judgeSetting + timingScale = ms.JudgeScalers[judgeSetting] + end + end +} + +t[#t+1] = Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "offsetPlot", "Background") + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:y(sizing.Height / 2) + self:zoomto(sizing.Width, sizing.Height) + end +} + +if extraFeatures then + t[#t+1] = Def.Quad { + Name = "MousePosition", + InitCommand = function(self) + self:valign(0) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "offsetPlot", "HoverLine") + self:zoomx(lineThickness) + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:zoomy(sizing.Height) + end + } +end + +t[#t+1] = Def.Quad { + Name = "CenterLine", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(lineAlpha) + registerActorToColorConfigElement(self, "judgment", "TapNoteScore_W1") + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:y(sizing.Height / 2) + self:zoomto(sizing.Width, lineThickness) + end +} + +for i, j in ipairs(barJudgments) do + t[#t+1] = Def.Quad { + Name = j.."_Late", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(lineAlpha) + registerActorToColorConfigElement(self, "judgment", j) + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + local window = ms.getLowerWindowForJudgment(j, timingScale) + self:y(fitY(window, maxOffset)) + self:zoomto(sizing.Width, lineThickness) + end + } + t[#t+1] = Def.Quad { + Name = j.."_Early", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(lineAlpha) + registerActorToColorConfigElement(self, "judgment", j) + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + local window = ms.getLowerWindowForJudgment(j, timingScale) + self:y(fitY(-window, maxOffset)) + self:zoomto(sizing.Width, lineThickness) + end + } +end + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "LateText", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoom(textSize) + registerActorToColorConfigElement(self, "offsetPlot", "Text") + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + local bound = ms.getUpperWindowForJudgment(barJudgments[#barJudgments], timingScale) + self:xy(textPadding, textPadding) + self:settextf("Late (+%dms)", bound) + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "EarlyText", + InitCommand = function(self) + self:halign(0):valign(1) + self:zoom(textSize) + registerActorToColorConfigElement(self, "offsetPlot", "Text") + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + local bound = ms.getUpperWindowForJudgment(barJudgments[#barJudgments], timingScale) + self:xy(textPadding, sizing.Height - textPadding) + self:settextf("Early (-%dms)", bound) + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "InstructionText", + InitCommand = function(self) + self:valign(1) + self:zoom(instructionTextSize) + self:settext("") + registerActorToColorConfigElement(self, "offsetPlot", "Text") + self:playcommand("UpdateSizing") + self:finishtweening() + end, + UpdateSizingCommand = function(self) + self:finishtweening() + self:smooth(resizeAnimationSeconds) + self:xy(sizing.Width / 2, sizing.Height - textPadding) + self:maxwidth((sizing.Width - self:GetParent():GetChild("EarlyText"):GetZoomedWidth() * 2) / instructionTextSize - textPadding) + end, + UpdateTextCommand = function(self) + local cols = {} + if #highlightTable == 0 or highlightTable[highlightIndex] == nil or not extraFeatures then + self:settext("") + else + for col, _ in pairs(highlightTable[highlightIndex]) do + cols[#cols+1] = col + cols[#cols+1] = " " + end + cols[#cols] = nil + cols = table.concat(cols) + self:settextf("Up/Down for column highlights (Now: %s)", cols) + end + end +} + +-- keeping track of stuff for persistence dont look at this +local lastOffsets = {} +local lastTracks = {} +local lastTiming = {} +local lastTimingData = nil +local lastTypes = {} +local lastHolds = {} +local lastMaxTime = 0 +local lastColumns = nil + +t[#t+1] = Def.ActorMultiVertex { + Name = "Dots", + InitCommand = function(self) + --self:zoomto(0, 0) + self:playcommand("UpdateSizing") + end, + UpdateSizingCommand = function(self) + + end, + ColorConfigUpdatedMessageCommand = function(self) + self:finishtweening() + self:linear(0.5) + self:queuecommand("DrawOffsets") + end, + LoadOffsetsCommand = function(self, params) + -- makes sure all sizes are updated + self:GetParent():playcommand("UpdateSizing", params) + + lastOffsets = params.offsetVector + lastTracks = params.trackVector + lastTimingData = params.timingData + lastTypes = params.typeVector + lastHolds = params.holdVector + lastTiming = {} + for i, row in ipairs(params.noteRowVector) do + lastTiming[i] = lastTimingData:GetElapsedTimeFromNoteRow(row) + end + + lastMaxTime = params.maxTime + lastColumns = params.columns + if not params.rejudged then + highlightTable = calcDotHighlightTable(lastTracks, lastColumns) + highlightIndex = 1 + end + + -- draw dots + self:playcommand("DrawOffsets") + end, + DrawOffsetsCommand = function(self) + local vertices = {} + local offsets = lastOffsets + local tracks = lastTracks + local timing = lastTiming + local types = lastTypes + local holds = lastHolds + local maxTime = lastMaxTime + self:GetParent():playcommand("UpdateText") + + if offsets == nil or #offsets == 0 then + self:SetVertices(vertices) + self:SetDrawState {Mode = "DrawMode_Quads", First = 1, Num = 0} + return + end + + -- dynamically change the dot size depending on the number of dots + -- for clarity on ultra dense scores + dotLineLength = clamp(scale(#offsets, 1000, 5000, dotLineUpperBound, dotLineLowerBound), dotLineLowerBound, dotLineUpperBound) + + -- taps and mines + for i, offset in ipairs(offsets) do + local x = fitX(timing[i], maxTime) + local y = fitY(offset, maxOffset) + local column = tracks ~= nil and tracks[i] ~= nil and tracks[i] + 1 or nil + + local cappedY = math.max(maxOffset, (maxOffset) * timingScale) + if y < 0 or y > sizing.Height then + y = fitY(cappedY, maxOffset) + end + + + if types[i] ~= "TapNoteType_Mine" then + -- handle highlighting logic + local dotColor = colorByTapOffset(offset, timingScale) + if not columnIsHighlighted(column) then + dotColor[4] = unHighlightedAlpha + end + placeDotVertices(vertices, x, y, dotColor) + else + -- this function handles the highlight logic + local mineColor = getMineColor(column) + placeMineVertices(vertices, x, fitY(-maxOffset, maxOffset), mineColor) + end + end + + -- holds and rolls + --[[ + if holds ~= nil and #holds > 0 then + for i, h in ipairs(holds) do + local row = h.row + local holdtype = h.TapNoteSubType + local column = h.track + 1 + local holdColor = getHoldColor(column, holdtype) + local rowtime = lastTimingData:GetElapsedTimeFromNoteRow(row) + local x = fitX(rowtime, maxTime) + placeNoodleVertices(vertices, x, fitY(-maxOffset, maxOffset), holdColor) + end + end + ]] + + -- animation breaks if we start from nothing + if self:GetNumVertices() ~= 0 then + self:finishtweening() + self:smooth(dotAnimationSeconds) + end + self:SetVertices(vertices) + self:SetDrawState {Mode = "DrawMode_Quads", First = 1, Num = #vertices} + end +} + + + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/assetsettings.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/assetsettings.lua new file mode 100644 index 0000000000..22e814aafe --- /dev/null +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/assetsettings.lua @@ -0,0 +1,909 @@ +-- a large amount of this file is copy pasted and adapted from til death and spawncamping-wallhack +-- things are cleaned up a bit to try to match the format of the rest of this theme +local ratios = { + Width = 782 / 1920, + Height = 971 / 1080, + TopLipHeight = 44 / 1080, + EdgePadding = 13 / 1920, -- distance from left and right edges for everything + PageTextRightGap = 33 / 1920, -- right of frame, right of text + PageTextUpperGap = 48 / 1080, -- literally a guess +} + +local actuals = { + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + TopLipHeight = ratios.TopLipHeight * SCREEN_HEIGHT, + EdgePadding = ratios.EdgePadding * SCREEN_WIDTH, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, + PageTextUpperGap = ratios.PageTextUpperGap * SCREEN_HEIGHT, +} + +local visibleframeX = SCREEN_WIDTH - actuals.Width +local visibleframeY = SCREEN_HEIGHT - actuals.Height +local hiddenframeX = SCREEN_WIDTH +local animationSeconds = 0.1 +local focused = false +local prevScreen = "" + +local t = Def.ActorFrame { + Name = "AssetSettingsFile", + InitCommand = function(self) + self:playcommand("SetPosition") + self:y(visibleframeY) + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + -- if we ever get this message we need to hide the frame and just exit. + focused = false + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + end, + PlayerInfoFrameTabSetMessageCommand = function(self, params) + if params.tab and params.tab == "AssetSettings" then + -- allow exiting out of this screen in a specific ... direction + prevScreen = params.prevScreen or "" + + self:diffusealpha(1) + self:finishtweening() + self:sleep(0.01) + self:queuecommand("FinishFocusing") + self:smooth(animationSeconds) + self:x(visibleframeX) + else + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + focused = false + end + end, + FinishFocusingCommand = function(self) + -- the purpose of this is to delay the act of focusing the screen + -- in case any input events cause breakage on this screen + focused = true + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "AssetSettings") + end, + SetPositionCommand = function(self) + if getWheelPosition() then + visibleframeX = SCREEN_WIDTH - actuals.Width + hiddenframeX = SCREEN_WIDTH + else + visibleframeX = 0 + hiddenframeX = -actuals.Width + end + if focused then + self:x(visibleframeX) + else + self:x(hiddenframeX) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, +} + +local titleTextSize = 0.8 +local choiceTextSize = 1 +local assetPathTextSize = 0.7 + +local pageTextSize = 0.7 +local textZoomFudge = 5 +local buttonHoverAlpha = 0.6 + +t[#t+1] = Def.Quad { + Name = "AssetSettingsBGQuad", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "AssetSettingsLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "AssetSettingsTitle", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight / 2) + self:zoom(titleTextSize) + self:maxwidth(actuals.Width / titleTextSize - textZoomFudge) + self:settext("Asset Settings") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end +} + +-- produces all the fun stuff in the asset settings +local function assetList() + local settingsframe = nil + + local curType = 2 -- start on Avatar page + local assetTypes = { + "toasty", + "avatar", + "judgment", + } + + -- state + local maxPage = 1 + local curPage = 1 + local maxRows = 5 + local maxColumns = 5 + local curIndex = 1 + local selectedIndex = 0 + local profile = PROFILEMAN:GetProfile(PLAYER_1) + local GUID = profile:GetGUID() + local curPath = "" + local lastClickedIndex = 0 + + local assetTable = {} + + -- sizing + local frameWidth = actuals.Width - actuals.EdgePadding*2 + local frameHeight = actuals.Height - actuals.TopLipHeight*2 + local aspectRatioProportion = (16/9) / (SCREEN_WIDTH / SCREEN_HEIGHT) -- this was designed for 16:9 so compensate + local squareWidth = 50 / aspectRatioProportion -- adjust for aspect ratio + local judgmentWidth = 75 / aspectRatioProportion -- same + local assetWidth = squareWidth + local assetHeight = 50 / aspectRatioProportion -- same + local assetXSpacing = (frameWidth + assetWidth/2) / (maxColumns + 1) + local assetYSpacing = (frameHeight - 20 / aspectRatioProportion) / (maxRows + 1) -- same + + local co = nil -- for async loading images + + ------------------- + -- utility functions + -- self explanatory + local function findIndexForCurPage() + local type = assetTypes[curType] + for i = 1+((curPage-1)*maxColumns*maxRows), 1+((curPage)*maxColumns*maxRows) do + if assetTable[i] == nil then return nil end + if assetFolders[type] .. assetTable[i] == curPath then + return i + end + end + end + local function findPickedIndexForCurPage() + local type = assetTypes[curType] + for i = 1, #assetTable do + if assetTable[i] == nil then return nil end + if assetFolders[type] .. assetTable[i] == selectedPath then + return i + end + end + end + local function isImage(filename) + local extensions = {".png", ".jpg", "jpeg"} -- lazy list + local ext = string.sub(filename, #filename-3) + for i=1, #extensions do + if extensions[i] == ext then return true end + end + return false + end + local function isAudio(filename) + local extensions = {".wav", ".mp3", ".ogg", ".mp4"} -- lazy to check and put in names + local ext = string.sub(filename, #filename-3) + for i=1, #extensions do + if extensions[i] == ext then return true end + end + return false + end + local function getImagePath(path, assets) -- expecting a table of asset paths where fallbacks are default + for i=1, #assets do + if isImage(assets[i]) then + return path .. "/" .. assets[i] + end + end + return assetsFolder .. assetFolders[assetTypes[curType]] .. getDefaultAssetByType(assetType[curType]) .. "/default.png" + end + local function getSoundPath(path, assets) -- expecting a table of asset paths where fallbacks are default + for i=1, #assets do + if isAudio(assets[i]) then + return path .. "/" .. assets[i] + end + end + return assetsFolder .. assetFolders[assetTypes[curType]] .. getDefaultAssetByType(assetTypes[curType]) .. "/default.ogg" + end + local function containsDirsOnly(dirlisting) + if #dirlisting == 0 then return true end + for i=1, #dirlisting do + if isImage(dirlisting[i]) or isAudio(dirlisting[i]) then + return false + end + end + return true + end + -- + -- + ------------------- + + ------------------- + -- these are also utility + -- load asset table for current type + local function loadAssetTable() + local type = assetTypes[curType] + curPath = getAssetByType(type, GUID) + selectedPath = getAssetByType(type, GUID) + local dirlisting = FILEMAN:GetDirListing(assetFolders[type]) + if containsDirsOnly(dirlisting) then + assetTable = dirlisting + else + assetTable = filter(isImage, dirlisting) + end + maxPage = math.max(1, math.ceil(#assetTable/(maxColumns * maxRows))) + local ind = findIndexForCurPage() + local pickind = findPickedIndexForCurPage() + if pickind ~= nil then selectedIndex = pickind end + if ind ~= nil then curIndex = ind end + end + + -- select the asset in the current index for use ingame + local function confirmPick() + if curIndex == 0 then return end + local type = assetTypes[curType] + local name = assetTable[lastClickedIndex+((curPage-1)*maxColumns*maxRows)] + if name == nil then return end + local path = assetFolders[type] .. name + curPath = path + selectedPath = path + selectedIndex = lastClickedIndex+((curPage-1)*maxColumns*maxRows) + + setAssetsByType(type, GUID, path) + + MESSAGEMAN:Broadcast("PickChanged") + if type == "avatar" then + MESSAGEMAN:Broadcast("AvatarChanged") + end + end + + -- Update all image actors (sprites) + local function updateImages() + loadAssetTable() + MESSAGEMAN:Broadcast("UpdatingAssets", {name = assetTypes[curType]}) + for i=1, math.min(maxRows * maxColumns, #assetTable) do + MESSAGEMAN:Broadcast("UpdateAsset", {index = i}) + coroutine.yield() + end + MESSAGEMAN:Broadcast("UpdateFinished") + end + + -- move and load asset type forward/backward + local function loadAssetType(n) + if n < 1 then n = #assetTypes end + if n > #assetTypes then n = 1 end + lastClickedIndex = 0 + curPage = 1 + curType = n + co = coroutine.create(updateImages) + end + + -- compatibility function to cope with copy pasta and lack of care + -- make sure this roughly follows the assetTypes list constructed above + local function groupSet(groupname) + local groupnameorder = { + toasty = 1, + avatar = 2, + judgment = 3, + } + if groupnameorder[groupname] == nil then return end + local cur = curType + local goal = groupnameorder[groupname] + local movementamount = goal - cur + loadAssetType(cur + movementamount) + end + + -- Get cursor index + local function getIndex() + local out = ((curPage-1) * maxColumns * maxRows) + curIndex + return out + end + + -- Get not the cursor index (really this doesnt work) + local function getSelectedIndex() + local out = ((curPage-1) * maxColumns * maxRows) + selectedIndex + return out + end + + -- Move n pages forward/backward + local function movePage(n) + local nextPage = curPage + n + if nextPage > maxPage then + nextPage = maxPage + elseif nextPage < 1 then + nextPage = 1 + end + + -- This loads all images again if we actually move to a new page. + if nextPage ~= curPage then + curIndex = n < 0 and math.min(#assetTable, maxRows * maxColumns) or 1 + lastClickedIndex = 0 + curPage = nextPage + MESSAGEMAN:Broadcast("PageMoved",{index = curIndex, page = curPage}) + co = coroutine.create(updateImages) + end + end + + -- move the cursor + local function moveCursor(x, y) + local move = x + y * maxColumns + local nextPage = curPage + local oldIndex = curIndex + + if curPage > 1 and curIndex == 1 and move < 0 then + curIndex = math.min(#assetTable, maxRows * maxColumns) + nextPage = curPage - 1 + elseif curPage < maxPage and curIndex == maxRows * maxColumns and move > 0 then + curIndex = 1 + nextPage = curPage + 1 + else + curIndex = curIndex + move + if curIndex < 1 then + curIndex = 1 + elseif curIndex > math.min(maxRows * maxColumns, #assetTable - (maxRows * maxColumns * (curPage-1))) then + curIndex = math.min(maxRows * maxColumns, #assetTable - (maxRows * maxColumns * (curPage-1))) + end + end + lastClickedIndex = curIndex + if curPage == nextPage then + MESSAGEMAN:Broadcast("CursorMoved",{index = curIndex, prevIndex = oldIndex}) + else + curPage = nextPage + MESSAGEMAN:Broadcast("PageMoved",{index = curIndex, page = curPage}) + co = coroutine.create(updateImages) + end + end + + local t = Def.ActorFrame { + Name = "AssetSettingsInternalFrame", + InitCommand = function(self) + settingsframe = self + end, + BeginCommand = function(self) + self:playcommand("UpdateItemList") + + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + -- init the input context but start it out false + CONTEXTMAN:RegisterToContextSet(snm, "AssetSettings", anm) + CONTEXTMAN:ToggleContextSet(snm, "AssetSettings", false) + + -- update function for coroutines + -- this is totally unnecessary but not really but yeah really + -- the coroutines are responsible for "parallel processing" functions or whatever + -- but the reality is thats literally a lie because lua is forced to be single threaded here + co = coroutine.create(updateImages) + self:SetUpdateFunction(function(self, delta) + if not focused then return end + if coroutine.status(co) ~= "dead" then + coroutine.resume(co) + end + end) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if context is set to AssetSettings, passthrough unless not holding ctrl and a number + -- pressing a number with ctrl should lead to the general tab + -- otherwise, typing numbers is allowed + if CONTEXTMAN:CheckContextSet(snm, "AssetSettings") then + if event.type ~= "InputEventType_Release" then + local btn = event.DeviceInput.button + local gbtn = event.button + if btn == "DeviceButton_escape" then + -- shortcut to exit back to general + -- or back to settings screen + if prevScreen == "Settings" then + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + else + MESSAGEMAN:Broadcast("GeneralTabSet") + end + else + local del = btn == "DeviceButton_delete" + local bs = btn == "DeviceButton_backspace" + local char = inputToCharacter(event) + local up = gbtn == "MenuUp" or gbtn == "Up" + local down = gbtn == "MenuDown" or gbtn == "Down" + local left = gbtn == "MenuLeft" or gbtn == "Left" + local right = gbtn == "MenuRight" or gbtn == "Right" + local enter = gbtn == "Start" + local pageup = gbtn == "EffectUp" + local pagedown = gbtn == "EffectDown" + + -- if ctrl is pressed with a number, let the general tab input handler deal with this + if char ~= nil and tonumber(char) and INPUTFILTER:IsControlPressed() then + return + end + + if enter then + confirmPick() + elseif left then + moveCursor(-1, 0) + elseif right then + moveCursor(1, 0) + elseif up then + moveCursor(0, -1) + elseif down then + moveCursor(0, 1) + elseif pageup then + loadAssetType(curType + 1) + elseif pagedown then + loadAssetType(curType - 1) + end + + self:playcommand("UpdateItemList") + end + end + end + + end) + end, + UpdateItemListCommand = function(self) + TOOLTIP:Hide() + -- maxPage = math.ceil(#packlisting / itemCount) + end, + + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.TopLipHeight + actuals.PageTextUpperGap) + self:zoom(pageTextSize) + self:maxwidth((actuals.Width - actuals.PageTextRightGap) / pageTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdatingAssetsMessageCommand = function(self, params) + local lb = clamp((curPage-1) * (maxColumns*maxRows) + 1, 0, #assetTable) + local ub = clamp(curPage * maxColumns * maxRows, 0, #assetTable) + self:settextf("%d-%d/%d", lb, ub, #assetTable) + end + }, + LoadFont("Common Normal") .. { + Name = "CurrentPath", + InitCommand = function(self) + self:zoom(assetPathTextSize) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight + actuals.PageTextUpperGap) + self:maxwidth((actuals.Width - actuals.EdgePadding) / assetPathTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + local type = assetTable[getIndex()] + local out = "" + if type ~= nil then + out = type:gsub("^%l", string.upper) + end + self:settextf("Hovered: %s", out) + end, + CursorMovedMessageCommand = function(self) + self:queuecommand("Set") + end, + UpdateFinishedMessageCommand = function(self) + self:queuecommand("Set") + end + }, + LoadFont("Common Normal") .. { + Name = "SelectedPath", + InitCommand = function(self) + self:zoom(assetPathTextSize) + self:halign(0) + -- extremely scuffed y position + self:xy(actuals.EdgePadding, actuals.TopLipHeight + actuals.PageTextUpperGap + actuals.PageTextUpperGap/2) + self:maxwidth((actuals.Width - actuals.EdgePadding) / assetPathTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + local type = assetTable[selectedIndex] + local out = "" + if type ~= nil then + out = type:gsub("^%l", string.upper) + end + self:settextf("Selected: %s", out) + end, + PickChangedMessageCommand = function(self) + self:queuecommand("Set") + end, + UpdateFinishedMessageCommand = function(self) + self:queuecommand("Set") + end + }, + } + + + local function assetBox(i) + local name = assetTable[i] + local t = Def.ActorFrame { + Name = tostring(i), + InitCommand = function(self) + self:x((((i-1) % maxColumns)+1)*assetXSpacing) + self:y(((math.floor((i-1)/maxColumns)+1)*assetYSpacing)-10+50) + self:diffusealpha(0) + end, + PageMovedMessageCommand = function(self) + self:finishtweening() + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:diffusealpha(0) + end, + UpdateAssetMessageCommand = function(self, params) + if params.index == i then + if i+((curPage-1)*maxColumns*maxRows) > #assetTable then + self:finishtweening() + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:diffusealpha(0) + else + local type = assetTypes[curType] + name = assetFolders[type] .. assetTable[i+((curPage-1)*maxColumns*maxRows)] + if name == curPath then + curIndex = i + end + + if curType == 3 then + assetWidth = judgmentWidth + else + assetWidth = squareWidth + end + + -- Load the asset image + self:GetChild("Image"):playcommand("LoadAsset") + self:GetChild("Sound"):playcommand("LoadAsset") + self:GetChild("SelectedAssetIndicator"):playcommand("Set") + if i == curIndex then + self:GetChild("Image"):finishtweening() + self:GetChild("Image"):zoomto(assetHeight+8,assetWidth+8) + self:GetChild("Border"):zoomto(assetHeight+12,assetWidth+12) + self:GetChild("Border"):diffuse(COLORS:getColor("assetSettings", "HoveredItem")):diffusealpha(0.8) + else + self:GetChild("Image"):zoomto(assetHeight,assetWidth) + self:GetChild("Border"):zoomto(assetHeight+4,assetWidth+4) + self:GetChild("Border"):diffuse(COLORS:getColor("assetSettings", "HoveredItem")):diffusealpha(0) + end + + self:y(((math.floor((i-1)/maxColumns)+1)*assetYSpacing)-10+50) + self:finishtweening() + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:diffusealpha(1) + self:y((math.floor((i-1)/maxColumns)+1)*assetYSpacing+50) + end + end + end, + UpdateFinishedMessageCommand = function(self) + if assetTable[i+((curPage-1)*maxColumns*maxRows)] == nil then + self:finishtweening() + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:diffusealpha(0) + end + if curType == 3 then + MESSAGEMAN:Broadcast("CursorMoved",{index = findPickedIndexForCurPage()}) + end + end + } + + t[#t+1] = Def.Quad { + Name = "SelectedAssetIndicator", + InitCommand = function(self) + self:zoomto(assetWidth+14, assetHeight+14) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "assetSettings", "SavedItem") + end, + SetCommand = function(self) + self:zoomto(assetWidth+14, assetHeight+14) + self:finishtweening() + if selectedPath == name then + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:diffusealpha(0.8) + else + self:smooth(0.2) + self:diffusealpha(0) + end + end, + PageMovedMessageCommand = function(self) + self:queuecommand("Set") + end, + PickChangedMessageCommand = function(self) + self:queuecommand("Set") + end + } + + t[#t+1] = Def.Quad { + Name = "Border", + InitCommand = function(self) + self:zoomto(assetWidth+4, assetHeight+4) + self:diffusealpha(0.8) + registerActorToColorConfigElement(self, "assetSettings", "HoveredItem") + end, + SelectCommand = function(self) + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:zoomto(assetWidth+12, assetHeight+12) + self:diffusealpha(0.8) + end, + DeselectCommand = function(self) + self:smooth(0.2) + self:zoomto(assetWidth+4, assetHeight+4) + self:diffusealpha(0) + end, + CursorMovedMessageCommand = function(self, params) + self:finishtweening() + if params.index == i then + self:playcommand("Select") + else + self:playcommand("Deselect") + end + end, + PageMovedMessageCommand = function(self, params) + self:finishtweening() + if params.index == i then + self:playcommand("Select") + else + self:playcommand("Deselect") + end + end, + } + + t[#t+1] = UIElements.SpriteButton(1, 1, nil) .. { + Name = "Image", + LoadAssetCommand = function(self) + local assets = findAssetsForPath(name) + if #assets > 1 then + local image = getImagePath(name, assets) + self:LoadBackground(image) + else + self:LoadBackground(name) + end + end, + CursorMovedMessageCommand = function(self, params) + self:finishtweening() + if params.index == i then + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:zoomto(assetWidth+8, assetHeight+8) + else + self:smooth(0.2) + self:zoomto(assetWidth, assetHeight) + end + end, + PageMovedMessageCommand = function(self, params) + self:finishtweening() + if params.index == i then + self:tween(0.5,"TweenType_Bezier",{0,0,0,0.5,0,1,1,1}) + self:zoomto(assetWidth+8, assetHeight+8) + else + self:smooth(0.2) + self:zoomto(assetWidth, assetHeight) + end + end, + MouseDownCommand = function(self) + if assetTable[i+((curPage-1)*maxColumns*maxRows)] ~= nil then + if lastClickedIndex == i then + confirmPick() + end + local prev = curIndex + lastClickedIndex = i + curIndex = i + MESSAGEMAN:Broadcast("CursorMoved",{index = i, prevIndex = prev}) + end + end, + MouseOverCommand = function(self) + if not self:IsInvisible() then + self:diffusealpha(buttonHoverAlpha) + end + end, + MouseOutCommand = function(self) + if not self:IsInvisible() then + self:diffusealpha(1) + end + end, + } + + t[#t+1] = Def.Sound { + Name = "Sound", + LoadAssetCommand = function(self) + local assets = findAssetsForPath(name) + if #assets > 1 then + local soundpath = getSoundPath(name, assets) + self:load(soundpath) + else + self:load("") + end + + end, + CursorMovedMessageCommand = function(self, params) + if params.index == i and curType == 1 and params.prevIndex ~= i then + self:play() + end + end + } + + return t + end + + + -- this is a copy paste adaptation from the downloads.lua choices + -- its good and bad for this application for a few reasons, but whatever works is good enough + local function tabChoices() + -- keeping track of which choices are on at any moment (keys are indices, values are true/false/nil) + local activeChoices = {} + + -- identify each choice using this table + -- Name: The name of the choice (NOT SHOWN TO THE USER) + -- Type: Toggle/Exclusive/Tap + -- Toggle - This choice can be clicked multiple times to scroll through choices + -- Exclusive - This choice is one out of a set of Exclusive choices. Only one Exclusive choice can be picked at once + -- Tap - This choice can only be pressed (if visible by Condition) and will only run TapFunction at that time + -- Display: The string the user sees. One option for each choice must be given if it is a Toggle choice + -- Condition: A function that returns true or false. Determines if the choice should be visible or not + -- IndexGetter: A function that returns an index for its status, according to the Displays set + -- TapFunction: A function that runs when the button is pressed + local choiceDefinitions = { + { -- Set to Toasty Select Page + Name = "toasty", + Type = "Exclusive", + Display = {"Toasty"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + groupSet("toasty") + page = 1 + end, + }, + { -- Set to Avatar Select Page + Name = "avatar", + Type = "Exclusive", + Display = {"Avatar"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + groupSet("avatar") + page = 1 + end, + }, + { -- Set to Judgment Select Page + Name = "judgment", + Type = "Exclusive", + Display = {"Judgment"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + groupSet("judgment") + page = 1 + end, + }, + } + + local function createChoice(i) + local definition = choiceDefinitions[i] + local displayIndex = 1 + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ChoiceButton_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceDefinitions) * (i-1) + (actuals.Width / #choiceDefinitions / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceDefinitions / choiceTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceDefinitions, actuals.TopLipHeight) + self:playcommand("UpdateText") + end, + UpdatingAssetsMessageCommand = function(self, params) + if params.name == definition.Name then + activeChoices[i] = true + else + activeChoices[i] = false + end + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- update index + displayIndex = definition.IndexGetter() + + -- update visibility by condition + if definition.Condition() then + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + else + self:diffusealpha(0) + end + + if activeChoices[i] then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.8)) + else + txt:strokecolor(color("0,0,0,0")) + end + + -- update display + txt:settext(definition.Display[displayIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- exclusive choices cause activechoices to be forced to this one + if definition.Type == "Exclusive" then + activeChoices = {[i]=true} + else + -- uhh i didnt implement any other type that would ... be used for.. this + end + + -- run the tap function + if definition.TapFunction ~= nil then + definition.TapFunction() + end + self:GetParent():GetParent():playcommand("UpdateItemList") + self:GetParent():playcommand("UpdateText") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.Height - actuals.TopLipHeight / 2) + end, + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end + } + } + for i = 1, #choiceDefinitions do + t[#t+1] = createChoice(i) + end + return t + end + for i = 1, maxRows * maxColumns do + t[#t+1] = assetBox(i) + end + t[#t+1] = tabChoices() + return t +end + +t[#t+1] = assetList() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/downloads.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/downloads.lua new file mode 100644 index 0000000000..7dc6f415eb --- /dev/null +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/downloads.lua @@ -0,0 +1,1074 @@ +local ratios = { + Width = 782 / 1920, + Height = 971 / 1080, + TopLipHeight = 44 / 1080, + EdgePadding = 13 / 1920, -- distance from left and right edges for everything + + IndexColumnLeftGap = 38 /1920, -- left edge to right edge (right align) + NameColumnLeftGap = 45 / 1920, -- left edge to left edge (left align) + MSDColumnLeftGap = 495 / 1920, -- left edge to center of text (center align) + SizeHeaderLeftGap = 592 / 1920, -- left edge to left edge (left align) + SizeColumnLeftGap = 662 / 1920, -- left edge to right edge (right align) + MainDLLeftGap = 692 / 1920, -- left edge to left edge + MirrorDLLeftGap = 734 / 1920, -- left edge to left edge + DLIconSize = 20 / 1080, -- it is a square + -- placement of the DL Text is left align on MainDLLeftGap with width MirrorDLLeftGap - MainDLLeftGap + DLIconSize + HeaderLineUpperGap = 13 / 1080, -- from bottom of top lip to top of header text + ItemListUpperGap = 55 / 1080, -- from bottom of top lip to top of topmost item in the list + -- ItemListAllottedSpace is instead just some fraction of the Height related to the height of the bundle area + MSDWidth = 80 / 1920, -- approximated max normal width of the MSD since it is center aligned and boundaries need to be made + + SearchBGLeftGap = 292 / 1920, -- left edge to left edge + SearchBGWidth = 456 / 1920, + SearchBGHeight = 28 / 1080, + SearchIconLeftGap = 6 / 1920, -- from left edge of search bg to left edge of icon + SearchIconSize = 23 / 1080, -- it is a square + SearchTextLeftGap = 40 / 1920, -- left edge of bg to left edge of text + PageTextRightGap = 33 / 1920, -- right of frame, right of text +} + +local actuals = { + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + TopLipHeight = ratios.TopLipHeight * SCREEN_HEIGHT, + EdgePadding = ratios.EdgePadding * SCREEN_WIDTH, + IndexColumnLeftGap = ratios.IndexColumnLeftGap * SCREEN_WIDTH, + NameColumnLeftGap = ratios.NameColumnLeftGap * SCREEN_WIDTH, + MSDColumnLeftGap = ratios.MSDColumnLeftGap * SCREEN_WIDTH, + SizeHeaderLeftGap = ratios.SizeHeaderLeftGap * SCREEN_WIDTH, + SizeColumnLeftGap = ratios.SizeColumnLeftGap * SCREEN_WIDTH, + MainDLLeftGap = ratios.MainDLLeftGap * SCREEN_WIDTH, + MirrorDLLeftGap = ratios.MirrorDLLeftGap * SCREEN_WIDTH, + DLIconSize = ratios.DLIconSize * SCREEN_HEIGHT, + HeaderLineUpperGap = ratios.HeaderLineUpperGap * SCREEN_HEIGHT, + ItemListUpperGap = ratios.ItemListUpperGap * SCREEN_HEIGHT, + MSDWidth = ratios.MSDWidth * SCREEN_WIDTH, + SearchBGLeftGap = ratios.SearchBGLeftGap * SCREEN_WIDTH, + SearchBGWidth = ratios.SearchBGWidth * SCREEN_WIDTH, + SearchBGHeight = ratios.SearchBGHeight * SCREEN_HEIGHT, + SearchIconLeftGap = ratios.SearchIconLeftGap * SCREEN_WIDTH, + SearchIconSize = ratios.SearchIconSize * SCREEN_HEIGHT, + SearchTextLeftGap = ratios.SearchTextLeftGap * SCREEN_WIDTH, + PageTextRightGap = ratios.PageTextRightGap * SCREEN_WIDTH, +} + +local visibleframeX = SCREEN_WIDTH - actuals.Width +local visibleframeY = SCREEN_HEIGHT - actuals.Height +local hiddenframeX = SCREEN_WIDTH +local animationSeconds = 0.1 +local focused = false + +local t = Def.ActorFrame { + Name = "DownloadsFile", + InitCommand = function(self) + self:playcommand("SetPosition") + self:y(visibleframeY) + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + -- if we ever get this message we need to hide the frame and just exit. + focused = false + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + end, + PlayerInfoFrameTabSetMessageCommand = function(self, params) + if params.tab and params.tab == "Downloads" then + self:diffusealpha(1) + self:finishtweening() + self:sleep(0.01) + self:queuecommand("FinishFocusing") + self:smooth(animationSeconds) + self:x(visibleframeX) + else + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + focused = false + end + end, + FinishFocusingCommand = function(self) + -- the purpose of this is to delay the act of focusing the screen + -- the reason is that we dont want to trigger Ctrl+3 inputting a 3 on the search field immediately + focused = true + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Downloads") + end, + SetPositionCommand = function(self) + if getWheelPosition() then + visibleframeX = SCREEN_WIDTH - actuals.Width + hiddenframeX = SCREEN_WIDTH + else + visibleframeX = 0 + hiddenframeX = -actuals.Width + end + if focused then + self:x(visibleframeX) + else + self:x(hiddenframeX) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, +} + +local titleTextSize = 0.8 +local indexTextSize = 0.8 +local nameTextSize = 0.8 +local msdTextSize = 0.8 +local sizeTextSize = 0.8 +local cancelTextSize = 0.8 + +local indexHeaderSize = 1 +local nameHeaderSize = 1 +local msdHeaderSize = 1 +local sizeHeaderSize = 1 +local searchTextSize = 1 +local choiceTextSize = 1 + +local pageTextSize = 0.5 +local textZoomFudge = 5 +local pageAnimationSeconds = 0.01 +local buttonHoverAlpha = 0.6 +local buttonEnabledAlphaMultiplier = 0.8 -- this is multiplied to the current alpha (including the hover alpha) if "clicked" + +t[#t+1] = Def.Quad { + Name = "DownloadsBGQuad", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "DownloadsLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "DownloadsTitle", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight / 2) + self:zoom(titleTextSize) + self:maxwidth(actuals.Width / titleTextSize - textZoomFudge) + self:settext("Pack Downloader") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end +} + +local function toolTipOn(msg) + TOOLTIP:SetText(msg) + TOOLTIP:Show() +end + +-- produces all the fun stuff in the pack downloader +local function downloadsList() + local itemCount = 25 + local listAllottedSpace = actuals.Height - actuals.TopLipHeight - actuals.ItemListUpperGap - actuals.TopLipHeight + local inBundles = false + local downloaderframe = nil + local searchstring = "" + + -- fallback behavior: this is a PackList + -- it has internal sorting properties we will use to our advantage + local pl = PackList:new() + local packlisting = pl:GetPackTable() + local downloadingPacks = DLMAN:GetDownloadingPacks() + local queuedPacks = DLMAN:GetQueuedPacks() + local downloadingPacksByName = {} + local queuedPacksByName = {} + + -- these are the defined bundle types + -- note that each bundle has an expanded version + local bundleTypes = { + "Novice", + "Beginner", + "Intermediate", + "Advanced", + "Expert", + } + + local page = 1 + local maxPage = 1 + + local function movePage(n) + if not inBundles then + if maxPage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + if downloaderframe ~= nil then + downloaderframe:playcommand("UpdateItemList") + end + end + end + + local function tabChoices() + -- keeping track of which choices are on at any moment (keys are indices, values are true/false/nil) + local activeChoices = {} + + -- identify each choice using this table + -- Name: The name of the choice (NOT SHOWN TO THE USER) + -- Type: Toggle/Exclusive/Tap + -- Toggle - This choice can be clicked multiple times to scroll through choices + -- Exclusive - This choice is one out of a set of Exclusive choices. Only one Exclusive choice can be picked at once + -- Tap - This choice can only be pressed (if visible by Condition) and will only run TapFunction at that time + -- Display: The string the user sees. One option for each choice must be given if it is a Toggle choice + -- Condition: A function that returns true or false. Determines if the choice should be visible or not + -- IndexGetter: A function that returns an index for its status, according to the Displays set + -- TapFunction: A function that runs when the button is pressed + local choiceDefinitions = { + { -- Enter or Exit from Bundle Select + Name = "bundleselect", + Type = "Tap", + Display = {"Bundle Select", "Back"}, + IndexGetter = function() + if inBundles then + return 2 + else + return 1 + end + end, + Condition = function() return true end, + TapFunction = function() + inBundles = not inBundles + page = 1 + end, + }, + { -- Cancel all current downloads + Name = "cancelall", + Type = "Tap", + Display = {"Cancel All Downloads"}, + IndexGetter = function() return 1 end, + Condition = function() return true end, + TapFunction = function() + local count = 0 + for _, p in ipairs(DLMAN:GetQueuedPacks()) do + local s = p:RemoveFromQueue() + if s then count = count + 1 end + end + for _, p in ipairs(DLMAN:GetDownloadingPacks()) do + p:GetDownload():Stop() + count = count + 1 + end + if count > 0 then + ms.ok("Stopped All Downloads: "..count.." Downloads") + end + end, + }, + } + + local function createChoice(i) + local definition = choiceDefinitions[i] + local displayIndex = 1 + + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ChoiceButton_" ..i, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.Width / #choiceDefinitions) * (i-1) + (actuals.Width / #choiceDefinitions / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.Width / #choiceDefinitions / choiceTextSize - textZoomFudge) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(actuals.Width / #choiceDefinitions, actuals.TopLipHeight) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- update index + displayIndex = definition.IndexGetter() + + -- update visibility by condition + if definition.Condition() then + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + else + self:diffusealpha(0) + end + + if activeChoices[i] then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.8)) + else + txt:strokecolor(color("0,0,0,0")) + end + + -- update display + txt:settext(definition.Display[displayIndex]) + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + -- exclusive choices cause activechoices to be forced to this one + if definition.Type == "Exclusive" then + activeChoices = {[i]=true} + else + -- uhh i didnt implement any other type that would ... be used for.. this + end + + -- run the tap function + if definition.TapFunction ~= nil then + definition.TapFunction() + end + self:GetParent():GetParent():playcommand("UpdateItemList") + self:GetParent():playcommand("UpdateText") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.Height - actuals.TopLipHeight / 2) + end, + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.Width, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end + } + } + + for i = 1, #choiceDefinitions do + t[#t+1] = createChoice(i) + end + + return t + end + + -- for the pack list + local function listItem(i) + local index = i + local pack = nil + local bundle = nil + + return Def.ActorFrame { + InitCommand = function(self) + self:y(actuals.TopLipHeight + actuals.ItemListUpperGap + listAllottedSpace / itemCount * (i-1)) + end, + SetPackCommand = function(self) + self:finishtweening() + self:diffusealpha(0) + self:smooth(pageAnimationSeconds * i) + pack = nil + bundle = nil + -- mixing bundle behavior with packs + -- ASSUMPTION: bundles arent going to take up more than 1 page + if inBundles then + if i <= #bundleTypes * 2 then + index = math.ceil(i/2) -- 1 = 1, 2 = 1, 3 = 2, 4 = 2 ... + -- this index points to bundleTypes index + -- if i % 2 == 0 then it is an expanded bundle + self:diffusealpha(1) + bundle = DLMAN:GetCoreBundle(bundleTypes[index]:lower()..(i%2==0 and "-expanded" or "")) + end + else + index = (page-1) * itemCount + i + pack = packlisting[index] + if pack ~= nil then + self:diffusealpha(1) + end + end + end, + + LoadFont("Common Normal") .. { + Name = "Index", + InitCommand = function(self) + self:valign(0) + self:x(actuals.IndexColumnLeftGap / 2) + self:zoom(indexTextSize) + -- without this random 2, the index touches the left edge of the frame and it feels really weird + self:maxwidth(actuals.IndexColumnLeftGap / indexTextSize - 2) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetPackCommand = function(self) + if pack ~= nil then + self:settext(index) + elseif bundle ~= nil then + self:settext(i) + end + end, + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "Name", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(actuals.NameColumnLeftGap) + self:zoom(nameTextSize) + self:maxwidth((actuals.MSDColumnLeftGap - actuals.NameColumnLeftGap - actuals.MSDWidth / 2) / nameTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "SecondaryText") + self.alphaDeterminingFunction = function(self) + if isOver(self) and pack ~= nil then self:diffusealpha(buttonHoverAlpha) else self:diffusealpha(1) end + end + end, + SetPackCommand = function(self) + if pack ~= nil then + self:settext(pack:GetName()) + elseif bundle ~= nil then + local expanded = i % 2 == 0 and " Expanded" or "" + self:settext(bundleTypes[index] .. expanded) + end + self:alphaDeterminingFunction() + end, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + if pack ~= nil then + local urlstring = "https://etternaonline.com/pack/" .. pack:GetID() + GAMESTATE:ApplyGameCommand("urlnoexit," .. urlstring) + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + }, + LoadFont("Common Normal") .. { + Name = "AverageMSD", + InitCommand = function(self) + self:valign(0) + self:x(actuals.MSDColumnLeftGap) + self:zoom(msdTextSize) + self:maxwidth(actuals.MSDWidth / msdTextSize - textZoomFudge) + end, + SetPackCommand = function(self) + if pack ~= nil then + local msd = pack:GetAvgDifficulty() + self:settextf("%0.2f", msd) + self:diffuse(colorByMSD(msd)) + elseif bundle ~= nil then + local msd = bundle.AveragePackDifficulty + self:settextf("%0.2f", msd) + self:diffuse(colorByMSD(msd)) + end + end, + }, + LoadFont("Common Normal") .. { + Name = "Size", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(actuals.SizeColumnLeftGap) + self:zoom(sizeTextSize) + self:maxwidth((actuals.SizeColumnLeftGap - actuals.MSDColumnLeftGap - actuals.MSDWidth / 2) / sizeTextSize - textZoomFudge) + end, + SetPackCommand = function(self) + if pack ~= nil then + local sz = pack:GetSize() / 1024 / 1024 + self:settextf("%iMB", sz) + self:diffuse(colorByFileSize(sz)) + elseif bundle ~= nil then + local sz = bundle.TotalSize + self:settextf("%iMB", sz) + self:diffuse(colorByFileSize(sz)) + end + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "packdlicon")) .. { + Name = "MainDL", + InitCommand = function(self) + -- white: not installed, can queue + -- installed: green + self:halign(0):valign(0) + self:x(actuals.MainDLLeftGap) + self:zoomto(actuals.DLIconSize, actuals.DLIconSize) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetPack") + end, + SetPackCommand = function(self) + if pack ~= nil then + self:playcommand("UpdateVisibilityByDownloadStatus") + elseif bundle ~= nil then + self:diffuse(COLORS:getDownloaderColor("NotInstalledIcon")) + self:diffusealpha(isOver(self) and buttonHoverAlpha or 1) + if isOver(self) then toolTipOn("Download Bundle") end + else + self:diffusealpha(0) + end + end, + UpdateVisibilityByDownloadStatusCommand = function(self) + if pack ~= nil then + local name = pack:GetName() + if SONGMAN:DoesSongGroupExist(name) then + -- the pack is already installed + self:diffuse(COLORS:getDownloaderColor("InstalledIcon")) + self:diffusealpha(1) + if isOver(self) then toolTipOn("Already Installed") end + elseif downloadingPacksByName[name] ~= nil or queuedPacksByName[name] ~= nil then + -- the pack is downloading or queued + self:diffusealpha(0) + else + self:diffuse(COLORS:getDownloaderColor("NotInstalledIcon")) + self:diffusealpha(isOver(self) and buttonHoverAlpha or 1) + if isOver(self) then toolTipOn("Download Pack") end + end + end + end, + MouseDownCommand = function(self) + if self:IsInvisible() then return end + if pack ~= nil then + local name = pack:GetName() + if downloadingPacksByName[name] ~= nil or queuedPacksByName[name] ~= nil or SONGMAN:DoesSongGroupExist(name) then + return + end + pack:DownloadAndInstall(false) + elseif bundle ~= nil then + local expanded = i % 2 == 0 and " Expanded" or "" + local name = bundleTypes[index]:lower()..(i%2==0 and "-expanded" or "") + DLMAN:DownloadCoreBundle(name) + inBundles = false + page = 1 + end + self:GetParent():GetParent():playcommand("UpdateItemList") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + if pack ~= nil then + local name = pack:GetName() + if downloadingPacksByName[name] ~= nil then + toolTipOn("Currently Downloading") + elseif queuedPacksByName[name] ~= nil then + toolTipOn("Queued") + elseif SONGMAN:DoesSongGroupExist(name) then + toolTipOn("Already Installed") + else + toolTipOn("Download Pack") + self:diffusealpha(buttonHoverAlpha) + end + elseif bundle ~= nil then + toolTipOn("Download Bundle") + self:diffusealpha(buttonHoverAlpha) + end + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "packdlicon")) .. { + Name = "MirrorDL", + InitCommand = function(self) + -- white: not installed, can queue + -- installed: green + self:halign(0):valign(0) + self:x(actuals.MirrorDLLeftGap) + self:zoomto(actuals.DLIconSize, actuals.DLIconSize) + end, + ColorConfigUpdatedMessageCommand = function(self) + self:playcommand("SetPack") + end, + SetPackCommand = function(self) + if pack ~= nil then + self:playcommand("UpdateVisibilityByDownloadStatus") + elseif bundle ~= nil then + self:diffuse(COLORS:getDownloaderColor("NotInstalledIcon")) + self:diffusealpha(isOver(self) and buttonHoverAlpha or 1) + if isOver(self) then toolTipOn("Download Bundle (Mirror)") end + else + self:diffusealpha(0) + end + end, + UpdateVisibilityByDownloadStatusCommand = function(self) + if pack ~= nil then + -- hide mirror DL if literally the same link + if pack:GetURL() == pack:GetMirror() then + self:diffusealpha(0) + return + end + + local name = pack:GetName() + if SONGMAN:DoesSongGroupExist(name) then + -- the pack is already installed + self:diffuse(COLORS:getDownloaderColor("InstalledIcon")) + self:diffusealpha(1) + if isOver(self) then toolTipOn("Already Installed") end + elseif downloadingPacksByName[name] ~= nil or queuedPacksByName[name] ~= nil then + -- the pack is downloading or queued + self:diffusealpha(0) + else + self:diffuse(COLORS:getDownloaderColor("NotInstalledIcon")) + self:diffusealpha(isOver(self) and buttonHoverAlpha or 1) + if isOver(self) then toolTipOn("Download Pack (Mirror)") end + end + end + end, + MouseDownCommand = function(self) + if self:IsInvisible() then return end + if pack ~= nil then + local name = pack:GetName() + if downloadingPacksByName[name] ~= nil or queuedPacksByName[name] ~= nil or SONGMAN:DoesSongGroupExist(name) then + return + elseif bundle ~= nil then + local expanded = i % 2 == 0 and " Expanded" or "" + local name = bundleTypes[index]:lower()..(i%2==0 and "-expanded" or "") + DLMAN:DownloadCoreBundle(name, true) + inBundles = false + page = 1 + end + pack:DownloadAndInstall(true) + self:GetParent():GetParent():playcommand("UpdateItemList") + end + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + if pack ~= nil then + local name = pack:GetName() + if downloadingPacksByName[name] ~= nil then + toolTipOn("Currently Downloading") + elseif queuedPacksByName[name] ~= nil then + toolTipOn("Queued") + elseif SONGMAN:DoesSongGroupExist(name) then + toolTipOn("Already Installed") + else + toolTipOn("Download Pack (Mirror)") + self:diffusealpha(buttonHoverAlpha) + end + elseif bundle ~= nil then + toolTipOn("Download Bundle (Mirror)") + self:diffusealpha(buttonHoverAlpha) + end + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + TOOLTIP:Hide() + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "CancelButton", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local width = actuals.MirrorDLLeftGap - actuals.MainDLLeftGap + actuals.DLIconSize + txt:halign(0):valign(0) + bg:halign(0):valign(0) + + self:x(actuals.MainDLLeftGap) + txt:settext(" ") + txt:zoom(cancelTextSize) + bg:zoomto(width, txt:GetZoomedHeight()) + txt:maxwidth(width / cancelTextSize - textZoomFudge) + + txt:settext(" ") + self:diffusealpha(0) + -- if clicked, cancels download or removes from queue + -- otherwise: + -- is invisible if not queued or in progress + -- says "Queued" if in queue + -- says "Cancel" if downloading + -- invisible if fully installed + registerActorToColorConfigElement(txt, "main", "SecondaryText") + end, + SetPackCommand = function(self) + if pack ~= nil then + self:playcommand("UpdateVisibilityByDownloadStatus") + else + self:diffusealpha(0) + -- button layer management + self:z(-5) + end + end, + UpdateVisibilityByDownloadStatusCommand = function(self) + if pack ~= nil then + local name = pack:GetName() + -- z movement is for button layer management + -- lower z has less priority, is "on bottom" + if SONGMAN:DoesSongGroupExist(name) then + -- the pack is already installed + self:diffusealpha(0) + self:z(-5) + elseif downloadingPacksByName[name] ~= nil then + -- the pack is downloading + self:diffusealpha(isOver(self:GetChild("BG")) and buttonHoverAlpha or 1) + if isOver(self) then TOOLTIP:Hide() end + self:GetChild("Text"):settext("Cancel") + self:z(5) + elseif queuedPacksByName[name] ~= nil then + -- the pack is queued + self:diffusealpha(isOver(self:GetChild("BG")) and buttonHoverAlpha or 1) + if isOver(self) then TOOLTIP:Hide() end + self:GetChild("Text"):settext("Queued") + self:z(5) + else + self:diffusealpha(0) + self:z(-5) + end + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update ~= "OnMouseDown" then return end + if pack ~= nil then + local name = pack:GetName() + if queuedPacksByName[name] then + pack:RemoveFromQueue() + elseif downloadingPacksByName[name] then + downloadingPacksByName[name]:Stop() + else + return + end + self:GetParent():GetParent():playcommand("UpdateItemList") + elseif bundle ~= nil then + return + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + TOOLTIP:Hide() + else + self:diffusealpha(1) + end + end, + } + } + end + + local t = Def.ActorFrame { + Name = "DownloaderInternalFrame", + InitCommand = function(self) + end, + BeginCommand = function(self) + -- make sure we arent filtering anything out on first load + pl:FilterAndSearch("", 0, 0, 0, 0) + downloaderframe = self + self:playcommand("UpdateItemList") + + -- this function will update the downloading pack and queued pack lists + -- pack downloads are sequential but can potentially become concurrent, so the downloading is a table of length 1 + self:SetUpdateFunction(function(self) + if not focused then return end -- dont update if cant see + downloadingPacks = DLMAN:GetDownloadingPacks() + queuedPacks = DLMAN:GetQueuedPacks() + downloadingPacksByName = {} + queuedPacksByName = {} + for _, pack in ipairs(queuedPacks) do + queuedPacksByName[pack:GetName()] = true + end + for _, pack in ipairs(downloadingPacks) do + downloadingPacksByName[pack:GetName()] = pack:GetDownload() + end + self:playcommand("UpdateVisibilityByDownloadStatus") + end) + self:SetUpdateFunctionInterval(0.25) -- slow down updates to quarter second + + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + -- init the input context but start it out false + CONTEXTMAN:RegisterToContextSet(snm, "Downloads", anm) + CONTEXTMAN:ToggleContextSet(snm, "Downloads", false) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if context is set to Downloads, passthrough unless not holding ctrl and a number + -- pressing a number with ctrl should lead to the general tab + -- otherwise, typing numbers is allowed + if CONTEXTMAN:CheckContextSet(snm, "Downloads") then + if inBundles then return end + if event.type ~= "InputEventType_Release" then + local btn = event.DeviceInput.button + local gbtn = event.button + if btn == "DeviceButton_escape" then + -- shortcut to exit back to general + MESSAGEMAN:Broadcast("GeneralTabSet") + else + local del = btn == "DeviceButton_delete" + local bs = btn == "DeviceButton_backspace" + local char = inputToCharacter(event) + local up = gbtn == "MenuUp" or gbtn == "Up" + local down = gbtn == "MenuDown" or gbtn == "Down" + local ctrl = INPUTFILTER:IsControlPressed() + local copypasta = btn == "DeviceButton_v" and ctrl + + -- if ctrl is pressed with a number, let the general tab input handler deal with this + if char ~= nil and tonumber(char) and INPUTFILTER:IsControlPressed() then + return + end + + -- paste + if copypasta then + char = Arch.getClipboard() + end + + local searchb4 = searchstring + + if bs then + searchstring = searchstring:sub(1, -2) + elseif del then + searchstring = "" + elseif char ~= nil then + searchstring = searchstring .. char + elseif up then + -- up move the page up + movePage(-1) + elseif down then + -- down move the page down + movePage(1) + else + if char == nil then return end + end + + -- reset page if search changed + if searchb4 ~= searchstring then + page = 1 + end + self:playcommand("UpdateSearch") + self:playcommand("UpdateItemList") + end + end + end + + end) + end, + UpdateSearchCommand = function(self) + pl:FilterAndSearch(searchstring, 0, 0, 0, 0) + end, + UpdateItemListCommand = function(self) + TOOLTIP:Hide() + if not inBundles then + packlisting = pl:GetPackTable() + maxPage = math.ceil(#packlisting / itemCount) + end + self:playcommand("SetPack") + end, + + Def.ActorFrame { + Name = "PackSearchFrame", + InitCommand = function(self) + self:xy(actuals.SearchBGLeftGap, actuals.TopLipHeight / 2) + end, + UpdateItemListCommand = function(self) + if inBundles then + self:diffusealpha(0) + else + self:diffusealpha(1) + end + end, + + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "searchBar")) .. { + Name = "PackSearchBG", + InitCommand = function(self) + self:halign(0) + self:zoomto(actuals.SearchBGWidth, actuals.SearchBGHeight) + self:diffusealpha(0.35) + end + }, + Def.Sprite { + Name = "PackSearchIcon", + Texture = THEME:GetPathG("", "searchIcon"), + InitCommand = function(self) + self:halign(0) + self:x(actuals.SearchIconLeftGap) + self:zoomto(actuals.SearchIconSize, actuals.SearchIconSize) + registerActorToColorConfigElement(self, "main", "IconColor") + end + }, + LoadFont("Common Normal") .. { + Name = "PackSearchText", + InitCommand = function(self) + self:halign(0) + self:x(actuals.SearchTextLeftGap) + self:zoom(searchTextSize) + self:maxwidth((actuals.SearchBGWidth - actuals.SearchTextLeftGap*1.5) / searchTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateSearchCommand = function(self) + self:settext(searchstring) + end, + } + }, + LoadFont("Common Normal") .. { + Name = "IndexHeader", + InitCommand = function(self) + self:valign(0) + self:xy(actuals.IndexColumnLeftGap / 2, actuals.HeaderLineUpperGap + actuals.TopLipHeight) + self:zoom(indexHeaderSize) + self:maxwidth(actuals.IndexColumnLeftGap / indexHeaderSize - textZoomFudge) + self:settext("#") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "NameHeader", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local width = actuals.MSDColumnLeftGap - actuals.NameColumnLeftGap - actuals.MSDWidth / 2 + self:xy(actuals.NameColumnLeftGap, actuals.HeaderLineUpperGap + actuals.TopLipHeight) + + txt:halign(0):valign(0) + bg:halign(0):valign(0) + txt:zoom(nameHeaderSize) + txt:maxwidth(width / nameHeaderSize - textZoomFudge) + txt:settext("Name") + bg:zoomto(math.max(width/2, txt:GetZoomedWidth()), txt:GetZoomedHeight()) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update ~= "OnMouseDown" then return end + pl:SortByName() + self:GetParent():playcommand("UpdateItemList") + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "AverageHeader", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local width = actuals.MSDWidth + self:xy(actuals.MSDColumnLeftGap, actuals.HeaderLineUpperGap + actuals.TopLipHeight) + + txt:valign(0) + bg:valign(0) + txt:zoom(msdHeaderSize) + txt:maxwidth(width / msdHeaderSize - textZoomFudge) + txt:settext("Avg") + bg:zoomto(width, txt:GetZoomedHeight()) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update ~= "OnMouseDown" then return end + pl:SortByDiff() + self:GetParent():playcommand("UpdateItemList") + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Size Header", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local width = actuals.SizeColumnLeftGap - actuals.MSDColumnLeftGap - actuals.MSDWidth / 2 + self:xy(actuals.SizeHeaderLeftGap, actuals.HeaderLineUpperGap + actuals.TopLipHeight) + + txt:valign(0) + bg:valign(0) + txt:zoom(sizeHeaderSize) + txt:maxwidth(width / sizeHeaderSize - textZoomFudge) + txt:settext("Size") + bg:zoomto(width, txt:GetZoomedHeight()) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update ~= "OnMouseDown" then return end + pl:SortBySize() + self:GetParent():playcommand("UpdateItemList") + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + }, + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:diffusealpha(0) + self:zoomto(actuals.Width, actuals.Height) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end + }, + LoadFont("Common Normal") .. { + Name = "PageText", + InitCommand = function(self) + self:halign(1):valign(0) + self:xy(actuals.Width - actuals.PageTextRightGap, actuals.TopLipHeight + actuals.HeaderLineUpperGap) + self:zoom(pageTextSize) + self:maxwidth((actuals.Width - actuals.SizeColumnLeftGap) / pageTextSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateItemListCommand = function(self) + if inBundles then + self:settext("") + else + local lb = clamp((page-1) * (itemCount) + 1, 0, #packlisting) + local ub = clamp(page * itemCount, 0, #packlisting) + self:settextf("%d-%d/%d", lb, ub, #packlisting) + end + end + }, + } + + for i = 1, itemCount do + t[#t+1] = listItem(i) + end + + t[#t+1] = tabChoices() + + return t +end + +t[#t+1] = downloadsList() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/main.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/main.lua new file mode 100644 index 0000000000..0c1e459fbc --- /dev/null +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/main.lua @@ -0,0 +1,1102 @@ +local openedGroup = "" +local t = Def.ActorFrame { + Name = "PlayerInfoFrame", + BeginCommand = function(self) + if not DLMAN:IsLoggedIn() then + local username = playerConfig:get_data().UserName + local token = playerConfig:get_data().PasswordToken + if username ~= nil and #username > 0 then + if token ~= nil and #token > 0 then + DLMAN:LoginWithToken(username, token) + end + end + end + end, + LoginMessageCommand = function(self) + self:playcommand("Set") + ms.ok("Login Successful") + + playerConfig:get_data().UserName = DLMAN:GetUsername() + playerConfig:get_data().PasswordToken = DLMAN:GetToken() + playerConfig:set_dirty() + playerConfig:save() + end, + LogOutMessageCommand = function(self) + self:playcommand("Set") + ms.ok("Logged out") + + playerConfig:get_data().UserName = "" + playerConfig:get_data().PasswordToken = "" + playerConfig:set_dirty() + playerConfig:save() + end, + LoginFailedMessageCommand = function(self) + self:playcommand("Set") + ms.ok("Login Failed") + + playerConfig:get_data().UserName = "" + playerConfig:get_data().PasswordToken = "" + playerConfig:set_dirty() + playerConfig:save() + end, + OnlineUpdateMessageCommand = function(self) + self:playcommand("Set") + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Music Wheel Position" then + self:playcommand("UpdateWheelPosition") + end + end, + OpenedGroupMessageCommand = function(self, params) + openedGroup = params.group + end, + ClosedGroupMessageCommand = function(self) + openedGroup = "" + end, +} + +local visEnabled = Var("visualizer") +local loadingScreenName = Var("screen") + +local ratios = { + Height = 109 / 1080, -- frame height + Width = 1, -- full screen width + AvatarWidth = 109 / 1080, -- this should end up square + ConnectionLogoRightGap = 0 / 1080, -- logo position relative to the right edge of avatar (height based for square) + ConnectionLogoBottomGap = 0 / 1080, -- same as above + ConnectionLogoSize = 36 / 1080, -- this is 36x36 + LeftTextLeftGap = 8 / 1920, -- this is after the avatar + LeftTextTopGap1 = 24 / 1080, -- from top to center of line 1 + LeftTextTopGap2 = 49 / 1080, -- from top to center of line 2 + LeftTextTopGap3 = 72 / 1080, -- from top to center of line 3 + LeftTextTopGap4 = 95 / 1080, -- from top to center of line 4 + RightTextLeftGap = 412 / 1920, -- this is from avatar to right text + RightTextTopGap1 = 21 / 1080, -- why did this have to be different from Left line 1 + RightTextTopGap2 = 54 / 1080, -- from top to center of line 2 + RightTextTopGap3 = 89 / 1080, -- from top to center of line 3 + VisualizerLeftGap = 707 / 1920, -- from left side of screen to leftmost bin + VisualizerWidth = 693 / 1920, + + RatingEdgeToVisualizerBuffer = 32 / 1920, + RatingSideBuffer = 25 / 1920, -- an area of buffer to the left and right of the player rating text + + IconUpperGap = 36 / 1080, + IconExitWidth = 47 / 1920, + IconExitHeight = 36 / 1080, + IconExitRightGap = 38 / 1920, -- from right side of screen to right end of icon + IconSettingsWidth = 44 / 1920, + IconSettingsHeight = 35 / 1080, + IconSettingsRightGap = 123 / 1920, + IconHelpWidth = 36 / 1920, + IconHelpHeight = 36 / 1080, + IconHelpRightGap = 205 / 1920, + IconDownloadsWidth = 51 / 1920, + IconDownloadsHeight = 36 / 1080, + IconDownloadsRightGap = 278 / 1920, + IconDownloadsProgressBar1UpperGap = 51 / 1080, -- top of icon to top of bar + IconDownloadsProgressBarHeight = 9 / 1080, + IconDownloadsProgressBarWidth = 88 / 1920, + IconRandomWidth = 41 / 1920, + IconRandomHeight = 36 / 1080, + IconRandomRightGap = 367 / 1920, + IconSearchWidth = 36 / 1920, + IconSearchHeight = 36 / 1080, + IconSearchRightGap = 446 / 1920, +} + +local actuals = { + Height = ratios.Height * SCREEN_HEIGHT, + Width = ratios.Width * SCREEN_WIDTH, + AvatarWidth = ratios.AvatarWidth * SCREEN_HEIGHT, + ConnectionLogoRightGap = ratios.ConnectionLogoRightGap * SCREEN_HEIGHT, + ConnectionLogoBottomGap = ratios.ConnectionLogoBottomGap * SCREEN_HEIGHT, + ConnectionLogoSize = ratios.ConnectionLogoSize * SCREEN_HEIGHT, + LeftTextLeftGap = ratios.LeftTextLeftGap * SCREEN_WIDTH, + LeftTextTopGap1 = ratios.LeftTextTopGap1 * SCREEN_HEIGHT, + LeftTextTopGap2 = ratios.LeftTextTopGap2 * SCREEN_HEIGHT, + LeftTextTopGap3 = ratios.LeftTextTopGap3 * SCREEN_HEIGHT, + LeftTextTopGap4 = ratios.LeftTextTopGap4 * SCREEN_HEIGHT, + RightTextLeftGap = ratios.RightTextLeftGap * SCREEN_WIDTH, + RightTextTopGap1 = ratios.RightTextTopGap1 * SCREEN_HEIGHT, + RightTextTopGap2 = ratios.RightTextTopGap2 * SCREEN_HEIGHT, + RightTextTopGap3 = ratios.RightTextTopGap3 * SCREEN_HEIGHT, + VisualizerLeftGap = ratios.VisualizerLeftGap * SCREEN_WIDTH, + VisualizerWidth = ratios.VisualizerWidth * SCREEN_WIDTH, + RatingEdgeToVisualizerBuffer = ratios.RatingEdgeToVisualizerBuffer * SCREEN_WIDTH, + RatingSideBuffer = ratios.RatingSideBuffer * SCREEN_WIDTH, + IconUpperGap = ratios.IconUpperGap * SCREEN_HEIGHT, + IconExitWidth = ratios.IconExitWidth * SCREEN_WIDTH, + IconExitHeight = ratios.IconExitHeight * SCREEN_HEIGHT, + IconExitRightGap = ratios.IconExitRightGap * SCREEN_WIDTH, + IconSettingsWidth = ratios.IconSettingsWidth * SCREEN_WIDTH, + IconSettingsHeight = ratios.IconSettingsHeight * SCREEN_HEIGHT, + IconSettingsRightGap = ratios.IconSettingsRightGap * SCREEN_WIDTH, + IconHelpWidth = ratios.IconHelpWidth * SCREEN_WIDTH, + IconHelpHeight = ratios.IconHelpHeight * SCREEN_HEIGHT, + IconHelpRightGap = ratios.IconHelpRightGap * SCREEN_WIDTH, + IconDownloadsWidth = ratios.IconDownloadsWidth * SCREEN_WIDTH, + IconDownloadsHeight = ratios.IconDownloadsHeight * SCREEN_HEIGHT, + IconDownloadsRightGap = ratios.IconDownloadsRightGap * SCREEN_WIDTH, + IconDownloadsProgressBar1UpperGap = ratios.IconDownloadsProgressBar1UpperGap * SCREEN_HEIGHT, + IconDownloadsProgressBarHeight = ratios.IconDownloadsProgressBarHeight * SCREEN_HEIGHT, + IconDownloadsProgressBarWidth = ratios.IconDownloadsProgressBarWidth * SCREEN_WIDTH, + IconRandomWidth = ratios.IconRandomWidth * SCREEN_WIDTH, + IconRandomHeight = ratios.IconRandomHeight * SCREEN_HEIGHT, + IconRandomRightGap = ratios.IconRandomRightGap * SCREEN_WIDTH, + IconSearchWidth = ratios.IconSearchWidth * SCREEN_WIDTH, + IconSearchHeight = ratios.IconSearchHeight * SCREEN_HEIGHT, + IconSearchRightGap = ratios.IconSearchRightGap * SCREEN_WIDTH, +} + +-- the list of buttons and the lists of screens those buttons are allowed on +-- if "All" is listed, the button is always active +local screensAllowedForButtons = { + Exit = { + All = true, + }, + Settings = { + ScreenSelectMusic = true, + }, + Help = { + ScreenSelectMusic = true, + }, + Downloads = { + ScreenSelectMusic = true, + }, + Random = { + ScreenSelectMusic = true, + }, + Search = { + ScreenSelectMusic = true, + }, + AssetSettings = { + ScreenSelectMusic = true, + }, +} + +-- find out if a button from the above list is selectable based on the current screen +-- wont work on Init, only when the screen exists (at or after BeginCommand) +local function selectable(name) + local screen = loadingScreenName + return screensAllowedForButtons[name]["All"] ~= nil or screensAllowedForButtons[name][screen] +end + +local disabledButtonAlpha = 0.4 +local hoverAlpha = 0.6 +local visualizerBins = 126 +local leftTextBigSize = 0.7 +local leftTextSmallSize = 0.65 +local rightTextSize = 0.7 +local textzoomFudge = 5 -- for gaps in maxwidth +-- a controllable hack to give more girth to the rating text (RightText) on smaller aspect ratios +-- should push the visualizer further right and make less problems +local textzoomBudge = 25 + +local profile = GetPlayerOrMachineProfile(PLAYER_1) +local pcount = SCOREMAN:GetTotalNumberOfScores() +local parrows = profile:GetTotalTapsAndHolds() +local strparrows = shortenIfOver1Mil(parrows) +local ptime = profile:GetTotalSessionSeconds() +local username = "" +local redir = false -- tell whether or not redirected input is on for the login prompt stuff + +local downloadsProgress1BGAlpha = 0.4 +local downloadsProgress1Alpha = 1 +local downloadsProgress2BGAlpha = 0.4 +local downloadsProgress2Alpha = 1 + +-- this does not include the exit button +-- from left to right starting at 1, the user may press a number while holding to control to activate them +-- assuming context is set and the button is allowed +local iconCountForKeyboardInput = 5 + +-- handle logging in +local function beginLoginProcess(self) + redir = SCREENMAN:get_input_redirected(PLAYER_1) + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + off() + + username = "" + + -- this sets up 2 text entry windows to pull your username and pass + -- if you press escape or just enter nothing, you are forced out + -- input redirects are controlled here because we want to be careful not to break any prior redirects + askForInputStringWithFunction( + "Enter Username", + 255, + false, + function(answer) + username = answer + -- moving on to step 2 if the answer isnt blank + if answer:gsub("^%s*(.-)%s*$", "%1") ~= "" then + self:sleep(0.01):queuecommand("LoginStep2") + else + ms.ok("Login cancelled") + on() + end + end, + function() return true, "" end, + function() + on() + ms.ok("Login cancelled") + end + ) +end + +-- do not use this function outside of first calling beginLoginProcess +local function loginStep2() + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + -- try to keep the scope of password here + -- if we open up the scope, if a lua error happens on this screen + -- the password may show up in the error message + local password = "" + askForInputStringWithFunction( + "Enter Password", + 255, + true, + function(answer) + password = answer + -- log in if not blank + if answer:gsub("^%s*(.-)%s*$", "%1") ~= "" then + DLMAN:Login(username, password) + else + ms.ok("Login cancelled") + end + on() + end, + function() return true, "" end, + function() + on() + ms.ok("Login cancelled") + end + ) +end + + +t[#t+1] = Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.8) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end +} + +t[#t+1] = UIElements.SpriteButton(1, 1, nil) .. { + Name = "Avatar", + InitCommand = function(self) + self:halign(0):valign(0) + end, + BeginCommand = function(self) + self:playcommand("Set") + end, + AvatarChangedMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + self:Load(getAvatarPath(PLAYER_1)) + self:zoomto(actuals.AvatarWidth, actuals.AvatarWidth) + end, + MouseOverCommand = function(self) + if selectable("AssetSettings") then + self:diffusealpha(hoverAlpha) + end + end, + MouseOutCommand = function(self) + if selectable("AssetSettings") then + self:diffusealpha(1) + end + end, + InvokeCommand = function(self) + if selectable("AssetSettings") then + -- if clicking or otherwise invoking this twice, just toggle back to generalBox + if CONTEXTMAN:CheckContextSet(SCREENMAN:GetTopScreen():GetName(), "AssetSettings") then + MESSAGEMAN:Broadcast("GeneralTabSet") + else + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "AssetSettings", prevScreen = "General"}) + end + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end +} + +t[#t+1] = UIElements.SpriteButton(1, 1, nil) .. { + Name = "ConnectionSprite", + InitCommand = function(self) + self:halign(1):valign(1) + -- position relative to the bottom right corner of the avatar + self:xy(actuals.AvatarWidth - actuals.ConnectionLogoRightGap, actuals.AvatarWidth - actuals.ConnectionLogoBottomGap) + self:playcommand("Set") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + self:Load(THEME:GetPathG("", "loggedin")) + else + self:Load(THEME:GetPathG("", "loggedout")) + end + self:zoomto(actuals.ConnectionLogoSize, actuals.ConnectionLogoSize) + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + if DLMAN:IsLoggedIn() then + TOOLTIP:SetText("Log out") + else + TOOLTIP:SetText("Log in") + end + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + TOOLTIP:Hide() + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + TOOLTIP:Hide() + if DLMAN:IsLoggedIn() then + DLMAN:Logout() + else + beginLoginProcess(self) + end + end + end, + LoginStep2Command = function(self) + loginStep2() + end +} + +t[#t+1] = Def.ActorFrame { + Name = "LeftText", + InitCommand = function(self) + self:x(actuals.AvatarWidth + actuals.LeftTextLeftGap) + end, + + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "NameRank", + InitCommand = function(self) + self:y(actuals.LeftTextTopGap1) + self:halign(0) + self:zoom(leftTextBigSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.LeftTextLeftGap) / leftTextBigSize - textzoomFudge) + self:playcommand("Set") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + local pn = DLMAN:GetUsername() + self:settextf("%s (#%d)", pn, DLMAN:GetSkillsetRank("Overall")) + else + self:settext(profile:GetDisplayName()) + end + end, + ProfileRenamedMessageCommand = function(self) + self:playcommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + renameProfileDialogue(profile) + end + end, + }, + LoadFont("Common Normal") .. { + Name = "Playcount", + InitCommand = function(self) + self:y(actuals.LeftTextTopGap2) + self:halign(0) + self:zoom(leftTextSmallSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.LeftTextLeftGap) / leftTextSmallSize - textzoomFudge) + self:settextf("%d plays", pcount) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "Arrows", + InitCommand = function(self) + self:y(actuals.LeftTextTopGap3) + self:halign(0) + self:zoom(leftTextSmallSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.LeftTextLeftGap) / leftTextSmallSize - textzoomFudge) + self:settextf("%s arrows smashed", strparrows) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:SetText(parrows) + TOOLTIP:Show() + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + TOOLTIP:Hide() + end, + }, + LoadFont("Common Normal") .. { + Name = "Playtime", + InitCommand = function(self) + self:y(actuals.LeftTextTopGap4) + self:halign(0) + self:zoom(leftTextSmallSize) + self:maxwidth((actuals.RightTextLeftGap - actuals.LeftTextLeftGap) / leftTextSmallSize - textzoomFudge) + self:settextf("%s playtime", SecondsToHHMMSS(ptime)) + registerActorToColorConfigElement(self, "main", "SecondaryText") + end + } +} + +t[#t+1] = Def.ActorFrame { + Name = "RightText", + InitCommand = function(self) + self:x(actuals.AvatarWidth + actuals.RightTextLeftGap) + end, + BeginCommand = function(self) + local lt = self:GetParent():GetChild("LeftText") + local longestWidth = getLargestChildWidth(lt) + self:x(actuals.AvatarWidth + longestWidth + actuals.RatingSideBuffer) + end, + + LoadFont("Common Normal") .. { + Name = "Header", + InitCommand = function(self) + self:y(actuals.RightTextTopGap1) + self:halign(0) + self:zoom(rightTextSize) + self:maxwidth((actuals.VisualizerLeftGap - actuals.RightTextLeftGap - actuals.AvatarWidth) / rightTextSize + textzoomBudge) + self:playcommand("Set") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + self:settext("Player Ratings:") + else + self:settext("Player Rating:") + end + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "OfflineRating", + InitCommand = function(self) + self:y(actuals.RightTextTopGap2) + self:halign(0) + self:zoom(rightTextSize) + self:maxwidth((actuals.VisualizerLeftGap - actuals.RightTextLeftGap - actuals.AvatarWidth) / rightTextSize + textzoomBudge) + self:playcommand("Set") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetCommand = function(self) + local offlinerating = profile:GetPlayerRating() + if DLMAN:IsLoggedIn() then + self:settextf("Offline - %5.2f", offlinerating) + else + self:settextf("%5.2f", offlinerating) + end + end, + PlayerRatingUpdatedMessageCommand = function(self) + self:playcommand("Set") + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + MESSAGEMAN:Broadcast("GeneralTabSet", {tab = SCUFF.profiletabindex}) + end + end, + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "OnlineRating", + InitCommand = function(self) + self:y(actuals.RightTextTopGap3) + self:halign(0) + self:zoom(rightTextSize) + self:maxwidth((actuals.VisualizerLeftGap - actuals.RightTextLeftGap - actuals.AvatarWidth) / rightTextSize + textzoomBudge) + self:playcommand("Set") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + SetCommand = function(self) + if DLMAN:IsLoggedIn() then + self:settextf("Online - %5.2f", DLMAN:GetSkillsetRating("Overall")) + else + self:settext("") + end + end, + MouseOverCommand = function(self) + self:diffusealpha(hoverAlpha) + end, + MouseOutCommand = function(self) + self:diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + MESSAGEMAN:Broadcast("GeneralTabSet", {tab = SCUFF.profiletabindex}) + end + end, + }, +} + +t[#t+1] = Def.ActorFrame { + Name = "Icons", + InitCommand = function(self) + self:xy(SCREEN_WIDTH, actuals.IconUpperGap) + end, + BeginCommand = function(self) + local tscr = SCREENMAN:GetTopScreen() + local snm = tscr:GetName() + local anm = self:GetName() + -- this keeps track of whether or not the user is allowed to use the keyboard to change tabs + CONTEXTMAN:RegisterToContextSet(snm, "Main1", anm) + + -- timing out the button combo to go into asset settings + local inputqueue = {} + local comboTimeout = nil + local comboTimeoutSeconds = 1 + + local function clearTimeout() + if comboTimeout ~= nil then + tscr:clearInterval(comboTimeout) + comboTimeout = nil + end + end + + local function resetTimeout() + clearTimeout() + -- we use setInterval because setTimeout doesnt do what is needed + comboTimeout = tscr:setInterval(function() + inputqueue = {} + clearTimeout() + end, + comboTimeoutSeconds) + end + + -- enable the possibility to press the keyboard to switch tabs + tscr:AddInputCallback(function(event) + if event.type ~= "InputEventType_FirstPress" then return end + -- this list of contexts are meant to be contexts of the PlayerInfo Frame + -- full numeric input is allowed on these tabs and you must hold CTRL+Number to get out of them + -- the same way you would get into them + local contextsToCtrlOutOf = { + "Search", + "Downloads", + "Settings", + "AssetSettings", + } + local ctxBypasses = false + for _, ctx in ipairs(contextsToCtrlOutOf) do + if CONTEXTMAN:CheckContextSet(snm, ctx) then ctxBypasses = true break end + end + + resetTimeout() + inputqueue[#inputqueue+1] = event.GameButton + if #inputqueue > 2 then + inputqueue[1] = inputqueue[2] + inputqueue[2] = inputqueue[3] + inputqueue[3] = nil + end + -- / / opens the sort menu + if inputqueue[1] == "Select" and inputqueue[2] == "Select" then + -- open asset settings + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "AssetSettings", prevScreen = "General"}) + inputqueue = {} + return + end + + local ctrl = INPUTFILTER:IsControlPressed() + -- login logout shortcut + if ctrl and event.DeviceInput.button == "DeviceButton_l" then + if not DLMAN:IsLoggedIn() then + beginLoginProcess(self) + else + DLMAN:Logout() + end + end + + -- allowing ctrl+n to go back to general tabs + if ctxBypasses then + if event.char and tonumber(event.char) and INPUTFILTER:IsControlPressed() then + local n = tonumber(event.char) + if n == 0 then n = 10 end + if n >= 1 and n <= SCUFF.generaltabcount then + MESSAGEMAN:Broadcast("GeneralTabSet", {tab = n}) + end + end + return + end + + -- if locked out, dont allow + if not CONTEXTMAN:CheckContextSet(snm, "Main1") then return end + -- must be a number with control held down + if event.char and tonumber(event.char) and INPUTFILTER:IsControlPressed() then + local n = tonumber(event.char) + if n == 0 then n = 10 end + if n >= 1 and n <= iconCountForKeyboardInput then + local childToInvoke = nil + if n == 1 then + childToInvoke = self:GetChild("Search") + elseif n == 2 then + childToInvoke = self:GetChild("Random") + elseif n == 3 then + childToInvoke = self:GetChild("Downloads") + elseif n == 4 then + childToInvoke = self:GetChild("Help") + elseif n == 5 then + childToInvoke = self:GetChild("Settings") + end + if childToInvoke ~= nil then + childToInvoke:playcommand("Invoke") + end + end + elseif event.DeviceInput.button == "DeviceButton_F1" then + -- im making a single exception that F1 alone invokes Search + -- for convenience purposes + self:GetChild("Search"):playcommand("Invoke") + end + end) + end, + LoginStep2Command = function(self) + loginStep2() + end, + + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "exit")) .. { + Name = "Exit", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(-actuals.IconExitRightGap) + self:zoomto(actuals.IconExitWidth, actuals.IconExitHeight) + self:diffusealpha(disabledButtonAlpha) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self) + if selectable(self:GetName()) then + TOOLTIP:Hide() + SCREENMAN:set_input_redirected(PLAYER_1, false) + SCREENMAN:GetTopScreen():Cancel() + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "settings")) .. { + Name = "Settings", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(-actuals.IconSettingsRightGap) + self:zoomto(actuals.IconSettingsWidth, actuals.IconSettingsHeight) + self:diffusealpha(disabledButtonAlpha) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self) + if selectable(self:GetName()) then + TOOLTIP:Hide() + -- if clicking or otherwise invoking this twice, just toggle back to generalBox + if CONTEXTMAN:CheckContextSet(SCREENMAN:GetTopScreen():GetName(), "Settings") then + MESSAGEMAN:Broadcast("GeneralTabSet") + else + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + end + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "gameinfoandhelp")) .. { + Name = "Help", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(-actuals.IconHelpRightGap) + self:zoomto(actuals.IconHelpWidth, actuals.IconHelpHeight) + self:diffusealpha(disabledButtonAlpha) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self) + if selectable(self:GetName()) then + TOOLTIP:Hide() + SCUFF.helpmenuBackout = SCREENMAN:GetTopScreen():GetName() + SCREENMAN:SetNewScreen("ScreenHelpMenu") + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end + }, + Def.ActorFrame { + Name = "Downloads", + InitCommand = function(self) + self:x(-actuals.IconDownloadsRightGap) + self:diffusealpha(disabledButtonAlpha) + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "packdownloads")) .. { + Name = "Downloads", + InitCommand = function(self) + self:halign(1):valign(0) + self:zoomto(actuals.IconDownloadsWidth, actuals.IconDownloadsHeight) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self) + if selectable(self:GetName()) then + TOOLTIP:Hide() + -- if clicking or otherwise invoking this twice, just toggle back to generalBox + if CONTEXTMAN:CheckContextSet(SCREENMAN:GetTopScreen():GetName(), "Downloads") then + MESSAGEMAN:Broadcast("GeneralTabSet") + else + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Downloads"}) + end + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end + }, + UIElements.QuadButton(1, 1) .. { + Name = "Progress1BG", + InitCommand = function(self) + self:valign(0) + self:x(-actuals.IconDownloadsWidth/2) + self:y(actuals.IconDownloadsProgressBar1UpperGap) + self:zoomto(actuals.IconDownloadsProgressBarWidth, actuals.IconDownloadsProgressBarHeight) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "downloader", "ProgressBarBackground") + end, + ToolTipCommand = function(self) + if isOver(self) then + local dlpacks = DLMAN:GetDownloadingPacks() + local qpacks = DLMAN:GetQueuedPacks() + + local result = {} + for i,p in ipairs(dlpacks) do + result[#result+1] = "Downloading: " .. p:GetName() + end + for i,p in ipairs(qpacks) do + result[#result+1] = "Queued: " .. p:GetName() + end + if #result > 0 then + local ttstr = table.concat(result, "\n") + TOOLTIP:SetText(ttstr) + TOOLTIP:Show() + end + else + TOOLTIP:Hide() + end + end, + MouseOverCommand = function(self) + self:playcommand("ToolTip") + end, + MouseOutCommand = function(self) + self:playcommand("ToolTip") + end, + DLProgressAndQueueUpdateMessageCommand = function(self) + if isOver(self) then + self:playcommand("ToolTip") + end + local dls = DLMAN:GetDownloads() + if #dls > 0 then + self:diffusealpha(downloadsProgress1BGAlpha) + else + self:diffusealpha(0) + end + end, + AllDownloadsCompletedMessageCommand = function(self) + if isOver(self) then + self:playcommand("ToolTip") + end + self:diffusealpha(0) + end + }, + Def.Quad { + Name = "Progress1Progress", + InitCommand = function(self) + self:halign(0):valign(0) + self:x(-actuals.IconDownloadsWidth/2 - actuals.IconDownloadsProgressBarWidth/2) + self:y(actuals.IconDownloadsProgressBar1UpperGap) + self:zoomto(actuals.IconDownloadsProgressBarWidth, actuals.IconDownloadsProgressBarHeight) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "downloader", "ProgressBarFill") + end, + DLProgressAndQueueUpdateMessageCommand = function(self) + local dls = DLMAN:GetDownloads() + if #dls > 0 then + self:diffusealpha(downloadsProgress1Alpha) + local progress = dls[1]:GetKBDownloaded() + local size = dls[1]:GetTotalKB() + local perc = progress / size + self:zoomx(actuals.IconDownloadsProgressBarWidth * perc) + else + self:diffusealpha(0) + end + end, + AllDownloadsCompletedMessageCommand = function(self) + self:diffusealpha(0) + end + } + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "random")) .. { + Name = "Random", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(-actuals.IconRandomRightGap) + self:zoomto(actuals.IconRandomWidth, actuals.IconRandomHeight) + self:diffusealpha(disabledButtonAlpha) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self, params) + if selectable(self:GetName()) then + TOOLTIP:Hide() + local scr = SCREENMAN:GetTopScreen() + if not params or params and params.event == "DeviceButton_left mouse button" then + -- full random + local group = WHEELDATA:GetRandomFolder() + local song = WHEELDATA:GetRandomSongInFolder(group) + scr:GetChild("WheelFile"):playcommand("FindSong", {song = song}) + else + if openedGroup ~= nil and #openedGroup > 0 then + -- random song in group + local song = WHEELDATA:GetRandomSongInFolder(openedGroup) + scr:GetChild("WheelFile"):playcommand("FindSong", {song = song, group = openedGroup}) + else + -- when not in any group, get a random group + local group = WHEELDATA:GetRandomFolder() + scr:GetChild("WheelFile"):playcommand("FindGroup", {group = group}) + end + end + end + end, + HoverWheelHeaderMessageCommand = function(self, params) + if params and params.on then + self:diffusealpha(hoverAlpha) + elseif params and params.off then + self:diffusealpha(1) + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke", params) + end + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "searchIcon")) .. { + Name = "Search", + InitCommand = function(self) + self:halign(1):valign(0) + self:x(-actuals.IconSearchRightGap) + self:zoomto(actuals.IconSearchWidth, actuals.IconSearchHeight) + self:diffusealpha(disabledButtonAlpha) + registerActorToColorConfigElement(self, "main", "IconColor") + end, + OnCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + end + end, + MouseOverCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(hoverAlpha) + TOOLTIP:SetText(self:GetName()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if selectable(self:GetName()) then + self:diffusealpha(1) + TOOLTIP:Hide() + end + end, + InvokeCommand = function(self) + if selectable(self:GetName()) then + TOOLTIP:Hide() + -- if clicking or otherwise invoking this twice, just toggle back to generalBox + if CONTEXTMAN:CheckContextSet(SCREENMAN:GetTopScreen():GetName(), "Search") then + MESSAGEMAN:Broadcast("GeneralTabSet") + else + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Search"}) + end + end + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke") + end + } +} + +-- if off at first, cannot toggle at runtime +-- for fps/compat reasons (this causes random crashes for some people) +-- but if it is on at first, allow toggling visibility +if visEnabled then + local intervals = {0, 10, 26, 48, 60, 92, 120, 140, 240, 400, 800, 1600, 2600, 3500, 4000} + t[#t+1] = audioVisualizer:new { + x = actuals.VisualizerLeftGap, + y = actuals.Height, + width = actuals.VisualizerWidth, + maxHeight = actuals.Height / 1.8, + freqIntervals = audioVisualizer.multiplyIntervals(intervals, 9), + color = color("1,1,1,1"), + onBarUpdate = function(self) + -- hmm + end + } .. { + BeginCommand = function(self) + local rt = self:GetParent():GetChild("RightText") + local x = rt:GetX() + local longestWidth = getLargestChildWidth(rt) + x = x + longestWidth + actuals.RatingEdgeToVisualizerBuffer + local newVisualizerWidth = actuals.VisualizerWidth + (actuals.VisualizerLeftGap - x) + self:x(x) + registerActorToColorConfigElement(self, "main", "Visualizer") + self:playcommand("ResetWidth", {width = newVisualizerWidth}) + end, + OptionUpdatedMessageCommand = function(self, params) + if params and params.name == "Music Visualizer" then + self:visible(params.choiceName == "On") + end + end, + } +end + +-- below this point we load things that only work on specific screens +-- buttons that arent meant to function on some screens dont need their intended targets loaded +-- this saves on load time and fps +if selectable("Exit") then + -- nothing, it's just a button +end + +if selectable("Settings") then + t[#t+1] = LoadActor("settings.lua") +end + +if selectable("Help") then + -- nothing, it's just a button +end + +if selectable("Downloads") then + t[#t+1] = LoadActor("downloads.lua") +end + +if selectable("Random") then + -- nothing, it's just a button +end + +if selectable("Search") then + t[#t+1] = LoadActor("searchfilter.lua") +end + +if selectable("AssetSettings") then + t[#t+1] = LoadActor("assetsettings.lua") +end + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua new file mode 100644 index 0000000000..6039bae777 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/searchfilter.lua @@ -0,0 +1,1160 @@ +local ratios = { + Width = 782 / 1920, + Height = 971 / 1080, + TopLipHeight = 44 / 1080, + EdgePadding = 13 / 1920, -- distance from left and right edges for everything + DividerThickness = 2 / 1080, -- consistently 2 pixels basically + SliderThickness = 18 / 1080, + SliderColumnLeftGap = 196 / 1920, -- from left edge of text to left edge of sliders + RightColumnLeftGap = 410 / 1920, -- from left edge of frame to left edge of text + -- using the section height to give equidistant spacing between items with "less" work + UpperSectionHeight = 440 / 1080, -- from bottom of upperlip to top of upper divider + LowerSectionHeight = 485 / 1080, -- from bottom of upper divider to bottom of frame +} + +local actuals = { + Width = ratios.Width * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + TopLipHeight = ratios.TopLipHeight * SCREEN_HEIGHT, + EdgePadding = ratios.EdgePadding * SCREEN_WIDTH, + DividerThickness = ratios.DividerThickness * SCREEN_HEIGHT, + SliderThickness = ratios.SliderThickness * SCREEN_HEIGHT, + SliderColumnLeftGap = ratios.SliderColumnLeftGap * SCREEN_WIDTH, + RightColumnLeftGap = ratios.RightColumnLeftGap * SCREEN_WIDTH, + UpperSectionHeight = ratios.UpperSectionHeight * SCREEN_HEIGHT, + LowerSectionHeight = ratios.LowerSectionHeight * SCREEN_HEIGHT, +} + +local visibleframeX = SCREEN_WIDTH - actuals.Width +local visibleframeY = SCREEN_HEIGHT - actuals.Height +local hiddenframeX = SCREEN_WIDTH +local animationSeconds = 0.1 +local focused = false + +local t = Def.ActorFrame { + Name = "SearchFile", + InitCommand = function(self) + self:playcommand("SetPosition") + self:y(visibleframeY) + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + -- if we ever get this message we need to hide the frame and just exit. + focused = false + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + end, + PlayerInfoFrameTabSetMessageCommand = function(self, params) + if params.tab and params.tab == "Search" then + self:diffusealpha(1) + self:finishtweening() + self:sleep(0.01) + self:queuecommand("FinishFocusing") + self:smooth(animationSeconds) + self:x(visibleframeX) + else + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(hiddenframeX) + focused = false + end + end, + FinishFocusingCommand = function(self) + -- the purpose of this is to delay the act of focusing the screen + -- the reason is that we dont want to trigger Ctrl+1 inputting a 1 on the search field immediately + focused = true + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Search") + end, + SetPositionCommand = function(self) + if getWheelPosition() then + visibleframeX = SCREEN_WIDTH - actuals.Width + hiddenframeX = SCREEN_WIDTH + else + visibleframeX = 0 + hiddenframeX = -actuals.Width + end + if focused then + self:x(visibleframeX) + else + self:x(hiddenframeX) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, +} + + +local textSize = 1 +local textZoomFudge = 5 +local textinputbuffer = 5 -- gap between "Search:" and input text +local buttonHoverAlpha = 0.6 + +-- i made a critical mistake in planning and have to put this here for scoping reasons +local searchentry = {} +local function upperSection() + + -- the base text for each line + local entryTextTable = { + "Any Search", + "Title Search", + "Subtitle Search", + "Artist Search", + "Author Search", + } + + -- used to actually search for things in WheelDataManager + -- get an empty one because we dont want to init with a search entry + searchentry = getEmptyActiveFilterMetadata() + + -- search on the wheel immediately based on the text entered + local function searchNow() + -- Main1 is the name of the Main SelectMusic context group + -- we exit search context after executing a search and set the general box back up + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Main1") + local scr = SCREENMAN:GetTopScreen() + local w = scr:GetChild("WheelFile") + if w ~= nil then + WHEELDATA:SetSearch(searchentry) + w:sleep(0.01):queuecommand("ApplyFilter") + end + MESSAGEMAN:Broadcast("GeneralTabSet") + end + + -- current focused text entry field, 1 is "Any" + -- based on entryTextTable + local focusedField = 1 + + -- text entry into a field causes the searchentry to be updated accordingly + -- based on entryTextTable + local entryFunction = { + -- "Any Search" + function(input) + -- must parse the input for title, subtitle, artist, author + -- same formatting as the old search + local artistpos = input:find("artist=") + local authorpos = input:find("author=") + local mapperpos = input:find("mapper=") + local charterpos = input:find("charter=") + local stepperpos = input:find("stepper=") + local titlepos = input:find("title=") + local subtitlepos = input:find("subtitle=") + + -- because title is a substring of subtitle we have to check to see if the match is incorrect + if titlepos ~= nil and subtitlepos ~= nil and titlepos == subtitlepos + 3 then + titlepos = input:find("title=", titlepos + 1) + end + + local foundartist = "" + local foundauthor = "" + local foundtitle = "" + local foundsubtitle = "" + + if artistpos ~= nil or authorpos ~= nil or titlepos ~= nil or subtitlepos ~= nil or mapperpos ~= nil or charterpos ~= nil or stepperpos ~= nil then + if artistpos ~= nil then + local strend = input:find("[;]", artistpos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundartist = input:sub(artistpos + 7, strend) + end + if authorpos ~= nil then + local strend = input:find("[;]", authorpos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundauthor = input:sub(authorpos + 7, strend) + elseif mapperpos ~= nil then + local strend = input:find("[;]", mapperpos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundauthor = input:sub(mapperpos + 7, strend) + elseif charterpos ~= nil then + local strend = input:find("[;]", charterpos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundauthor = input:sub(charterpos + 8, strend) + elseif stepperpos ~= nil then + local strend = input:find("[;]", stepperpos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundauthor = input:sub(stepperpos + 8, strend) + end + if titlepos ~= nil then + local strend = input:find("[;]", titlepos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundtitle = input:sub(titlepos + 6, strend) + end + if subtitlepos ~= nil then + local strend = input:find("[;]", subtitlepos+1) + if strend == nil then strend = #input else strend = strend-1 end + foundsubtitle = input:sub(subtitlepos + 9, strend) + end + searchentry.Title = foundtitle + searchentry.Subtitle = foundsubtitle + searchentry.Artist = foundartist + searchentry.Author = foundauthor + else + searchentry.Title = input + searchentry.Subtitle = "" + searchentry.Artist = "" + searchentry.Author = "" + end + + -- you know what im just going to update all the other entry fields based on this one + + end, + -- "Title Search" + function(input) + searchentry.Title = input + end, + -- "Subtitle Search" + function(input) + searchentry.Subtitle = input + end, + -- "Artist Search" + function(input) + searchentry.Artist = input + end, + -- "Author Search" + function(input) + searchentry.Author = input + end, + } + + -- move focus of text entry to another line + local function changeFocus(direction) + focusedField = focusedField + direction + if focusedField > #entryTextTable then focusedField = 1 end + if focusedField < 1 then focusedField = #entryTextTable end + MESSAGEMAN:Broadcast("UpdateSearchFocus") + end + + local function textEntryField(i) + return Def.ActorFrame { + Name = "RowFrame_"..i, + InitCommand = function(self) + local numberForSpacingItsLikeTheChoicesButMore = #entryTextTable+1 + local allowedVerticalSpaceForTheseItems = actuals.UpperSectionHeight + self:xy(actuals.EdgePadding, (allowedVerticalSpaceForTheseItems / numberForSpacingItsLikeTheChoicesButMore) * (i-1) + (allowedVerticalSpaceForTheseItems / numberForSpacingItsLikeTheChoicesButMore / 2)) + end, + + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "RowText", + InitCommand = function(self) + self:halign(0) + self:settextf("%s:", entryTextTable[i]) + self:maxwidth((actuals.Width - actuals.EdgePadding * 2) / textSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:GetParent():diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:GetParent():diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + focusedField = i + MESSAGEMAN:Broadcast("UpdateSearchFocus") + end + end, + UpdateSearchFocusMessageCommand = function(self) + if focusedField == i then + self:strokecolor(color("0.6,0.6,0.6,0.75")) + self:diffusealpha(1) + else + self:strokecolor(color("1,1,1,0")) + self:diffusealpha(0.8) + end + end + }, + UIElements.TextToolTip(1, 1, "Common Normal") .. { + Name = "RowInput", + BeginCommand = function(self) + local rowtext = self:GetParent():GetChild("RowText") + self:x(rowtext:GetZoomedWidth() + textinputbuffer) + self:halign(0) + self:maxwidth((actuals.Width - actuals.EdgePadding * 2 - rowtext:GetZoomedWidth() - textinputbuffer) / textSize - textZoomFudge) + self:diffuse(COLORS:getMainColor("SecondaryText")) + self:diffusealpha(1) + end, + ColorConfigUpdatedMessageCommand = function(self) + local ab4 = self:GetDiffuseAlpha() + self:diffuse(COLORS:getMainColor("SecondaryText")) + self:diffusealpha(ab4) + self:playcommand("UpdateSearchFocus") + end, + MouseOverCommand = function(self) + if self:IsInvisible() then return end + self:GetParent():diffusealpha(buttonHoverAlpha) + end, + MouseOutCommand = function(self) + if self:IsInvisible() then return end + self:GetParent():diffusealpha(1) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_left mouse button" then + focusedField = i + MESSAGEMAN:Broadcast("UpdateSearchFocus") + end + end, + UpdateSearchFocusMessageCommand = function(self) + if focusedField == i then + self:strokecolor(Brightness(COLORS:getMainColor("SecondaryText"), 0.85)) + self:diffusealpha(1) + else + self:strokecolor(color("1,1,1,0")) + self:diffusealpha(0.8) + end + end, + InputCommand = function(self, params) + local txt = self:GetText() + if params.backspace then + txt = txt:sub(1, -2) + elseif params.delete then + txt = "" + elseif params.char then + txt = txt .. params.char + end + self:settext(txt) + entryFunction[i](txt) + end, + InvokeCommand = function(self) + searchNow() + end + }, + Def.Quad { -- funny we make this a quad instead of an underscore, right + Name = "CursorUnderscore", + InitCommand = function(self) + self:halign(0) + self:zoomto(10,2) + -- disabled for now + self:visible(false) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + UpdateSearchFocusMessageCommand = function(self) + if focusedField == i then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end + }, + } + end + + local t = Def.ActorFrame { + Name = "UpperSectionFrame", + InitCommand = function(self) + self:y(actuals.TopLipHeight) + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + + local function updateFields() + -- im just gonna.. update all the fields... for your information.... + -- this is ... the ... worst possible way .... but also the best.... + if focusedField == 1 then + self:GetChild("RowFrame_2"):GetChild("RowInput"):settext(searchentry.Title) + self:GetChild("RowFrame_3"):GetChild("RowInput"):settext(searchentry.Subtitle) + self:GetChild("RowFrame_4"):GetChild("RowInput"):settext(searchentry.Artist) + self:GetChild("RowFrame_5"):GetChild("RowInput"):settext(searchentry.Author) + else + -- backwards engineering the any search field + -- for the kids who have big brains and want bigger brains + local finalstr = "" + if searchentry.Title ~= "" or searchentry.Subtitle ~= "" or searchentry.Artist ~= "" or searchentry.Author ~= "" then + if searchentry.Title ~= "" then + finalstr = finalstr .. "title="..searchentry.Title..";" + end + if searchentry.Subtitle ~= "" then + finalstr = finalstr .. "subtitle="..searchentry.Subtitle..";" + end + if searchentry.Artist ~= "" then + finalstr = finalstr .. "artist="..searchentry.Artist..";" + end + if searchentry.Author ~= "" then + finalstr = finalstr .. "author="..searchentry.Author..";" + end + end + self:GetChild("RowFrame_1"):GetChild("RowInput"):settext(finalstr) + end + end + -- update all the search fields + updateFields() + focusedField = 2 + updateFields() + focusedField = 1 + -- it works + + -- init the search input context but start it out false + CONTEXTMAN:RegisterToContextSet(snm, "Search", anm) + CONTEXTMAN:ToggleContextSet(snm, "Search", false) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if context is set to Search, passthrough unless not holding ctrl and a number + -- pressing a number alone should lead to the general tab + if CONTEXTMAN:CheckContextSet(snm, "Search") then + if event.type ~= "InputEventType_Release" then + local btn = event.DeviceInput.button + local shift = INPUTFILTER:IsShiftPressed() + local ctrl = INPUTFILTER:IsControlPressed() + local focusedChild = self:GetChild("RowFrame_"..focusedField) + + if btn == "DeviceButton_enter" or event.button == "Start" then + focusedChild:playcommand("Invoke") + elseif event.type == "InputEventType_FirstPress" and (btn == "DeviceButton_tab" and not shift) or btn == "DeviceButton_down" then + changeFocus(1) + elseif event.type == "InputEventType_FirstPress" and (btn == "DeviceButton_tab" and shift) or btn == "DeviceButton_up" then + changeFocus(-1) + elseif btn == "DeviceButton_escape" then + -- shortcut to escape out of search without searching + MESSAGEMAN:Broadcast("GeneralTabSet") + else + local del = btn == "DeviceButton_delete" + local bs = btn == "DeviceButton_backspace" + local copypasta = btn == "DeviceButton_v" and ctrl + local char = inputToCharacter(event) + + -- if ctrl is pressed with a number, let the general tab input handler deal with this + if char ~= nil and tonumber(char) and INPUTFILTER:IsControlPressed() then + return + end + + -- paste + if copypasta then + char = Arch.getClipboard() + end + + focusedChild:playcommand("Input", {delete = del, backspace = bs, char = char}) + + updateFields() + end + end + end + end) + self:playcommand("UpdateSearchFocus") + end, + PlayerInfoFrameTabSetMessageCommand = function(self, params) + if params.tab and params.tab == "Search" then + if focusedField ~= 1 then + focusedField = 1 + self:playcommand("UpdateSearchFocus") + end + end + end + } + + for i = 1, #entryTextTable do + t[#t+1] = textEntryField(i) + end + + return t +end + +local function lowerSection() + -- putting these functions here to save on space below, less copy pasting, etc + local function onHover(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + end + local function onUnHover(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end + + -- names for each filter line + local filterCategoryTable = { + "Overall", + "Stream", + "Jumpstream", + "Handstream", + "Stamina", + "JackSpeed", + "Chordjacks", + "Technical", + "Length", + "Clear %", + } + + -- defines the bounds for each filter line + -- if a bound is at either limit, it is considered infinite in that direction + -- so a Length filter of 1400,3600 is really >1400 + -- or a Length filter of 0,360 is really <360 + -- etc + local filterCategoryLimits = { + { 0, 40 }, -- Overall + { 0, 40 }, -- Stream + { 0, 40 }, -- Jumpstream + { 0, 40 }, -- Handstream + { 0, 40 }, -- Stamina + { 0, 40 }, -- JackSpeed + { 0, 40 }, -- Chordjacks + { 0, 40 }, -- Technical + { 0, 600 }, -- Length (in seconds) + { 85, 100 }, -- Percent + } + + -- convenience to set the upper and lower bound for a skillset + -- for interacting with the c++ side + local function setSSFilter(ss, lb, ub) + FILTERMAN:SetSSFilter(lb, ss, 0) + FILTERMAN:SetSSFilter(ub, ss, 1) + end + + -- convenience to get the upper and lower bounds for a skillset + -- for interacting with the c++ side + local function getSSFilter(ss) + return FILTERMAN:GetSSFilter(ss, 0), FILTERMAN:GetSSFilter(ss, 1) + end + + -- functions for each filter, what they control + -- each of these filters are range filters, take 2 parameters + -- the third parameter are the limits as determined by the filterCategoryLimits table + -- i know this looks bad but this is just in case we decide to make more filters in the future + -- and possibly separate the behavior for upper/lower bounds in some case + local filterCategoryFunction = { + -- Overall range + function(lb, ub, limits) + setSSFilter(1, lb, ub) + end, + -- Stream range + function(lb, ub, limits) + setSSFilter(2, lb, ub) + end, + -- Jumpstream range + function(lb, ub, limits) + setSSFilter(3, lb, ub) + end, + -- Handstream range + function(lb, ub, limits) + setSSFilter(4, lb, ub) + end, + -- Stamina range + function(lb, ub, limits) + setSSFilter(5, lb, ub) + end, + -- Jackspeed range + function(lb, ub, limits) + setSSFilter(6, lb, ub) + end, + -- Chordjacks range + function(lb, ub, limits) + setSSFilter(7, lb, ub) + end, + -- Tech range + function(lb, ub, limits) + setSSFilter(8, lb, ub) + end, + -- Length range + function(lb, ub, limits) + -- funny enough we put the length filter in the mysterious 9th skillset spot + setSSFilter(9, lb, ub) + end, + -- Clear range + function(lb, ub, limits) + -- and we put the clear filter in the mysterious 10th skillset spot + if lb == limits[1] then + setSSFilter(10, 0, ub) + else + setSSFilter(10, lb, ub) + end + end, + } + + -- functions for each filter, getters for what they control + -- OH NO I COPY PASTED THE ABOVE TABLE THIS JUST GOT A LOT WORSE + local filterCategoryGetters = { + -- Overall range + function() + return getSSFilter(1) + end, + -- Stream range + function() + return getSSFilter(2) + end, + -- Jumpstream range + function() + return getSSFilter(3) + end, + -- Handstream range + function() + return getSSFilter(4) + end, + -- Stamina range + function() + return getSSFilter(5) + end, + -- Jackspeed range + function() + return getSSFilter(6) + end, + -- Chordjacks range + function() + return getSSFilter(7) + end, + -- Tech range + function() + return getSSFilter(8) + end, + -- Length range + function() + -- funny enough we put the length filter in the mysterious 9th skillset spot + return getSSFilter(9) + end, + -- Clear range + function() + -- and we put the clear filter in the mysterious 10th skillset spot + return getSSFilter(10) + end, + } + + local grabbedSlider = nil + + local function filterSlider(i) + -- convenience on convenience yo + local theLimits = filterCategoryLimits[i] + local theSetter = filterCategoryFunction[i] + local theGetter = filterCategoryGetters[i] + local theName = filterCategoryTable[i] + + -- repeated use vars here + local xp = actuals.SliderColumnLeftGap - actuals.EdgePadding + local width = actuals.RightColumnLeftGap - actuals.EdgePadding * 1.5 - actuals.SliderColumnLeftGap + local sliderBGSizeBump = 15/1920 * SCREEN_WIDTH + + -- internal vars + local grabbedDot = nil -- either 0 or 1 for left/right, nil for none + + local function gatherToolTipString() + local lb, ub = theGetter() + -- upper bound of 0 is infinite: display an indeterminant upper bound + if ub == 0 then + return string.format("%s\n%.2f - %.2f+", theName, lb, theLimits[2]) + else + return string.format("%s\n%.2f - %.2f", theName, lb, ub) + end + end + + -- ended up copy pasting this 3 times and took a look and said no thanks + -- this is responsible for setting all the stuff related to clicky movy things + local function draggyEvent(self, params) + local localX = clamp(params.MouseX - xp, 0, width) + local localY = clamp(params.MouseY, 0, actuals.SliderThickness) + + local lo, hi = theGetter() + local lb, ub = theLimits[1], theLimits[2] + local range = ub - lb + -- convert upper 0 to 100% (infinite) + if hi == 0 then hi = ub end + if lo == lb then lo = 0 end + local percentX = localX / width + local leftDotPercent = math.max(0, (lo - lb)) / range + local rightDotPercent = math.min(ub, (hi - lb)) / range + + -- make sure the dot being dragged is not dragged too close to or beyond the other dot + if grabbedDot == 0 then + if percentX > rightDotPercent then + percentX = clamp(rightDotPercent - 0.001, 0, 1) + end + leftDotPercent = percentX + elseif grabbedDot == 1 then + if percentX < leftDotPercent then + percentX = clamp(leftDotPercent + 0.001, 0, 1) + end + rightDotPercent = percentX + else + -- dont know how this could happen, but quit if it does + return + end + + local fLower = lb + (range * leftDotPercent) + local fUpper = lb + (range * rightDotPercent) + -- an upper limit of 100% is meant to be 0 so it can be interpreted as infinite + if fUpper >= ub then fUpper = 0 end + fLower = clamp(fLower, 0, ub) + fUpper = clamp(fUpper, 0, ub) + + theSetter(fLower, fUpper, theLimits) + TOOLTIP:SetText(gatherToolTipString()) + self:GetParent():playcommand("UpdateDots") + end + + return Def.ActorFrame { + Name = "SliderOwnerFrame_"..i, + InitCommand = function(self) + local tblAndOne = #filterCategoryTable + 1 + self:xy(actuals.EdgePadding, (actuals.LowerSectionHeight / tblAndOne) * (i-1) + (actuals.LowerSectionHeight / tblAndOne / 2)) + end, + + LoadFont("Common Normal") .. { + Name = "SliderTitle", + InitCommand = function(self) + self:halign(0) + self:zoom(textSize) + self:maxwidth((actuals.SliderColumnLeftGap - actuals.EdgePadding) / textSize - textZoomFudge) + self:settext(theName) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + }, + Def.ActorFrame { + Name = "SliderFrame", + InitCommand = function(self) + self:x(xp) + end, + + Def.Sprite { + Name = "SliderBG", + Texture = THEME:GetPathG("", "sliderBar"), + InitCommand = function(self) + self:halign(0) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "generalBox", "SliderBackground") + -- we want the bg to actually be just barely larger for reasons + -- visually it makes everything look a lot better + -- because otherwise the dots end up outside the visible bg at the edges + self:zoomto(width + sliderBGSizeBump, actuals.SliderThickness) + self:x(-sliderBGSizeBump/2) + end + }, + UIElements.QuadButton(1, 1) .. { + Name = "SliderButtonArea", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(0) + self:zoomto(width, actuals.SliderThickness) + end, + MouseOverCommand = function(self) + if grabbedSlider == nil then + TOOLTIP:SetText(gatherToolTipString()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if isOver(self:GetParent():GetChild("LowerBound")) then return end + if isOver(self:GetParent():GetChild("UpperBound")) then return end + + if grabbedSlider == nil then + TOOLTIP:Hide() + end + end, + MouseDownCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if grabbedDot == nil then + local localX = clamp(params.MouseX - xp, 0, width) + local localY = clamp(params.MouseY, 0, actuals.SliderThickness) + + local lo, hi = theGetter() + local lb, ub = theLimits[1], theLimits[2] + local range = ub - lb + -- convert upper 0 to 100% (infinite) + if hi == 0 then hi = ub end + local percentX = localX / width + local leftDotPercent = math.max(0, (lo - lb)) / range + local rightDotPercent = math.min(ub, (hi - lb)) / range + + -- set the grabbed dot to the closest dot + if math.abs(percentX - rightDotPercent) < math.abs(percentX - leftDotPercent) then + -- closer to the right dot + grabbedDot = 1 + elseif math.abs(percentX - rightDotPercent) == math.abs(percentX - leftDotPercent) then + -- somehow in the center or the dots are on top of each other + -- pick the closest dot by direction + if percentX > rightDotPercent then + grabbedDot = 1 + elseif percentX < leftDotPercent then + grabbedDot = 0 + end + else + -- closer to the left dot or in the middle + grabbedDot = 0 + end + grabbedSlider = i + end + end, + MouseHoldCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if grabbedDot ~= nil then + draggyEvent(self, params) + end + end, + MouseClickCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + MouseReleaseCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while not on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "Marker")) .. { + Name = "LowerBound", + InitCommand = function(self) + -- we use the hypotenuse of a triangle to find the size of the dot but then make it smaller + local hypotenuse = math.sqrt(2 * (actuals.SliderThickness ^ 2)) / 2 + self:zoomto(hypotenuse, hypotenuse) + self:playcommand("UpdateDots") + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + UpdateDotsCommand = function(self) + local lb, ub = theGetter() + local percentX = clamp((lb - theLimits[1]) / (theLimits[2] - theLimits[1]), 0, 1) + self:x(percentX * width) + end, + MouseDownCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if params.event == "DeviceButton_left mouse button" then + grabbedDot = 0 + grabbedSlider = i + end + end, + MouseHoldCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if grabbedDot ~= nil then + draggyEvent(self, params) + end + end, + MouseOverCommand = function(self) + if grabbedSlider == nil then + TOOLTIP:SetText(gatherToolTipString()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if isOver(self:GetParent():GetChild("SliderButtonArea")) then return end + + if grabbedSlider == nil then + TOOLTIP:Hide() + end + end, + MouseClickCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + MouseReleaseCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while not on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + }, + UIElements.SpriteButton(1, 1, THEME:GetPathG("", "Marker")) .. { + Name = "UpperBound", + InitCommand = function(self) + -- we use the hypotenuse of a triangle to find the size of the dot but then make it smaller + local hypotenuse = math.sqrt(2 * (actuals.SliderThickness ^ 2)) / 2 + self:zoomto(hypotenuse, hypotenuse) + self:playcommand("UpdateDots") + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + UpdateDotsCommand = function(self) + local lb, ub = theGetter() + if ub == 0 then ub = theLimits[2] end + local percentX = clamp((ub - theLimits[1]) / (theLimits[2] - theLimits[1]), 0, 1) + self:x(percentX * width) + end, + MouseDownCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if params.event == "DeviceButton_left mouse button" then + grabbedDot = 1 + grabbedSlider = i + end + end, + MouseHoldCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + + if grabbedDot ~= nil then + draggyEvent(self, params) + end + end, + MouseOverCommand = function(self) + if grabbedSlider == nil then + TOOLTIP:SetText(gatherToolTipString()) + TOOLTIP:Show() + end + end, + MouseOutCommand = function(self) + if isOver(self:GetParent():GetChild("SliderButtonArea")) then return end + if grabbedSlider == nil then + TOOLTIP:Hide() + end + end, + MouseClickCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + MouseReleaseCommand = function(self, params) + if params.event ~= "DeviceButton_left mouse button" then return end + -- for all release events while not on this button (having already pressed it) + grabbedDot = nil + grabbedSlider = nil + if not isOver(self) then + TOOLTIP:Hide() + end + end, + } + + } + } + end + + -- use this function to generate a new line for the right column + -- this column has multiple purposes, so all this function will do is generate the base + -- the base being: they are all BitmapText on a consistent column with consistent spacing + local function filterMiscLine(i) + return UIElements.TextToolTip(1, 1, "Common Normal") .. { + InitCommand = function(self) + -- x pos: right column + -- y pos: a line on the right column based on i (similar math to the Tab system positioning) + local tblAndOne = #filterCategoryTable + 1 + self:halign(0) + self:xy(actuals.RightColumnLeftGap, (actuals.LowerSectionHeight / tblAndOne) * (i-1) + (actuals.LowerSectionHeight / tblAndOne / 2)) + self:maxwidth((actuals.Width - actuals.EdgePadding - actuals.RightColumnLeftGap) / textSize - textZoomFudge) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + } + end + + + local t = Def.ActorFrame { + Name = "LowerSectionFrame", + InitCommand = function(self) + self:y(actuals.TopLipHeight + actuals.UpperSectionHeight + actuals.DividerThickness) + end, + } + + for i = 1, #filterCategoryTable do + t[#t+1] = filterSlider(i) + end + + -- well ... i tried to reduce the code duplication without going stupid + t[#t+1] = filterMiscLine(1) .. { + Name = "MaxRateLine", + InitCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local maxrate = FILTERMAN:GetMaxFilterRate() + self:settextf("Max Rate: %2.1f", maxrate) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self, params) + local maxrate = FILTERMAN:GetMaxFilterRate() + local increment = 0.1 + if params.event == "DeviceButton_left mouse button" then + -- it's already set haha + elseif params.event == "DeviceButton_right mouse button" then + increment = increment * -1 + else + return + end + + maxrate = clamp(clamp(maxrate + increment, FILTERMAN:GetMinFilterRate(), 3), 0.7, 3) + FILTERMAN:SetMaxFilterRate(maxrate) + self:playcommand("UpdateText") + end, + } + + t[#t+1] = filterMiscLine(2) .. { + Name = "MinRateLine", + InitCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local maxrate = FILTERMAN:GetMinFilterRate() + self:settextf("Min Rate: %2.1f", maxrate) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self, params) + local minrate = FILTERMAN:GetMinFilterRate() + local increment = 0.1 + if params.event == "DeviceButton_left mouse button" then + -- it's already set haha + elseif params.event == "DeviceButton_right mouse button" then + increment = increment * -1 + else + return + end + + minrate = clamp(clamp(minrate + increment, 0.7, FILTERMAN:GetMaxFilterRate()), 0.7, 3) + FILTERMAN:SetMinFilterRate(minrate) + self:playcommand("UpdateText") + end, + } + + t[#t+1] = filterMiscLine(3) .. { + Name = "FilterModeLine", + InitCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = FILTERMAN:GetFilterMode() and "AND" or "OR" + self:settextf("Mode: %s", txt) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + FILTERMAN:ToggleFilterMode() + self:playcommand("UpdateText") + end + } + + t[#t+1] = filterMiscLine(4) .. { + Name = "HighestSkillsetOnlyLine", + InitCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = FILTERMAN:GetHighestSkillsetsOnly() and "ON" or "OFF" + self:settextf("Highest Skillset Only: %s", txt) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + FILTERMAN:ToggleHighestSkillsetsOnly() + self:playcommand("UpdateText") + end + } + + t[#t+1] = filterMiscLine(5) .. { + Name = "HighestDifficultyOnlyLine", + InitCommand = function(self) + self:playcommand("UpdateText") + end, + UpdateTextCommand = function(self) + local txt = FILTERMAN:GetHighestDifficultyOnly() and "ON" or "OFF" + self:settextf("Highest Difficulty Only: %s", txt) + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + FILTERMAN:ToggleHighestDifficultyOnly() + self:playcommand("UpdateText") + end + } + + t[#t+1] = filterMiscLine(6) .. { + Name = "MatchCountLine", + UpdateTextCommand = function(self) + local count1 = WHEELDATA:GetFilteredSongCount() + local count2 = WHEELDATA:GetSongCount() + self:settextf("Matches: %d/%d", count1, count2) + end, + FinishedSortMessageCommand = function(self) + self:playcommand("UpdateText") + end + } + + t[#t+1] = filterMiscLine(7) .. { + Name = "ResetLine", + InitCommand = function(self) + self:settext("Reset") + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + FILTERMAN:ResetAllFilters() + self:GetParent():playcommand("UpdateText") + self:GetParent():playcommand("UpdateDots") + end + } + + t[#t+1] = filterMiscLine(8) .. { + Name = "ApplyLine", + InitCommand = function(self) + self:settext("Apply") + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self) + -- really all this does is trigger a search + -- since the filter is always set to what you visually see, you just have to reload the wheel + local scr = SCREENMAN:GetTopScreen() + local w = scr:GetChild("WheelFile") + if w ~= nil then + WHEELDATA:SetSearch(searchentry) + w:sleep(0.01):queuecommand("ApplyFilter") + end + -- but we dont change the input context to keep it from being too jarring + end + } + + return t +end + +t[#t+1] = Def.Quad { + Name = "SearchFilterBGQuad", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end +} + +t[#t+1] = Def.Quad { + Name = "SearchFilterLip", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.Width, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end +} + +t[#t+1] = LoadFont("Common Normal") .. { + Name = "SearchFilterTitle", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight / 2) + self:zoom(textSize) + self:maxwidth(actuals.Width / textSize - textZoomFudge) + self:settext("Search and Filters") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end +} + +t[#t+1] = Def.Quad { + Name = "Divider", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.EdgePadding, actuals.UpperSectionHeight) + self:zoomto(actuals.Width - actuals.EdgePadding * 2, actuals.DividerThickness) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end +} + +t[#t+1] = upperSection() +t[#t+1] = lowerSection() + +return t \ No newline at end of file diff --git a/Themes/Rebirth/BGAnimations/playerInfoFrame/settings.lua b/Themes/Rebirth/BGAnimations/playerInfoFrame/settings.lua new file mode 100644 index 0000000000..2f4a823fa1 --- /dev/null +++ b/Themes/Rebirth/BGAnimations/playerInfoFrame/settings.lua @@ -0,0 +1,7204 @@ +-- every time i look at this file my desire to continue modifying it gets worse +-- at least it isnt totally spaghetti code yet +-- hmm +-- this absolute behemoth of a file ... +-- lol +local ratios = { + RightWidth = 782 / 1920, + LeftWidth = 783 / 1920, + Height = 971 / 1080, + TopLipHeight = 44 / 1080, + BottomLipHeight = 99 / 1080, + + EdgePadding = 12 / 1920, -- distance from edges for text and items + + -- + -- right options + OptionTextWidth = 275 / 1920, -- left edge of text to edge of area for text + OptionTextListTopGap = 21 / 1080, -- bottom of right top lip to top of text + OptionTextBuffer = 7 / 1920, -- distance from end of width to beginning of selection frame + OptionSelectionFrameWidth = 250 / 1920, -- allowed area for option selection + OptionBigTriangleHeight = 19 / 1080, -- visually the width most of the time because the triangles are usually turned + OptionBigTriangleWidth = 20 / 1920, + OptionSmallTriangleHeight = 12 / 1080, + OptionSmallTriangleWidth = 13 / 1920, + OptionSmallTriangleGap = 2 / 1920, + OptionChoiceDirectionGap = 7 / 1920, -- gap between direction arrow pairs and between direction arrows and choices + OptionChoiceAllottedWidth = 450 / 1920, -- width between the arrows for MultiChoices basically (or really long SingleChoices) + OptionChoiceUnderlineThickness = 2 / 1080, + + -- for this area, this is the allowed height for all options including sub options + -- when an option opens, it may only show as many sub options as there are lines after subtracting the amount of option categories + -- so 1 category with 24 sub options has 25 lines + -- 2 categories can then only have up to 23 sub options each to make 25 lines + -- etc + OptionAllottedHeight = 672 / 1080, -- from top of top option to bottom of bottom option + NoteskinDisplayWidth = 240 / 1920, -- width of the text but lets fit the arrows within this + NoteskinDisplayRightGap = 17 / 1920, -- distance from right edge of frame to right edge of display + NoteskinDisplayReceptorTopGap = 29 / 1080, -- bottom of text to top of receptors + NoteskinDisplayTopGap = 21 / 1080, -- bottom of right top lip to top of text + + -- the smaller of these values is used to pick how big the color box is and how tall the sliders are + ColorBoxHeight = 339 / 1080, + ColorBoxWidth = 339 / 1920, + + -- controls the width of the mouse wheel scroll box, should be the same number as the general box X position + -- (found in generalBox.lua) + GeneralBoxLeftGap = 1140 / 1920, -- distance from left edge to the left edge of the general box +} + +local actuals = { + LeftWidth = ratios.LeftWidth * SCREEN_WIDTH, + RightWidth = ratios.RightWidth * SCREEN_WIDTH, + Height = ratios.Height * SCREEN_HEIGHT, + TopLipHeight = ratios.TopLipHeight * SCREEN_HEIGHT, + BottomLipHeight = ratios.BottomLipHeight * SCREEN_HEIGHT, + EdgePadding = ratios.EdgePadding * SCREEN_WIDTH, + OptionTextWidth = ratios.OptionTextWidth * SCREEN_WIDTH, + OptionTextListTopGap = ratios.OptionTextListTopGap * SCREEN_HEIGHT, + OptionTextBuffer = ratios.OptionTextBuffer * SCREEN_WIDTH, + OptionSelectionFrameWidth = ratios.OptionSelectionFrameWidth * SCREEN_WIDTH, + OptionBigTriangleHeight = ratios.OptionBigTriangleHeight * SCREEN_HEIGHT, + OptionBigTriangleWidth = ratios.OptionBigTriangleWidth * SCREEN_WIDTH, + OptionSmallTriangleHeight = ratios.OptionSmallTriangleHeight * SCREEN_HEIGHT, + OptionSmallTriangleWidth = ratios.OptionSmallTriangleWidth * SCREEN_WIDTH, + OptionSmallTriangleGap = ratios.OptionSmallTriangleGap * SCREEN_WIDTH, + OptionChoiceDirectionGap = ratios.OptionChoiceDirectionGap * SCREEN_WIDTH, + OptionChoiceAllottedWidth = ratios.OptionChoiceAllottedWidth * SCREEN_WIDTH, + OptionChoiceUnderlineThickness = ratios.OptionChoiceUnderlineThickness * SCREEN_HEIGHT, + OptionAllottedHeight = ratios.OptionAllottedHeight * SCREEN_HEIGHT, + NoteskinDisplayWidth = ratios.NoteskinDisplayWidth * SCREEN_WIDTH, + NoteskinDisplayRightGap = ratios.NoteskinDisplayRightGap * SCREEN_WIDTH, + NoteskinDisplayReceptorTopGap = ratios.NoteskinDisplayReceptorTopGap * SCREEN_HEIGHT, + NoteskinDisplayTopGap = ratios.NoteskinDisplayTopGap * SCREEN_HEIGHT, + ColorBoxHeight = ratios.ColorBoxHeight * SCREEN_HEIGHT, + ColorBoxWidth = ratios.ColorBoxWidth * SCREEN_WIDTH, + GeneralBoxLeftGap = ratios.GeneralBoxLeftGap * SCREEN_WIDTH, +} + +local visibleframeY = SCREEN_HEIGHT - actuals.Height +local animationSeconds = 0.1 +local focused = false +local lefthidden = false + +local titleTextSize = 0.8 +local explanationTextSize = 0.8 +local textZoomFudge = 5 + +local choiceTextSize = 0.8 +local buttonHoverAlpha = 0.6 +local previewOpenedAlpha = 0.6 +local previewButtonTextSize = 0.8 + +local keyinstructionsTextSize = 0.7 +local bindingChoicesTextSize = 0.75 +local currentlybindingTextSize = 0.7 +local menuBindingTextSize = 0.7 +local colorConfigTextSize = 0.75 +local colorConfigChoiceTextSize = 0.75 + +local optionTitleTextSize = 0.7 +local optionChoiceTextSize = 0.7 +-- for accessibility concerns, make buttons a bit bigger than the text they cover +local textButtonHeightFudgeScalarMultiplier = 1.6 +local optionRowAnimationSeconds = 0.15 +local optionRowQuickAnimationSeconds = 0.07 +-- theoretically this is how long it takes for text to write out when queued by the explanation text +-- but because the game isnt perfect this isnt true at all +-- (but changing this number does make a difference) +local explanationTextWriteAnimationSeconds = 0.2 +-- color config list animation time +local itemListAnimationSeconds = 0.1 + +local maxExplanationTextLines = 2 + +-- lost patience +-- undertaking this was a massive mistake +-- hope you people like it +SCUFF.showingNoteskins = false +SCUFF.showingPreview = false +SCUFF.showingColor = false +SCUFF.showingKeybinds = false + +-- reset customize gameplay here +-- couldnt think of a really good place to put it instead +playerConfig:get_data().CustomizeGameplay = false +playerConfig:set_dirty() +playerConfig:save() + +local t = Def.ActorFrame { + Name = "SettingsFile", + InitCommand = function(self) + self:y(visibleframeY) + self:diffusealpha(0) + end, + GeneralTabSetMessageCommand = function(self, params) + -- if we ever get this message we need to hide the frame and just exit. + focused = false + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:playcommand("HideLeft") + self:playcommand("HideRight") + MESSAGEMAN:Broadcast("ShowWheel") + end, + PlayerInfoFrameTabSetMessageCommand = function(self, params) + if params.tab and params.tab == "Settings" then + -- + -- movement is delegated to the left and right halves + -- right half immediately comes out + -- left half comes out when selecting "Customize Playfield" or "Customize Keybinds" or some appropriate choice + -- + self:diffusealpha(1) + self:finishtweening() + self:sleep(0.01) + self:queuecommand("FinishFocusing") + self:playcommand("ShowRight") + self:playcommand("HideLeft") + MESSAGEMAN:Broadcast("ShowWheel") + else + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:playcommand("HideLeft") + self:playcommand("HideRight") + MESSAGEMAN:Broadcast("ShowWheel") + focused = false + end + end, + FinishFocusingCommand = function(self) + focused = true + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Settings") + end, + ShowSettingsAltMessageCommand = function(self, params) + if params and params.name then + self:playcommand("ShowLeft", params) + else + self:playcommand("HideLeft") + end + end, + OptionCursorUpdatedMessageCommand = function(self, params) + if params and params.name then + -- will only work when hovering certain options + if SCUFF.optionsThatWillOpenTheLeftSideWhenHovered[params.name] ~= nil then + MESSAGEMAN:Broadcast("ShowSettingsAlt", params) + else + -- if moving off of the noteskin tab (without keybinds) + if SCUFF.showingNoteskins and not SCUFF.showingKeybinds then + self:playcommand("HideLeft") + -- HACK HACK HACK HACK HACK + MESSAGEMAN:Broadcast("ShowSettingsAlt") + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Settings") + end + end + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, +} + + +local function leftFrame() + local offscreenX = -actuals.LeftWidth + local onscreenX = 0 + + local t = Def.ActorFrame { + Name = "LeftFrame", + InitCommand = function(self) + self:playcommand("SetPosition") + self:diffusealpha(0) + end, + BeginCommand = function(self) + self:playcommand("HideLeft") + end, + HideLeftCommand = function(self) + -- move off screen left and go invisible + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(offscreenX) + lefthidden = true + end, + ShowLeftCommand = function(self, params) + -- move on screen from left and go visible + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(1) + self:x(onscreenX) + lefthidden = false + end, + SetPositionCommand = function(self) + if getWheelPosition() then + onscreenX = 0 + offscreenX = -actuals.LeftWidth + else + onscreenX = SCREEN_WIDTH - actuals.LeftWidth + offscreenX = SCREEN_WIDTH + end + if lefthidden then + self:x(offscreenX) + else + self:x(onscreenX) + end + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.LeftWidth, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end + }, + Def.Quad { + Name = "TopLip", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.LeftWidth, actuals.TopLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end + }, + LoadFont("Common Normal") .. { + Name = "HeaderText", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight / 2) + self:zoom(titleTextSize) + self:maxwidth((actuals.LeftWidth - actuals.EdgePadding*2) / titleTextSize - textZoomFudge) + self:settext("") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + ShowLeftCommand = function(self, params) + if params and params.name then + self:settext(params.name) + end + end, + + } + } + + -- the noteskin page function as noteskin preview and keybindings + local function createNoteskinPage() + -- list of GameButtons we can map + local gameButtonsToMap = INPUTMAPPER:GetGameButtonsToMap() + -- list of MenuButtons we can map + -- could be grabbed by INPUTMAPPER:GetMenuButtonsToMap() but want to be really specific + local menuButtonsToMap = { + "Coin", + "EffectUp", + "EffectDown", + "RestartGameplay", + "Select", + } + local inMenuPage = false + local aspectRatioProportion = (16/9) / (SCREEN_WIDTH / SCREEN_HEIGHT) + local menuBoxSize = 48 * aspectRatioProportion -- hard coded. do not care (add 20 more menu buttons then i will care) + local currentController = 0 + local currentlyBinding = false + local currentKey = "" + local cursorIndex = 1 + local automaticallyBindingEverything = false -- when true, move forward until we bound the last allowed index + local optionActive = 0 -- 0 = nothing, 1 = bind all, 2 = swap pages (to keep track of vertical hover position) + + -- entries into this list are not allowed to be bound + local bannedKeys = { + -- valid entries: + -- "key" (all keyboard input) + -- "mouse" (all mouse input) + -- "cz" (the letter z) + -- "left" (the left arrow on the keyboard) + -- "kp 2" (2 on the numpad) + mouse = true, + enter = true, + escape = true, + } + + -- function to remove all double+ binding and leave only defaults + -- this goes out to all cheaters and losers + -- if you want to use double bindings dont touch this settings menu + local function setUpKeyBindings() + INPUTBINDING:RemoveDoubleBindings(false) + automaticallyBindingEverything = false + + -- kill your precious menu double bindings (not gonna lie couldnt think of a better way to guarantee what you see is what is bound) + -- will only mess with the left side bindings (controller 0) + for _, b in ipairs(menuButtonsToMap) do + local tmp = INPUTMAPPER:GetButtonMapping(b, 0, INPUTBINDING.defaultColumn) + if tmp == nil then tmp = "nil" end + for col = 0, INPUTBINDING.maxColumn do + INPUTMAPPER:SetInputMap("", b, col, 0) + end + INPUTMAPPER:SetInputMap(tmp, b, INPUTBINDING.defaultColumn, 0) + end + + -- make sure doing this didnt break menu navigation + INPUTBINDING:MakeSureMenuIsNavigable() + + MESSAGEMAN:Broadcast("UpdatedBoundKeys") + end + + -- just moves the cursor, for keyboard compatibility only + local function selectKeybind(direction) + local n = cursorIndex + direction + local maxindex = not inMenuPage and #gameButtonsToMap*2 or #menuButtonsToMap + if n > maxindex then n = 1 end + if n < 1 then n = maxindex end + + cursorIndex = n + MESSAGEMAN:Broadcast("UpdatedBoundKeys") + end + + -- switch page between menu buttons and game buttons + local function switchBindingPage() + inMenuPage = not inMenuPage + currentKey = "" + currentlyBinding = false + currentController = 0 + cursorIndex = 1 + automaticallyBindingEverything = false + optionActive = 0 + + MESSAGEMAN:Broadcast("UpdatedBoundKeys") -- hack to get visible cursor position to update + MESSAGEMAN:Broadcast("BindingPageSet") + end + + -- select this specific key to begin binding, lock input + local function startBinding(buttonName, controller) + currentKey = buttonName + currentController = controller + currentlyBinding = true + MESSAGEMAN:Broadcast("StartedBinding", {key = currentKey, controller = controller}) + end + local function stopBinding() + currentlyBinding = false + automaticallyBindingEverything = false + MESSAGEMAN:Broadcast("StoppedBinding") + end + local function startBindingEverything() + cursorIndex = 1 + automaticallyBindingEverything = true + optionActive = 0 + local controller = ((not inMenuPage and cursorIndex > #gameButtonsToMap) and 1 or 0) + local buttonindex = controller == 0 and cursorIndex or cursorIndex - #gameButtonsToMap + local buttonbinding = not inMenuPage and gameButtonsToMap[buttonindex] or menuButtonsToMap[buttonindex] + MESSAGEMAN:Broadcast("UpdatedBoundKeys") -- hack to get visible cursor position to update + startBinding(buttonbinding, controller) + end + + -- for the currentKey, use this InputEventPlus to bind the pressed key to the button + local function bindCurrentKey(event) + if event == nil or event.DeviceInput == nil then return end -- ?? + local dev = event.DeviceInput.device + if dev == nil then return end -- ??? + local key = event.DeviceInput.button + if key == nil then return end -- ???? + local spldev = strsplit(dev, "_") + if spldev == nil or #spldev ~= 2 then return end -- ????? + local splkey = strsplit(key, "_") + if splkey == nil or #splkey ~= 2 then return end -- ?????? + local pizzaHut = spldev[2]:lower() + local tacoBell = splkey[2]:lower() + -- numpad buttons and F keys are case sensitive + if tacoBell:sub(1,2) == "kp" then + tacoBell = tacoBell:gsub("kp", "KP") + elseif tacoBell:sub(1,1) == "f" and tonumber(tacoBell:sub(2,2)) ~= nil then + tacoBell = tacoBell:gsub("f", "F") + end + local combinationPizzaHutAndTacoBell = (pizzaHut .. "_" .. tacoBell) + -- not gonna bother finding a better way to do all that + if currentKey == nil or #currentKey == 0 then return end -- ??????? + if bannedKeys[tacoBell] or bannedKeys[pizzaHut] or bannedKeys[combinationPizzaHutAndTacoBell] then return true end -- ???????? + + -- bind it + INPUTMAPPER:SetInputMap(combinationPizzaHutAndTacoBell, currentKey, INPUTBINDING.defaultColumn, currentController) + -- check to see if the button bound + local result = INPUTMAPPER:GetButtonMapping(currentKey, currentController, INPUTBINDING.defaultColumn) + -- make sure we didnt just make navigation impossible + INPUTBINDING:MakeSureMenuIsNavigable() + return result ~= nil + end + + local t = Def.ActorFrame { + Name = "NoteSkinPageContainer", + ShowLeftCommand = function(self, params) + if params and (params.name == "Noteskin" or params.name == "Customize Keybinds") then + if params.name == "Customize Keybinds" then + SCUFF.showingKeybinds = true + setUpKeyBindings() + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "Keybindings") + else + if SCUFF.showingKeybinds then + INPUTMAPPER:SaveMappingsToDisk() + end + SCUFF.showingKeybinds = false + end + self:diffusealpha(1) + self:z(1) + SCUFF.showingNoteskins = true + else + self:playcommand("HideLeft") + end + end, + HideLeftCommand = function(self) + self:diffusealpha(0) + self:z(-1) + -- save when exiting + if SCUFF.showingKeybinds then + INPUTMAPPER:SaveMappingsToDisk() + end + SCUFF.showingNoteskins = false + SCUFF.showingKeybinds = false + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + + -- cursor input management for keybindings + -- noteskin display is not relevant for this, just contains it for reasons + CONTEXTMAN:RegisterToContextSet(snm, "Keybindings", anm) + CONTEXTMAN:ToggleContextSet(snm, "Keybindings", false) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if locked out, dont allow + if not CONTEXTMAN:CheckContextSet(snm, "Keybindings") then return end + if event.type ~= "InputEventType_Release" then -- allow Repeat and FirstPress + local gameButton = event.button + local key = event.DeviceInput.button + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local enter = gameButton == "Start" + local ctrl = INPUTFILTER:IsBeingPressed("left ctrl") or INPUTFILTER:IsBeingPressed("right ctrl") + local back = key == "DeviceButton_escape" + local rightclick = key == "DeviceButton_right mouse button" + local leftclick = key == "DeviceButton_left mouse button" + + if not currentlyBinding and left then + -- functionality to attempt to make vertical choice movement very slightly more intuitive + -- this probably just confuses the user + -- dont care + if inMenuPage and optionActive == 0 and cursorIndex == 1 then + optionActive = 2 + cursorIndex = 0 + elseif inMenuPage and optionActive == 2 then + optionActive = 1 + else + optionActive = 0 + selectKeybind(-1) + end + self:playcommand("Set") + elseif not currentlyBinding and right then + if inMenuPage and optionActive == 0 and cursorIndex == #menuButtonsToMap then + optionActive = 1 + cursorIndex = 0 + elseif inMenuPage and optionActive == 1 then + optionActive = 2 + else + optionActive = 0 + selectKeybind(1) + end + self:playcommand("Set") + elseif not currentlyBinding and (up or down) then + -- functionality to let movement go into the extra vertical choices + -- because we orient the menu page vertically it makes menu logic that much more cancer + if optionActive == 1 then -- hovered "bind all" + if up then + optionActive = 0 + cursorIndex = inMenuPage and #menuButtonsToMap or 1 + else + optionActive = 2 + end + elseif optionActive == 2 then -- hovered "swap page" + if up then + optionActive = 1 + else + optionActive = 0 + cursorIndex = 1 + end + elseif optionActive <= 0 then -- not hovered on anything + if inMenuPage then + if up and cursorIndex == 1 then + optionActive = 2 + cursorIndex = 0 + elseif down and cursorIndex == #menuButtonsToMap then + optionActive = 1 + cursorIndex = 0 + elseif down then + selectKeybind(1) + elseif up then + selectKeybind(-1) + end + else + cursorIndex = 0 + if up then + optionActive = 2 + else + optionActive = 1 + end + end + end + self:playcommand("Set") + elseif not currentlyBinding and enter then + if cursorIndex <= 0 then + -- logic for pressing enter on the vertical choices + -- this is really hacked + if optionActive == 1 then + -- bind all start + startBindingEverything() + self:playcommand("Set") + elseif optionActive == 2 then + -- swap page button + switchBindingPage() + optionActive = 2 + cursorIndex = 0 + MESSAGEMAN:Broadcast("UpdatedBoundKeys") -- hack to get visible cursor position to update + self:playcommand("Set") + end + else + -- i overcomplicated logic here just for you, reader. you are welcome + -- (consider menu bindings, use either the gamebutton table or the menubutton table) + local controller = ((not inMenuPage and cursorIndex > #gameButtonsToMap) and 1 or 0) + local buttonindex = controller == 0 and cursorIndex or cursorIndex - #gameButtonsToMap + local buttonbinding = not inMenuPage and gameButtonsToMap[buttonindex] or menuButtonsToMap[buttonindex] + startBinding(buttonbinding, controller) + end + elseif not currentlyBinding and back then + -- shortcut to exit back to settings + -- press twice to exit back to general + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + elseif currentlyBinding and (back or rightclick or leftclick) then + -- cancel the binding process + -- update highlights + stopBinding() + self:playcommand("Set") + elseif currentlyBinding then + -- pressed a button that could potentially be bindable and we should bind it + local result = bindCurrentKey(event) + if result then + if automaticallyBindingEverything then + local cursorbefore = cursorIndex + selectKeybind(1) + -- if the cursor moved backwards, we finished binding everything + if cursorIndex < cursorbefore then + stopBinding() + else + local controller = ((not inMenuPage and cursorIndex > #gameButtonsToMap) and 1 or 0) + local buttonindex = controller == 0 and cursorIndex or cursorIndex - #gameButtonsToMap + local buttonbinding = not inMenuPage and gameButtonsToMap[buttonindex] or menuButtonsToMap[buttonindex] + startBinding(buttonbinding, controller) + end + else + stopBinding() + end + else + ms.ok(currentKey) + ms.ok(currentController) + ms.ok("There was some error in attempting to bind the key... Report to developers") + end + self:playcommand("Set") + else + -- nothing happens + return + end + end + end) + end, + } + + -- yeah these numbers are bogus (but are in fact based on the 4key numbers so they arent all that bad) + local columnwidth = 64 + local noteskinwidthbaseline = 256 + local secondrowYoffset = 64 + local noteskinbasezoom = 1.5 -- pick a zoom that fits 4key in 16:9 aspect ratio + local NSDirTable = GivenGameToFullNSkinElements(GAMESTATE:GetCurrentGame():GetName()) + local keybindBGSizeMultiplier = 0.97 -- this is multiplied with columnwidth + local keybindBG2SizeMultiplier = 0.97 -- this is multiplied with columnwidth and keybindBGSizeMultiplier + local keybindingTextSize = 1 -- text size inside the key button thing + -- calculation: find a zoom that fits for the current chosen column count the same way 4key on 16:9 does + local noteskinzoom = noteskinbasezoom / (#NSDirTable * columnwidth / noteskinwidthbaseline) / aspectRatioProportion + + -- finds noteskin index + local function findNoteskinIndex(skin) + local nsnames = NOTESKIN:GetNoteSkinNames() + for i, name in ipairs(nsnames) do + if name:lower() == skin:lower() then + return i + end + end + return 1 + end + + local tt = Def.ActorFrame { + Name = "SkinContainer", + InitCommand = function(self) + self:x(actuals.LeftWidth / 2) + self:zoom(noteskinzoom) + self:y(actuals.Height / 4) + end, + OnCommand = function(self) + local ind = findNoteskinIndex(getPlayerOptions():NoteSkin()) + self:playcommand("SetSkinVisibility", {index = ind}) + end, + UpdateVisibleSkinMessageCommand = function(self, params) + local ind = findNoteskinIndex((params or {}).name or "") + self:playcommand("SetSkinVisibility", {index = ind}) + end, + BindingPageSetMessageCommand = function(self) + if inMenuPage then + self:diffusealpha(0) + else + self:diffusealpha(1) + end + end, + ShowLeftCommand = function(self) + if SCUFF.showingKeybinds then + self:x(actuals.LeftWidth / 3) + self:zoom(noteskinzoom / 2) + self:playcommand("BindingPageSet") + else + self:x(actuals.LeftWidth / 2) + self:zoom(noteskinzoom) + end + end, + } + -- works almost exactly like the legacy PlayerOptions preview + -- except has some secret things attached + -- at this point in time we cannot load every Game's noteskin like I would like to + for i, dir in ipairs(NSDirTable) do + -- so the elements are centered + -- add half a column width because elements are center aligned + local leftoffset = -columnwidth * #NSDirTable / 2 + columnwidth / 2 + local tapForThisIteration = nil + local receptorForThisIteration = nil + + -- load taps + tt[#tt+1] = Def.ActorFrame { + InitCommand = function(self) + self:x(leftoffset + columnwidth * (i-1)) + self:y(secondrowYoffset) + tapForThisIteration = self + end, + Def.ActorFrame { + LoadNSkinPreview("Get", dir, "Tap Note", false) .. { + OnCommand = function(self) + for i = 1, #NOTESKIN:GetNoteSkinNames() do + local c = self:GetChild("N"..i) + c:visible(true) + end + end, + SetSkinVisibilityCommand = function(self, params) + if params and params.index then + local ind = params.index + -- noteskin displays are actually many sprites in one spot + -- for the chosen noteskin, display only the one we want + -- have to search the list to find it + for i = 1, #NOTESKIN:GetNoteSkinNames() do + local c = self:GetChild("N"..i) + if i == ind then + c:diffusealpha(1) + else + c:diffusealpha(0) + end + end + end + end, + } + }, + } + -- load receptors + tt[#tt+1] = Def.ActorFrame { + InitCommand = function(self) + self:x(leftoffset + columnwidth * (i-1)) + receptorForThisIteration = self + end, + Def.ActorFrame { + LoadNSkinPreview("Get", dir, "Receptor", false) .. { + OnCommand = function(self) + for i = 1, #NOTESKIN:GetNoteSkinNames() do + local c = self:GetChild("N"..i) + c:visible(true) + end + end, + SetSkinVisibilityCommand = function(self, params) + if params and params.index then + local ind = params.index + -- noteskin displays are actually many sprites in one spot + -- for the chosen noteskin, display only the one we want + -- have to search the list to find it + for i = 1, #NOTESKIN:GetNoteSkinNames() do + local c = self:GetChild("N"..i) + if i == ind then + c:diffusealpha(1) + else + c:diffusealpha(0) + end + end + end + end, + } + }, + } + -- load shadow taps (doubles modes) + tt[#tt+1] = Def.ActorProxy { + InitCommand = function(self) + -- ActorProxy offsets only have to be relative to the original + -- set x to the same as the highest offset + self:x(columnwidth * (#NSDirTable)) + end, + BeginCommand = function(self) + self:SetTarget(tapForThisIteration) + end, + ShowLeftCommand = function(self) + if SCUFF.showingKeybinds then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end, + } + -- load shadow receptors (doubles modes) + tt[#tt+1] = Def.ActorProxy { + InitCommand = function(self) + -- ActorProxy offsets only have to be relative to the original + -- set x to the same as the highest offset + self:x(columnwidth * (#NSDirTable)) + end, + BeginCommand = function(self) + self:SetTarget(receptorForThisIteration) + end, + ShowLeftCommand = function(self) + if SCUFF.showingKeybinds then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end, + } + -- load keybinding display + -- this is put into a function to prevent a lot of copy pasting and unmaintainability + local function keybindingDisplay(i, isDoublesSide) + -- the doubles side starting index is #NSDirTable+1 + local trueIndex = i + (isDoublesSide and #NSDirTable or 0) + local controller = isDoublesSide and 1 or 0 + return Def.ActorFrame { + Name = "KeybindingFrame", + InitCommand = function(self) + self:x(leftoffset + columnwidth * (i-1)) + if isDoublesSide then + self:addx(columnwidth * #NSDirTable) + end + self:y(secondrowYoffset * 2) + end, + ShowLeftCommand = function(self) + if SCUFF.showingKeybinds then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end, + UIElements.QuadButton(1, 1) .. { + Name = "KeybindBGBG", + InitCommand = function(self) + -- font color + self:zoomto(columnwidth * keybindBGSizeMultiplier, columnwidth * keybindBGSizeMultiplier) + self:playcommand("Set") + registerActorToColorConfigElement(self, "options", "KeybindButtonEdge") + end, + SetAlphaCommand = function(self) + if isOver(self) or cursorIndex == trueIndex then + self:diffusealpha(0.6 * buttonHoverAlpha) + else + self:diffusealpha(0.6) + end + end, + SetCommand = function(self) + self:playcommand("SetAlpha") + end, + UpdatedBoundKeysMessageCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseOverCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseOutCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseDownCommand = function(self) + if self:IsInvisible() then return end + if not currentlyBinding and not inMenuPage then + local dist = trueIndex - cursorIndex + selectKeybind(dist) + startBinding(gameButtonsToMap[i], controller) + end + end, + }, + Def.Quad { + Name = "KeybindBG", + InitCommand = function(self) + -- generally bg color + self:diffusealpha(0.6) + self:zoomto(columnwidth * keybindBGSizeMultiplier * keybindBG2SizeMultiplier, columnwidth * keybindBGSizeMultiplier * keybindBG2SizeMultiplier) + registerActorToColorConfigElement(self, "options", "KeybindButtonBackground") + end, + }, + LoadFont("Common Large") .. { + Name = "KeybindText", + InitCommand = function(self) + self:zoom(keybindingTextSize) + self:maxwidth(columnwidth * keybindBGSizeMultiplier * keybindBGSizeMultiplier / keybindingTextSize) + self:maxheight(columnwidth * keybindBGSizeMultiplier * keybindBG2SizeMultiplier / keybindingTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdatedBoundKeysMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + local buttonmapped = INPUTMAPPER:GetButtonMappingString(gameButtonsToMap[i], controller, INPUTBINDING.defaultColumn) + if buttonmapped then + self:settext(buttonmapped:gsub("Key ", "")) + else + self:settext("none") + end + end, + } + } + end + tt[#tt+1] = keybindingDisplay(i, false) + tt[#tt+1] = keybindingDisplay(i, true) + end + t[#t+1] = tt + + -- more elements to keybinding screen + -- many numbers which follow are fudged hard + -- this function creates a menu binding element for only player 1 + local function menuBinding(i, key) + return Def.ActorFrame { + Name = "KeybindingFrame", + InitCommand = function(self) + self:x(actuals.LeftWidth / 8) + self:y(menuBoxSize * (i-1)) + end, + UIElements.QuadButton(1, 1) .. { + Name = "KeybindBGBG", + InitCommand = function(self) + -- font color + self:zoomto(menuBoxSize * keybindBGSizeMultiplier, menuBoxSize * keybindBGSizeMultiplier) + self:playcommand("Set") + registerActorToColorConfigElement(self, "options", "KeybindButtonEdge") + end, + SetAlphaCommand = function(self) + if isOver(self) or cursorIndex == i then + self:diffusealpha(0.6 * buttonHoverAlpha) + else + self:diffusealpha(0.6) + end + end, + SetCommand = function(self) + self:playcommand("SetAlpha") + end, + UpdatedBoundKeysMessageCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseOverCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseOutCommand = function(self) + self:playcommand("SetAlpha") + end, + MouseDownCommand = function(self) + if not currentlyBinding then + local dist = i - cursorIndex + selectKeybind(dist) + startBinding(key, 0) + end + end, + }, + Def.Quad { + Name = "KeybindBG", + InitCommand = function(self) + -- generally bg color + self:diffusealpha(0.6) + self:zoomto(menuBoxSize * keybindBGSizeMultiplier * keybindBG2SizeMultiplier, menuBoxSize * keybindBGSizeMultiplier * keybindBG2SizeMultiplier) + registerActorToColorConfigElement(self, "options", "KeybindButtonBackground") + end, + }, + LoadFont("Common Large") .. { + Name = "KeybindText", + InitCommand = function(self) + self:zoom(keybindingTextSize) + self:maxwidth(menuBoxSize * keybindBGSizeMultiplier * keybindBGSizeMultiplier / keybindingTextSize) + self:maxheight(menuBoxSize * keybindBGSizeMultiplier * keybindBG2SizeMultiplier / keybindingTextSize) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdatedBoundKeysMessageCommand = function(self) + self:playcommand("Set") + end, + SetCommand = function(self) + local buttonmapped = INPUTMAPPER:GetButtonMappingString(key, 0, INPUTBINDING.defaultColumn) + if buttonmapped then + self:settext(buttonmapped:gsub("Key ", "")) + else + self:settext("none") + end + end, + }, + LoadFont("Common Normal") .. { + Name = "KeybindButtonText", + InitCommand = function(self) + self:x(menuBoxSize / 2 + 5) + self:halign(0) + self:zoom(menuBindingTextSize) + self:maxwidth((actuals.LeftWidth - menuBoxSize * 3 - actuals.LeftWidth/8) / menuBindingTextSize) + self:settext(key:gsub("_", " ")) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + } + } + end + t[#t+1] = Def.ActorFrame { + Name = "ExtraKeybindingElementsFrame", + ShowLeftCommand = function(self) + if SCUFF.showingKeybinds then + self:diffusealpha(1) + else + self:diffusealpha(0) + end + end, + + LoadFont("Common Normal") .. { + Name = "CurrentlyBinding", + InitCommand = function(self) + self:valign(1) + self:x(actuals.LeftWidth/2) + self:maxwidth(actuals.LeftWidth / currentlybindingTextSize) + self:playcommand("Set") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + if inMenuPage then + self:y(actuals.Height / 1.5) + else + self:y(actuals.Height / 2) + end + + if currentlyBinding and not inMenuPage then + self:settextf("Currently Binding: %s (Controller %s)", currentKey, currentController) + elseif currentlyBinding and inMenuPage then + self:settextf("Currently Binding: %s", currentKey) + else + self:settext("Currently Binding: ") + end + end, + StartedBindingMessageCommand = function(self) + self:playcommand("Set") + end, + StoppedBindingMessageCommand = function(self) + self:playcommand("Set") + end, + BindingPageSetMessageCommand = function(self) + self:playcommand("Set") + end, + }, + LoadFont("Common Normal") .. { + Name = "Instructions", + InitCommand = function(self) + self:valign(0) + self:xy(actuals.LeftWidth/2, actuals.TopLipHeight * 1.2) + self:zoom(keyinstructionsTextSize) + self:wrapwidthpixels(actuals.LeftWidth - 10) + self:maxheight((actuals.Height / 4 - actuals.TopLipHeight * 1.5) / keyinstructionsTextSize) + self:settext("Select a button to rebind with mouse or keyboard.\nPress Escape or click to cancel binding.") + registerActorToColorConfigElement(self, "main", "SecondaryText") + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "StartBindingAll", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + bg:halign(0) + self:xy(actuals.EdgePadding, actuals.Height/2 + actuals.Height/4) + txt:zoom(bindingChoicesTextSize) + txt:maxwidth(actuals.LeftWidth / bindingChoicesTextSize) + txt:settext("Start Binding All") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + bg:diffusealpha(0.2) + self.alphaDeterminingFunction = function(self) + local multiplier = optionActive == 1 and buttonHoverAlpha or 1 + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha * multiplier) + else + self:diffusealpha(1 * multiplier) + end + end + end, + SetCommand = function(self) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + startBindingEverything() + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ToggleAdvancedKeybindings", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + bg:halign(0) + self:xy(actuals.EdgePadding, actuals.Height/2 + actuals.Height/4 + 30 * bindingChoicesTextSize) + txt:zoom(bindingChoicesTextSize) + txt:maxwidth(actuals.LeftWidth / bindingChoicesTextSize) + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:diffusealpha(0.2) + self:playcommand("BindingPageSet") + self.alphaDeterminingFunction = function(self) + local multiplier = optionActive == 2 and buttonHoverAlpha or 1 + if isOver(bg) then + self:diffusealpha(buttonHoverAlpha * multiplier) + else + self:diffusealpha(1 * multiplier) + end + end + end, + BindingPageSetMessageCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + if inMenuPage then + txt:settext("View Gameplay Keybindings") + else + txt:settext("View Menu Keybindings") + end + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + SetCommand = function(self) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + switchBindingPage() + end + end, + }, + } + + -- to collect all the menu bindings + local function mbf() + local t = Def.ActorFrame { + Name = "MenuBindingFrame", + InitCommand = function(self) + self:y(actuals.Height / 4) + self:playcommand("BindingPageSet") + end, + BindingPageSetMessageCommand = function(self) + if inMenuPage then + self:diffusealpha(1) + self:z(1) + else + self:diffusealpha(0) + self:z(-1) + end + end, + } + for i, b in ipairs(menuButtonsToMap) do + t[#t+1] = menuBinding(i, b) + end + return t + end + t[#t+1] = mbf() + + return t + end + + -- the notefield preview, an optional showcase of what mods are doing + -- literally a copy of chart preview -- an ActorProxy + local function createPreviewPage() + local t = Def.ActorFrame { + Name = "PreviewPageContainer", + ShowLeftCommand = function(self, params) + -- dont open the preview if left is already opened and it is being used + if params and params.name == "Preview" and not SCUFF.showingNoteskins and not SCUFF.showingColor then + self:diffusealpha(1) + self:z(1) + SCUFF.showingPreview = true + MESSAGEMAN:Broadcast("PreviewPageOpenStatusChanged", {opened = true}) + else + self:playcommand("HideLeft") + end + end, + HideLeftCommand = function(self) + self:diffusealpha(0) + self:z(-1) + SCUFF.showingPreview = false + MESSAGEMAN:Broadcast("PreviewPageOpenStatusChanged", {opened = false}) + end, + + -- the preview notefield (but not really) + Def.ActorProxy { + Name = "NoteField", + InitCommand = function(self) + -- centered horizontally and vertically + self:x(actuals.LeftWidth / 2) + self:y(actuals.Height / 4) + end, + BeginCommand = function(self) + -- take the long road to find the actual chart preview actor + local realnotefieldpreview = SCREENMAN:GetTopScreen():safeGetChild( + "RightFrame", + "GeneralBoxFile", + "Container", + "GeneralPageFile", + "ChartPreviewFile", + "NoteField" + ) + if realnotefieldpreview ~= nil then + self:SetTarget(realnotefieldpreview) + self:addx(-realnotefieldpreview:GetX()) + else + print("It appears that chart preview is not where it should be ....") + end + end, + }, + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:diffusealpha(0) + -- the sizing here should make everything left of the wheel a mousewheel region + -- and also just a bit above and below it + -- and also the empty region to the right + -- the wheel positioning is not as clear as it could be + self:halign(0) + self:valign(0) + self:playcommand("SetPosition") + self:zoomto(actuals.GeneralBoxLeftGap, actuals.Height) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + self:halign(0) + self:x(0) + else + self:halign(1) + self:x(actuals.LeftWidth) + end + end, + UpdateWheelPositionCommand = function(self) + self:playcommand("SetPosition") + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and SCUFF.showingPreview then + if params.direction == "Up" then + SCREENMAN:GetTopScreen():GetChild("WheelFile"):playcommand("Move", {direction = -1}) + else + SCREENMAN:GetTopScreen():GetChild("WheelFile"):playcommand("Move", {direction = 1}) + end + end + end, + MouseClickPressMessageCommand = function(self, params) + if params ~= nil and params.button ~= nil and SCUFF.showingPreview then + if params.button == "DeviceButton_right mouse button" then + if isOver(self) then + SCREENMAN:GetTopScreen():PauseSampleMusic() + end + end + end + end + }, + } + return t + end + + -- includes color modifying and preset picking + -- while the code may work it contains incredibly bad practice (for one, dont spam messageman broadcasts) + local function createColorConfigPage() + local saturationOverlay = nil + local colorPickPosition = nil + local saturationSliderPos = nil + local alphaSliderPos = nil + local textCursorPos = 1 + local boxSize = math.min(actuals.ColorBoxHeight, actuals.ColorBoxWidth) + local sliderWidth = boxSize / 10 + local textLineSeparation = boxSize / 8 -- basically the y position of the bottom of each line + local widthOfTheRightSide = actuals.LeftWidth - (boxSize + actuals.EdgePadding * 2 + sliderWidth) - actuals.EdgePadding * 2 + local halfWayInTheMiddleOfTheRightSide = actuals.LeftWidth - widthOfTheRightSide/2 + + -- probably make this an odd number for the ocd kids because this includes a top item which does not ever change + local colorConfigItemCount = 13 + -- lost track of what this means dont care + -- basically if this is true and you press enter you save + -- and if not and you press enter, the change is applied then you have to hit enter again + local aboutToSave = false + + -- stores the data for items to display + -- should be a list of strings + -- starting it off with categories because categories is the starting state + local displayItemDatas = getColorConfigCategories() + local page = 1 + local maxPage = math.ceil(#displayItemDatas / (colorConfigItemCount-1)) + local cursorPosition = 1 + + -- selected and saved element colors and HSV info (defaulted to white) + local currentColor = color("1,1,1,1") + local savedColor = currentColor + local hueNum, satNum, valNum, alphaNum = colorToHSVNums(currentColor) + + -- determines the current state of color config selection + -- valid options: + -- category - currently selecting a color config element category + -- element - currently selecting an element in a selected category + -- preset - currently selecting a color config preset + -- editing - currently editing an element color + local selectionstate = "category" + local selectedcategory = "" + local selectedpreset = getColorPreset() + local selectedelement = "" + local hexEntryString = "" + + -- switch this variable (here, not at runtime) to display and allow editing alpha + local showAlpha = false + local hexStringMaxLengthWithAlpha = 9 + local hexStringMaxLengthWithoutAlpha = 7 + local hexStringMaxLength = showAlpha and hexStringMaxLengthWithAlpha or hexStringMaxLengthWithoutAlpha + + -- move choice pages + local function movePage(n) + if maxPage <= 1 then + return + end + -- the tooltip gets stuck on if it is visible and page changes + TOOLTIP:Hide() + -- math to make pages loop both directions + local nn = (page + n) % (maxPage + 1) + if nn == 0 then + nn = n > 0 and 1 or maxPage + end + page = nn + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + -- move choice selection cursor and also maybe move pages + local function moveChoiceCursor(n) + -- math to make pages loop both directions + local newpos = cursorPosition + n + if newpos > #displayItemDatas then + newpos = 1 + cursorPosition = newpos + if page ~= 1 then + page = 1 + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + elseif newpos < 1 then + newpos = #displayItemDatas + cursorPosition = newpos + if maxPage ~= page then + page = maxPage + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + else + cursorPosition = newpos + local lb = clamp((page-1) * (colorConfigItemCount-1) + 1, 0, #displayItemDatas) + local ub = clamp(page * colorConfigItemCount-1, 0, #displayItemDatas) + if cursorPosition < lb then + page = page - 1 + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + elseif cursorPosition > ub then + page = page + 1 + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + end + MESSAGEMAN:Broadcast("UpdateColorConfigChoiceCursorDisplay") + end + + -- apply the HSV+A vars to the current state of the config + -- updates the elements which display the information about the color + local function applyHSV() + local newColor = HSV(hueNum, 1 - satNum, 1 - valNum) + newColor[4] = alphaNum + currentColor = newColor + + -- the color information Actors may not be present for various reasons + if colorPickPosition ~= nil then + colorPickPosition:xy(boxSize * hueNum/360, boxSize * valNum) + end + if saturationOverlay ~= nil then + saturationOverlay:diffusealpha(satNum) + end + if saturationSliderPos ~= nil then + saturationSliderPos:y(boxSize * satNum) + end + if alphaSliderPos ~= nil then + alphaSliderPos:y(boxSize * (1-alphaNum)) + end + + textCursorPos = hexStringMaxLength + hexEntryString = "#" .. ColorToHex(currentColor) + hexEntryString = hexEntryString:sub(1,hexStringMaxLength) + + MESSAGEMAN:Broadcast("ClickedNewColor") + end + local function updateSaturation(percent) + if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end + satNum = percent + applyHSV() + end + local function updateAlpha(percent) + if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end + alphaNum = 1 - percent + applyHSV() + end + local function updateColor(percentX, percentY) + if percentY < 0 then percentY = 0 elseif percentY > 1 then percentY = 1 end + if percentX < 0 then percentX = 0 elseif percentX > 1 then percentX = 1 end + -- not 360 because 360 makes it produce FF00FF instead of FF0000 + hueNum = 359.99 * percentX + valNum = percentY + applyHSV() + end + + -- handling keyboard inputs for hex characters only - str:match("[%x]") + local function handleHexEntry(character) + character = character:upper() + if #hexEntryString <= hexStringMaxLength then -- #23 45 67 89 format + if #hexEntryString == hexStringMaxLength and textCursorPos == hexStringMaxLength then + hexEntryString = hexEntryString:sub(1,-2) .. character + else + if textCursorPos == #hexEntryString + 1 then + hexEntryString = hexEntryString .. character + else + local left = hexEntryString:sub(1,textCursorPos-1) + local right = hexEntryString:sub(textCursorPos+1) + hexEntryString = left .. character .. right + end + textCursorPos = textCursorPos + 1 + end + end + if textCursorPos > hexStringMaxLength then textCursorPos = hexStringMaxLength end + aboutToSave = false + MESSAGEMAN:Broadcast("UpdateStringDisplay") + end + + -- preparing to save via pressing Enter + local function handleTextUpdate() + local hxl = #hexEntryString - 1 + local finalcolor = color("1,1,1,1") + if hxl == 3 or hxl == 4 or hxl == 5 then -- color 3/4/5 hex + finalcolor[1] = tonumber("0x"..hexEntryString:sub(2,2)) / 15 + finalcolor[2] = tonumber("0x"..hexEntryString:sub(3,3)) / 15 + finalcolor[3] = tonumber("0x"..hexEntryString:sub(4,4)) / 15 + if hxl == 4 then finalcolor[4] = tonumber("0x"..hexEntryString:sub(5,5)) / 15 end + if hxl == 5 then finalcolor[4] = tonumber("0x"..hexEntryString:sub(5,6)) / 255 end + elseif hxl == 6 or hxl == 7 or hxl == 8 then -- color 6/7/8 hex + finalcolor[1] = tonumber("0x"..hexEntryString:sub(2,3)) / 255 + finalcolor[2] = tonumber("0x"..hexEntryString:sub(4,5)) / 255 + finalcolor[3] = tonumber("0x"..hexEntryString:sub(6,7)) / 255 + if hxl == 7 then finalcolor[4] = tonumber("0x"..hexEntryString:sub(7,7)) / 15 end + if hxl == 8 then finalcolor[4] = tonumber("0x"..hexEntryString:sub(8,9)) / 255 end + else + return + end + local bh, bs, bv, ba = hueNum, satNum, valNum, alphaNum + hueNum, satNum, valNum, alphaNum = colorToHSVNums(finalcolor) + -- check to see if the color changed + if bh ~= hueNum or bs ~= satNum or valNum ~= bv or alphaNum ~= ba then + aboutToSave = true + else + aboutToSave = false + end + applyHSV() + end + + ------=== magical text cursor functions (fun fact utf-16 probably breaks this hard copy pasters beware) + -- find the x position for a char in text relative to text left edge + local function getXPositionInText(self, index) + local tlChar1 = self:getGlyphRect(1) -- top left vertex of first char in text + local tlCharIndex = self:getGlyphRect(index) -- top left of char at text + -- the [1] index is the x coordinate of the vertex + if tlCharIndex and tlChar1 then + local theX = tlCharIndex[1] - tlChar1[1] + return theX * self:GetZoom() + else + return 0 + end + end + local function getWidthOfChar(self, index) + local tl, bl, tr, br = self:getGlyphRect(index) -- topleft/bottomleft/topright/bottomright coord tables + if tr and br then + local glyphWidth = tr[1] - bl[1] + return glyphWidth * self:GetZoom() * 0.95 -- slightly smaller than needed because it was too big + else + return 1 + end + end + local function cursorCanMove(speed) + local maxTextSize = (#hexEntryString == hexStringMaxLength and hexStringMaxLength or #hexEntryString + 1) + local tmpCursor = textCursorPos + speed + if tmpCursor > maxTextSize or tmpCursor < 2 then + return 0 + end + return speed + end + ------=== + + -- just revert the color back to the saved color + local function undoChanges() + aboutToSave = false + currentColor = savedColor + hueNum, satNum, valNum, alphaNum = colorToHSVNums(currentColor) + applyHSV() + end + -- revert current color to original default color, do not save + local function resetToDefault() + if selectedcategory == "" or selectedelement == "" then return end + aboutToSave = true + currentColor = getDefaultColor(selectedcategory, selectedelement) + hueNum, satNum, valNum, alphaNum = colorToHSVNums(currentColor) + applyHSV() + end + -- revert color state to blank + local function resetToBlank() + aboutToSave = false -- this shouldnt be used when color is editable + currentColor = color("1,1,1,1") + savedColor = currentColor + hueNum, satNum, valNum, alphaNum = colorToHSVNums(currentColor) + applyHSV() + end + -- save changes + local function saveColor() + if selectedpreset == "" or selectedcategory == "" or selectedelement == "" then return end + aboutToSave = false + savedColor = currentColor + COLORS:saveColor(selectedcategory, selectedelement, hexEntryString) + COLORS:saveColorPreset(selectedpreset) + applyHSV() + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + + -- handle switching states and stuff + local function switchSelectionState(cat) + if cat == nil then cat = "" end + cat = cat:lower() + if cat == "category" then + -- populate with all the categories + displayItemDatas = getColorConfigCategories() + selectedcategory = "" + selectedelement = "" + resetToBlank() + elseif cat == "element" then + -- populate with all elements in the selected category + displayItemDatas = getColorConfigElementsForCategory(selectedcategory) + selectedelement = "" + resetToBlank() + elseif cat == "preset" then + -- populate with all available presets + displayItemDatas = getColorConfigPresets() + selectedpreset = "" + selectedcategory = "" + selectedelement = "" + resetToBlank() + elseif cat == "editing" then + -- dont change listing, but change state to allow color editing + currentColor = COLORS:getColor(selectedcategory, selectedelement) + savedColor = currentColor + hueNum, satNum, valNum, alphaNum = colorToHSVNums(currentColor) + aboutToSave = false + applyHSV() + else + return + end + maxPage = math.ceil(#displayItemDatas / (colorConfigItemCount-1)) + page = 1 + cursorPosition = 1 + selectionstate = cat + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + end + + local function selectCategory(category) + -- selecting a category brings you to element selection state + selectedcategory = category + switchSelectionState("element") + end + local function selectElement(element) + -- selecting an element begins editing of the element color + selectedelement = element + switchSelectionState("editing") + end + local function selectPreset(preset) + -- selecting a preset brings you to category select + -- it also loads the preset globally + selectedpreset = preset + changeCurrentColorPreset(preset) + switchSelectionState("category") + end + local function looking4preset() + -- looking at the list of presets to load + switchSelectionState("preset") + end + + local function goUpOneLayer() + if selectionstate == "category" then + switchSelectionState("preset") + elseif selectionstate == "element" then + switchSelectionState("category") + elseif selectionstate == "preset" then + -- impossible + elseif selectionstate == "editing" then + switchSelectionState("category") + else + return + end + end + + -- typing window for making a new preset + local function newPresetDialogue() + local redir = SCREENMAN:get_input_redirected(PLAYER_1) + local function off() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, false) + end + end + local function on() + if redir then + SCREENMAN:set_input_redirected(PLAYER_1, true) + end + end + off() + + local function f(answer) + -- success: + -- blank color info, jump to preset select page + resetToBlank() + if COLORS:loadColorPreset(answer:lower()) then + selectPreset(answer:lower()) + else + looking4preset() + end + on() + end + local question = "NEW COLOR CONFIG PRESET\nPlease enter a new preset name." + askForInputStringWithFunction( + question, + 128, + false, + f, + function(answer) + local result = answer ~= nil and answer:gsub("^%s*(.-)%s*$", "%1") ~= "" and not answer:match("::") and answer:gsub("^%s*(.-)%s*$", "%1"):sub(-1) ~= ":" + local presets = getColorConfigPresets() + -- no dupes + for _, n in ipairs(presets) do + if n:lower() == answer:lower() then + result = false + break + end + end + -- so far we can attempt to make the color config entry + if result then + result = COLORS:newColorPreset(answer:lower()) + if not result then + SCREENMAN:GetTopScreen():GetChild("Question"):settext(question .. "\nThere was an issue creating the new color config preset. You may try again.\nTo exit, press Esc.") + return false, "Response invalid." + else + return true, "Response invalid." -- the 2nd param doesnt matter here + end + else + SCREENMAN:GetTopScreen():GetChild("Question"):settext(question .. "\nDo not leave this space blank. Do not use illegal characters.\nTo exit, press Esc.") + return false, "Response invalid." + end + end, + function() + -- upon exit, do nothing + on() + end + ) + end + + local t = Def.ActorFrame { + Name = "ColorConfigPageContainer", + ShowLeftCommand = function(self, params) + if params and params.name == "Color Config" then + self:diffusealpha(1) + self:z(1) + SCUFF.showingColor = true + CONTEXTMAN:SetFocusedContextSet(SCREENMAN:GetTopScreen():GetName(), "ColorConfig") + else + self:playcommand("HideLeft") + end + end, + HideLeftCommand = function(self) + self:diffusealpha(0) + self:z(-1) + SCUFF.showingColor = false + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + + -- cursor input management for color config + -- noteskin display is not relevant for this, just contains it for reasons + CONTEXTMAN:RegisterToContextSet(snm, "ColorConfig", anm) + CONTEXTMAN:ToggleContextSet(snm, "ColorConfig", false) + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if locked out, dont allow + if not CONTEXTMAN:CheckContextSet(snm, "ColorConfig") then return end + if event.type ~= "InputEventType_Release" then -- allow Repeat and FirstPress + local gameButton = event.button + local key = event.DeviceInput.button + local letter = event.char + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local enter = gameButton == "Start" + local ctrl = INPUTFILTER:IsBeingPressed("left ctrl") or INPUTFILTER:IsBeingPressed("right ctrl") + local alt = INPUTFILTER:IsBeingPressed("right alt") or INPUTFILTER:IsBeingPressed("left alt") + local back = key == "DeviceButton_escape" + local delete = key == "DeviceButton_delete" + local backspace = key == "DeviceButton_backspace" + local rightclick = key == "DeviceButton_right mouse button" + local leftclick = key == "DeviceButton_left mouse button" + + if back then + if selectionstate == "editing" then + -- pressing back while editing moves back to element seletion + switchSelectionState("element") + else + -- shortcut to exit back to settings + -- press twice to exit back to general + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + end + elseif selectionstate == "editing" then + -- editing a color, typing only on the color + if letter and letter:match('[%x]') then + -- match all hex for inputting color + handleHexEntry(letter) + elseif delete then + -- pressed delete + if ctrl then + -- holding ctrl, "pressed reset to default" + resetToDefault() + elseif alt then + -- holding alt, "pressed undo" + undoChanges() + else + hexEntryString = "#" + textCursorPos = 2 + aboutToSave = false + end + MESSAGEMAN:Broadcast("UpdateStringDisplay") + elseif backspace then + if #hexEntryString > 1 then + if textCursorPos - 1 == #hexEntryString then + hexEntryString = hexEntryString:sub(1, -2) + else + local left = hexEntryString:sub(1, textCursorPos - 1) + local right = hexEntryString:sub(textCursorPos + 1) + hexEntryString = left .. "0" .. right + end + textCursorPos = textCursorPos + cursorCanMove(-1) + aboutToSave = false + MESSAGEMAN:Broadcast("UpdateStringDisplay") + end + elseif left then + local before = textCursorPos + textCursorPos = textCursorPos + cursorCanMove(-1) + if before ~= textCursorPos then + MESSAGEMAN:Broadcast("UpdateStringDisplay") + end + elseif right then + local before = textCursorPos + textCursorPos = textCursorPos + cursorCanMove(1) + if before ~= textCursorPos then + MESSAGEMAN:Broadcast("UpdateStringDisplay") + end + elseif enter then + if aboutToSave then + saveColor() + else + handleTextUpdate() + end + end + elseif selectionstate ~= "editing" then + -- all cursor movement + if left or up then + moveChoiceCursor(-1) + elseif right or down then + moveChoiceCursor(1) + elseif enter then + local itemData = displayItemDatas[cursorPosition] + if selectionstate == "preset" then + -- clicked a preset, switching to category + selectPreset(itemData) + elseif selectionstate == "category" then + -- clicked a category, switching to element + selectCategory(itemData) + elseif selectionstate == "element" then + -- clicked an element, switching to edit mode + selectElement(itemData) + end + elseif backspace then + -- go up one layer + goUpOneLayer() + elseif key and key == "DeviceButton_n" and ctrl then + -- ctrl-n makes a new preset + newPresetDialogue() + end + else + -- nothing happens + return + end + end + end) + + -- hack to set up all default values + applyHSV() + MESSAGEMAN:Broadcast("ColorConfigSelectionStateChanged") + MESSAGEMAN:Broadcast("UpdateColorConfigChoiceCursorDisplay") + end, + + Def.ActorFrame { + Name = "TopColorStuff", + InitCommand = function(self) + self:xy(actuals.EdgePadding, actuals.TopLipHeight * 2) + end, + Def.Sprite { + Name = "HSVImage", + Texture = THEME:GetPathG("", "color_hsv"), + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(boxSize, boxSize) + end, + }, + UIElements.SpriteButton(1, 0, THEME:GetPathG("", "color_sat_overlay")) .. { + Name = "SaturationOverlay", + InitCommand = function(self) + saturationOverlay = self + self:halign(0):valign(0) + self:zoomto(boxSize, boxSize) + self:diffusealpha(satNum) + end, + InvokeCommand = function(self, params) + if not focused or not SCUFF.showingColor or selectionstate ~= "editing" then return end + -- normally local x and y is provided but something looks broken and im not fixing it + -- (has to do with button roots and nonsense) + local relX, relY = self:GetLocalMousePos(INPUTFILTER:GetMouseX(), INPUTFILTER:GetMouseY(), 0) + aboutToSave = true + updateColor(relX / boxSize, relY / boxSize) + end, + -- did not add hover functions because we want to be color correct + MouseDownCommand = function(self, params) + self:playcommand("Invoke", params) + end, + MouseDragCommand = function(self, params) + self:playcommand("Invoke", params) + end, + }, + Def.ActorFrame { + Name = "SaturationSliderFrame", + InitCommand = function(self) + self:x(boxSize + actuals.EdgePadding) + end, + UIElements.SpriteButton(1, 0, THEME:GetPathG("", "color_sat_gradient")) .. { + Name = "SaturationSlider", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(sliderWidth, boxSize) + end, + InvokeCommand = function(self, params) + if not focused or not SCUFF.showingColor or selectionstate ~= "editing" then return end + -- normally local x and y is provided but something looks broken and im not fixing it + -- (has to do with button roots and nonsense) + local relX, relY = self:GetLocalMousePos(INPUTFILTER:GetMouseX(), INPUTFILTER:GetMouseY(), 0) + aboutToSave = true + updateSaturation(relY / boxSize) + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke", params) + end, + MouseDragCommand = function(self, params) + self:playcommand("Invoke", params) + end, + }, + Def.Sprite { + Name = "SaturationPointer", + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:x(sliderWidth + sliderWidth / 5 - 1) + self:zoomto(sliderWidth / 2, sliderWidth / 2) + self:rotationz(-90) + saturationSliderPos = self + end, + } + }, + --[[-- We will not be including alpha control. Alpha is overridden by the Actors. + -- I'm nice enough to provide it here (didnt test) + Def.ActorFrame { + Name = "AlphaSliderFrame", + InitCommand = function(self) + self:x(boxSize + actuals.EdgePadding * 2 + sliderWidth) + end, + UIElements.QuadButton(1, 0) .. { + Name = "AlphaSlider", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(sliderWidth, boxSize) + self:diffuse(color("#666666FF")) + end, + InvokeCommand = function(self, params) + if not focused or not SCUFF.showingColor then return end + -- normally local x and y is provided but something looks broken and im not fixing it + -- (has to do with button roots and nonsense) + local relX, relY = self:GetLocalMousePos(INPUTFILTER:GetMouseX(), INPUTFILTER:GetMouseY(), 0) + aboutToSave = true + updateAlpha(relY / boxSize) + end, + MouseDownCommand = function(self, params) + self:playcommand("Invoke", params) + end, + MouseDragCommand = function(self, params) + self:playcommand("Invoke", params) + end, + }, + Def.Sprite { + Name = "AlphaPointer", + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:x(sliderWidth + sliderWidth / 5 - 1) + self:zoomto(sliderWidth / 2, sliderWidth / 2) + self:rotationz(-90) + alphaSliderPos = self + end, + } + }, + ]] + Def.Sprite { + Name = "ColorPickPosition", + Texture = THEME:GetPathG("", "_thick circle"), + InitCommand = function(self) + self:zoom(0.2) + colorPickPosition = self + end, + }, + Def.ActorFrame { + Name = "TopRightSide", + InitCommand = function(self) + self:x(boxSize + actuals.EdgePadding * 2 + sliderWidth) + end, + LoadFont("Common Normal") .. { + Name = "CurrentPreset", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(textLineSeparation * 1) + self:zoom(colorConfigTextSize) + self:maxwidth(widthOfTheRightSide / colorConfigChoiceTextSize - textZoomFudge) + self:settext("Current preset:") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + self:settextf("Current preset: %s", selectedpreset) + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("Set") + end, + }, + LoadFont("Common Normal") .. { + Name = "CurrentElement", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(textLineSeparation * 2) + self:zoom(colorConfigTextSize) + self:maxwidth(widthOfTheRightSide / colorConfigChoiceTextSize - textZoomFudge) + self:settext("Current element:") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + SetCommand = function(self) + self:settextf("Current element: %s", selectedelement) + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("Set") + end, + }, + LoadFont("Common Normal") .. { + Name = "CurrentColorTitle", + InitCommand = function(self) + self:halign(0):valign(1) + self:y(textLineSeparation * 3) + self:zoom(colorConfigTextSize) + self:maxwidth(widthOfTheRightSide/2 / colorConfigChoiceTextSize - textZoomFudge) + self:settext("Current color") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + Def.ActorFrame { + Name = "CurrentColorInputTextContainer", + InitCommand = function(self) + self:xy(widthOfTheRightSide, textLineSeparation * 3) + end, + LoadFont("Common Normal") .. { + Name = "CurrentColorInputText", + InitCommand = function(self) + self:halign(1):valign(1) + self:zoom(colorConfigTextSize) + self:maxwidth(widthOfTheRightSide/2 / colorConfigChoiceTextSize - textZoomFudge) + self:settext("#") + -- colored white always ? + end, + SetCommand = function(self) + self:settext(hexEntryString) + self:GetParent():GetChild("CurrentColorCursorPosition"):playcommand("UpdateCursorDisplay") + self:GetParent():GetParent():GetChild("CurrentColorPreview"):playcommand("UpdateColorDisplay") + self:GetParent():GetParent():GetChild("SaveChangesButton"):playcommand("Set") + end, + UpdateStringDisplayMessageCommand = function(self) + self:playcommand("Set") + end, + ClickedNewColorMessageCommand = function(self) + self:playcommand("Set") + end, + }, + Def.Quad { + Name = "CurrentColorCursorPosition", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(0,0) + self:y(3) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + UpdateCursorDisplayCommand = function(self) + local pos = 11 + local txt = self:GetParent():GetChild("CurrentColorInputText") + if textCursorPos ~= #hexEntryString + 1 then -- if the cursor is under an actual char + local glyphWidth = getWidthOfChar(txt, textCursorPos) - 1 + self:zoomto(glyphWidth, 2) + pos = getXPositionInText(txt, textCursorPos) + else + pos = getXPositionInText(txt, textCursorPos-1) + getWidthOfChar(txt, textCursorPos-1) + end + self:finishtweening() + self:linear(0.05) + self:x(-txt:GetZoomedWidth() + pos) + end + }, + }, + Def.Quad { + Name = "CurrentColorPreview", + InitCommand = function(self) + self:halign(0) + self:y(textLineSeparation * 4) + self:zoomto(widthOfTheRightSide / 2 - actuals.EdgePadding/2, textLineSeparation) + end, + SetCommand = function(self) + self:diffuse(currentColor) + end, + UpdateColorDisplayCommand = function(self) + self:playcommand("Set") + end, + }, + Def.Quad { + Name = "SavedColorPreview", + InitCommand = function(self) + self:halign(1) + self:xy(widthOfTheRightSide, textLineSeparation * 4) + self:zoomto(widthOfTheRightSide / 2 - actuals.EdgePadding/2, textLineSeparation) + end, + SetCommand = function(self) + self:diffuse(savedColor) + end, + UpdateColorDisplayCommand = function(self) + self:playcommand("Set") + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("Set") + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "UndoButton", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(1) + bg:halign(0):valign(1) + txt:zoom(colorConfigTextSize) + txt:maxwidth(widthOfTheRightSide / 2 / colorConfigTextSize) + self:y(textLineSeparation * 6) + bg:y(1) + txt:settext("Undo") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + local disabledmultiplier = selectionstate ~= "editing" and 0.3 or 1 + self:diffusealpha(1 * hovermultiplier * disabledmultiplier) + if isOver(bg) then + TOOLTIP:SetText("Undo: Alt-Delete") + TOOLTIP:Show() + else + TOOLTIP:Hide() + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + if params.update == "OnMouseDown" then + undoChanges() + self:alphaDeterminingFunction() + end + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + ClickedNewColorMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ResetToDefaultButton", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(1):valign(1) + bg:halign(1):valign(1) + txt:zoom(colorConfigTextSize) + txt:maxwidth(widthOfTheRightSide / 2 / colorConfigTextSize) + self:x(widthOfTheRightSide) + self:y(textLineSeparation * 6) + bg:y(1) + txt:settext("Reset to Default") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + local disabledmultiplier = selectionstate ~= "editing" and 0.3 or 1 + self:diffusealpha(1 * hovermultiplier * disabledmultiplier) + if isOver(bg) then + TOOLTIP:SetText("Reset: Ctrl-Delete") + TOOLTIP:Show() + else + TOOLTIP:Hide() + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + if params.update == "OnMouseDown" then + resetToDefault() + self:alphaDeterminingFunction() + end + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + ClickedNewColorMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "SaveChangesButton", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(1) + bg:halign(0):valign(1) + txt:zoom(colorConfigTextSize) + txt:maxwidth(widthOfTheRightSide / colorConfigTextSize) + self:y(textLineSeparation * 7) + bg:y(1) + self:playcommand("Set") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + local disabledmultiplier = selectionstate ~= "editing" and 0.3 or 1 + self:diffusealpha(1 * hovermultiplier * disabledmultiplier) + if isOver(bg) then + TOOLTIP:SetText("Save (or press Enter)") + TOOLTIP:Show() + else + TOOLTIP:Hide() + end + end + end, + SetCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + if aboutToSave then + txt:settext("Save Changes (Not Saved!)") + else + txt:settext("Save Changes") + end + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() or selectionstate ~= "editing" then return end + if params.update == "OnMouseDown" then + saveColor() + self:alphaDeterminingFunction() + end + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + ClickedNewColorMessageCommand = function(self) + self:alphaDeterminingFunction() + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "NewPreset", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0):valign(1) + bg:halign(0):valign(1) + self:y(textLineSeparation * 8) + bg:y(1) + txt:zoom(colorConfigTextSize) + txt:maxwidth(widthOfTheRightSide / colorConfigChoiceTextSize - textZoomFudge) + txt:settext("New Color Config Preset") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + self:diffusealpha(1 * hovermultiplier) + if isOver(bg) then + TOOLTIP:SetText("New Preset: Ctrl-N") + TOOLTIP:Show() + else + TOOLTIP:Hide() + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + newPresetDialogue() + self:alphaDeterminingFunction() + end + end, + }, + }, + }, + } + + local function colorConfigChoices() + local frameY = actuals.TopLipHeight*2 + boxSize + actuals.EdgePadding + local remainingYSpace = actuals.Height - frameY - actuals.EdgePadding + + local t = Def.ActorFrame { + Name = "ColorConfigItemChoiceFrame", + InitCommand = function(self) + self:xy(actuals.EdgePadding, frameY) + end, + + LoadFont("Common Normal") .. { + Name = "ColorConfigTopItemChoice", + InitCommand = function(self) + self:halign(0) + self:zoom(colorConfigChoiceTextSize) + self:maxwidth((actuals.LeftWidth - actuals.EdgePadding*2) * 0.7 / colorConfigTextSize - textZoomFudge) + self:y(remainingYSpace / colorConfigItemCount / 2) + self:settext(" ") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateColorConfigDisplayCommand = function(self) + if selectionstate == "category" then + self:settext("Browsing Color Categories") + elseif selectionstate == "preset" then + self:settext("Browsing Color Config Presets") + elseif selectionstate == "element" then + self:settext("Browsing Elements in '"..selectedcategory.."'") + elseif selectionstate == "editing" then + self:settext("Browsing Elements in '"..selectedcategory.."' (editing)") + end + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("UpdateColorConfigDisplay") + end, + }, + LoadFont("Common Normal") .. { + Name = "PageNumber", + InitCommand = function(self) + self:halign(1) + self:zoom(colorConfigChoiceTextSize) + self:maxwidth((actuals.LeftWidth - actuals.EdgePadding*2) * 0.2 / colorConfigTextSize - textZoomFudge) + self:x(actuals.LeftWidth - actuals.EdgePadding*2) + self:y(remainingYSpace - remainingYSpace / colorConfigItemCount / 2) + self:settext(" ") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end, + UpdateColorConfigDisplayCommand = function(self) + local lb = clamp((page-1) * (colorConfigItemCount-1) + 1, 0, #displayItemDatas) + local ub = clamp(page * colorConfigItemCount-1, 0, #displayItemDatas) + self:settextf("%d-%d/%d", lb, ub, #displayItemDatas) + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("UpdateColorConfigDisplay") + end, + }, + Def.Quad { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(actuals.LeftWidth - actuals.EdgePadding*2, remainingYSpace) + self:diffusealpha(0) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and SCUFF.showingColor then + if params.direction == "Up" then + movePage(-1) + else + movePage(1) + end + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "BackButton", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(1) + bg:halign(1) + txt:zoom(colorConfigChoiceTextSize) + txt:maxwidth((actuals.LeftWidth - actuals.EdgePadding*2) * 0.3 / colorConfigTextSize - textZoomFudge) + self:x(actuals.LeftWidth - actuals.EdgePadding*2) + self:y(remainingYSpace / colorConfigItemCount / 2) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self.alphaDeterminingFunction = function(self) + local statemultiplier = selectionstate ~= "preset" and 1 or 0 + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + self:diffusealpha(1 * statemultiplier * hovermultiplier) + end + end, + UpdateColorConfigDisplayCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + if selectionstate == "category" then + txt:settext("Back to Presets") + elseif selectionstate == "preset" then + txt:settext(" ") + elseif selectionstate == "element" then + txt:settext("Back to Categories") + elseif selectionstate == "editing" then + txt:settext("Back to Categories") + end + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self:alphaDeterminingFunction() + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("UpdateColorConfigDisplay") + end, + RolloverUpdateCommand = function(self, params) + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if params.update == "OnMouseDown" then + goUpOneLayer() + self:alphaDeterminingFunction() + end + end, + } + } + + local function colorConfigChoice(i) + local index = i + -- leaving 0.2 here for the page number + local itemWidth = (actuals.LeftWidth - actuals.EdgePadding*2) * 0.6 + local itemColorPreviewWith = (actuals.LeftWidth - actuals.EdgePadding*2) * 0.2 + local itemData = nil + + local t = UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ColorConfigItemChoice", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + -- position the item half way into the nth position like how we do horizontal choice positioning + -- then dont vertically align anything so it Just Works + self:y((remainingYSpace / colorConfigItemCount) * i + (remainingYSpace / colorConfigItemCount / 2)) + txt:halign(0) + bg:halign(0) + txt:zoom(colorConfigChoiceTextSize) + txt:maxwidth(itemWidth / colorConfigChoiceTextSize - textZoomFudge) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + registerActorToColorConfigElement(bg, "options", "Cursor") + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + self.alphaDeterminingFunction = function(self) + local hovermultiplier = isOver(bg) and buttonHoverAlpha or 1 + local visiblemultiplier = itemData == nil and 0 or 1 + self:diffusealpha(1 * hovermultiplier * visiblemultiplier) + end + end, + UpdateColorConfigDisplayCommand = function(self) + index = (page - 1) * (colorConfigItemCount-1) + i + itemData = displayItemDatas[index] + self:finishtweening() + self:diffusealpha(0) + self:GetChild("ElementPreview"):playcommand("UpdateElementPreview") + self:playcommand("UpdateCursorDisplay") + if itemData ~= nil then + self:playcommand("UpdateText") + self:smooth(itemListAnimationSeconds * i) + self:alphaDeterminingFunction() + end + end, + UpdateTextCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + local txtstr = itemData or "" + txt:settextf("%d. %s", index, txtstr) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + UpdateColorConfigChoiceCursorDisplayMessageCommand = function(self) + local bg = self:GetChild("BG") + if itemData ~= nil then + if cursorPosition == index then + bg:diffusealpha(0.2) + else + bg:diffusealpha(0) + end + else + bg:diffusealpha(0) + end + end, + ColorConfigSelectionStateChangedMessageCommand = function(self) + self:playcommand("UpdateColorConfigDisplay") + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if selectionstate == "preset" then + -- clicked a preset, switching to category + selectPreset(itemData) + elseif selectionstate == "category" then + -- clicked a category, switching to element + selectCategory(itemData) + elseif selectionstate == "element" or selectionstate == "editing" then + -- clicked an element, switching to edit mode + selectElement(itemData) + end + self:alphaDeterminingFunction() + end + end + } + t[#t+1] = Def.Quad { + Name = "ElementPreview", + InitCommand = function(self) + self:halign(0) + self:x(itemWidth) + self:zoomto(itemColorPreviewWith, remainingYSpace / colorConfigItemCount * 0.6) + end, + UpdateElementPreviewCommand = function(self) + if itemData == nil or itemData == "" or (selectionstate ~= "editing" and selectionstate ~= "element") then + self:visible(false) + return + else + self:visible(true) + self:diffuse(COLORS:getColor(selectedcategory, itemData)) + end + end, + } + return t + end + for i = 1, colorConfigItemCount-1 do + t[#t+1] = colorConfigChoice(i) + end + return t + end + t[#t+1] = colorConfigChoices() + return t + end + + t[#t+1] = createNoteskinPage() + t[#t+1] = createPreviewPage() + t[#t+1] = createColorConfigPage() + + return t +end + +local function rightFrame() + -- to reach the explanation text from anywhere without all the noise + local explanationHandle = nil + local offscreenX = SCREEN_WIDTH + local onscreenX = SCREEN_WIDTH - actuals.RightWidth + + -- settings stored here will get applied when the screen is hidden + -- the key can be anything but it should be consistent for a single setting + -- luckily they should all be preferences + --[[ format: + KeyName = { + -- either use a key-value Preference Value thing + Name = "Some Preference Name" (like FrameLimitGameplay) + Value = ???, -- a value to set for either the Preference Name or with the associated options enabled below + -- + -- or give it one/all of these (all would probably break it) + SetGame = true/false, -- induces GAMEMAN:SetGame(newGame, curTheme) + SetTheme = true/false, -- induces GAMEMAN:SetGame(currentGame, newTheme) + SetGraphics = true/false, -- induces GAMEMAN:SetGame(currentGame, curTheme) (a graphics reset) + SetLanguage = true/false, -- induces THEME:SwitchThemeAndLanguage(theme, language) + } + ]] + local modsToApplyAtExit = {} + + local function checkModsToApply() + local setGraphics = false + local setGame = nil + local setTheme = nil + local setLanguage = nil + for n, t in pairs(modsToApplyAtExit) do + local usedExtraThing = false -- want to avoid setting a preference and these at the same time + if t.SetGraphics then + setGraphics = t.SetGraphics + -- usedExtraThing = true -- produces funny results but we can skip this + end + if t.SetTheme then + setTheme = t.Value + usedExtraThing = true + end + if t.SetGame then + setGame = t.Value + usedExtraThing = true + end + if t.SetLanguage then + setLanguage = t.Value + usedExtraThing = true + end + if not usedExtraThing then + local prefname = t.Name + local val = t.Value + PREFSMAN:SetPreference(prefname, val) + end + end + modsToApplyAtExit = {} + + -- SetGame - GAMEMAN:SetGame(game, theme) + -- SetTheme - GAMEMAN:SetGame(game, theme) + -- SetGraphics - GAMEMAN:SetGame(game, theme) + -- SetLanguage - THEME:SwitchThemeAndLanguage(theme, language) + local themeToUse = setTheme or THEME:GetCurThemeName() + if setLanguage then + THEME:SwitchThemeAndLanguage(THEME:GetCurThemeName(), setLanguage) + end + if setGraphics and not setGame and not setTheme then + THEME:SetTheme(THEME:GetCurThemeName()) + elseif setGame or setTheme then + local gameToUse = setGame or GAMESTATE:GetCurrentGame():GetName() + GAMEMAN:SetGame(gameToUse, themeToUse) + end + end + + local t = Def.ActorFrame { + Name = "RightFrame", + InitCommand = function(self) + self:playcommand("SetPosition") + self:diffusealpha(0) + end, + HideRightCommand = function(self) + checkModsToApply() + + -- move off screen right and go invisible + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(0) + self:x(offscreenX) + end, + ShowRightCommand = function(self) + -- move on screen from right and go visible + self:finishtweening() + self:smooth(animationSeconds) + self:diffusealpha(1) + self:x(onscreenX) + end, + SetPositionCommand = function(self) + if getWheelPosition() then + onscreenX = SCREEN_WIDTH - actuals.RightWidth + offscreenX = SCREEN_WIDTH + else + onscreenX = 0 + offscreenX = -actuals.RightWidth + end + if focused then + self:x(onscreenX) + else + self:x(offscreenX) + end + end, + + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:valign(0):halign(0) + self:zoomto(actuals.RightWidth, actuals.Height) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "PrimaryBackground") + end, + }, + Def.Quad { + Name = "TopLip", + InitCommand = function(self) + -- height is double normal top lip + self:valign(0):halign(0) + self:zoomto(actuals.RightWidth, actuals.TopLipHeight * 2) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end, + }, + Def.Quad { + Name = "BottomLip", + InitCommand = function(self) + -- height is double normal top lip + self:valign(1):halign(0) + self:y(actuals.Height) + self:zoomto(actuals.RightWidth, actuals.BottomLipHeight) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "main", "SecondaryBackground") + end, + }, + LoadFont("Common Normal") .. { + Name = "HeaderText", + InitCommand = function(self) + self:halign(0) + self:xy(actuals.EdgePadding, actuals.TopLipHeight / 2) + self:zoom(titleTextSize) + self:maxwidth((actuals.RightWidth - actuals.EdgePadding*2) / titleTextSize - textZoomFudge) + self:settext("Options") + registerActorToColorConfigElement(self, "main", "PrimaryText") + end + }, + LoadFont("Common Normal") .. { + Name = "ExplanationText", + InitCommand = function(self) + self:halign(0):valign(0) + self:xy(actuals.EdgePadding, actuals.Height - actuals.BottomLipHeight + actuals.EdgePadding) + self:zoom(explanationTextSize) + --self:maxwidth((actuals.RightWidth - actuals.EdgePadding*2) / explanationTextSize - textZoomFudge) + self:wrapwidthpixels((actuals.RightWidth - actuals.EdgePadding * 2) / explanationTextSize) + self:maxheight((actuals.BottomLipHeight - actuals.EdgePadding * 2) / explanationTextSize) + self:settext(" ") + registerActorToColorConfigElement(self, "main", "PrimaryText") + explanationHandle = self + end, + SetExplanationCommand = function(self, params) + if params and params.text and #params.text > 0 then + -- here we go ... + -- editors note 5 minutes later: i cant believe this works + -- this begins the explainloop below which will slowly write out the desired text + -- it fires a finishtweening when new text is queued here in case we are in the middle of looping + self.txt = params.text + self.pos = 0 + self:finishtweening() + self:settext("") + self:queuecommand("_explainloop") + else + self.txt = "" + self:settext("") + end + end, + _explainloopCommand = function(self) + self.pos = self.pos + 1 + local subtxt = self.txt:sub(1, self.pos) + self:settext(subtxt) + self:sleep(explanationTextWriteAnimationSeconds / #self.txt) + if self.pos < #self.txt then + self:queuecommand("_explainloop") + end + end, + }, + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "PreviewToggle", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + txt:zoom(previewButtonTextSize) + txt:maxwidth(actuals.RightWidth / 2 / previewButtonTextSize - textZoomFudge) + txt:settext("Toggle Chart Preview") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + + bg:halign(0) + bg:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + bg:diffusealpha(0.2) + registerActorToColorConfigElement(bg, "options", "Cursor") + + self:xy(actuals.EdgePadding, actuals.Height - actuals.BottomLipHeight - actuals.BottomLipHeight/4) + -- is this being lazy or being big brained? ive stored a function within an actor instance + self.alphaDeterminingFunction = function(self) + local isOpened = SCUFF.showingPreview + local canBeToggled = SCUFF.showingPreview or (not SCUFF.showingColor and not SCUFF.showingKeybinds and not SCUFF.showingNoteskins) + local alphamultiplier = (isOpened and canBeToggled) and previewOpenedAlpha or 1 + local hovermultiplier = (isOver(bg) and canBeToggled) and buttonHoverAlpha or 1 + local finalalpha = 1 * hovermultiplier * alphamultiplier + self:diffusealpha(finalalpha) + end + end, + PreviewPageOpenStatusChangedMessageCommand = function(self, params) + if self:IsInvisible() then return end + if params and params.opened ~= nil then + self:alphaDeterminingFunction() + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + self:alphaDeterminingFunction() + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if not SCUFF.showingColor and not SCUFF.showingKeybinds and not SCUFF.showingNoteskins and not SCUFF.showingPreview then + MESSAGEMAN:Broadcast("ShowSettingsAlt", {name = "Preview"}) + elseif SCUFF.showingPreview then + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + end + end + end, + } + } + + -- ----- + -- Utility functions for options not necessarily needed for global use in /Scripts (could easily be put there instead though) + + -- set any mod as part of PlayerOptions at all levels in one easy function + local function setPlayerOptionsModValueAllLevels(funcname, ...) + -- you give a funcname like MMod, XMod, CMod and it just works + local poptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Preferred") + local stoptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Stage") + local soptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Song") + local coptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Current") + poptions[funcname](poptions, ...) + stoptions[funcname](stoptions, ...) + soptions[funcname](soptions, ...) + coptions[funcname](coptions, ...) + end + -- set any mod as part of SongOptions at all levels in one easy function + local function setSongOptionsModValueAllLevels(funcname, ...) + -- you give a funcname like MusicRate and it just works + local poptions = GAMESTATE:GetSongOptionsObject("ModsLevel_Preferred") + local stoptions = GAMESTATE:GetSongOptionsObject("ModsLevel_Stage") + local soptions = GAMESTATE:GetSongOptionsObject("ModsLevel_Song") + local coptions = GAMESTATE:GetSongOptionsObject("ModsLevel_Current") + poptions[funcname](poptions, ...) + stoptions[funcname](stoptions, ...) + soptions[funcname](soptions, ...) + coptions[funcname](coptions, ...) + end + + --- for Speed Mods -- this has been adapted from the fallback script which does speed and mode at once + local function getSpeedModeFromPlayerOptions() + local poptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Preferred") + if poptions:MaxScrollBPM() > 0 then + return "M" + elseif poptions:TimeSpacing() > 0 then + return "C" + else + return "X" + end + end + local function getSpeedValueFromPlayerOptions() + local poptions = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Preferred") + if poptions:MaxScrollBPM() > 0 then + return math.round(poptions:MaxScrollBPM()) + elseif poptions:TimeSpacing() > 0 then + return math.round(poptions:ScrollBPM()) + else + return math.round(poptions:ScrollSpeed() * 100) + end + end + + -- for convenience to generate a choice table for a float interface setting + local function floatSettingChoice(visibleName, funcName, enabledValue, offValue) + return { + Name = visibleName, + ChosenFunction = function() + local po = getPlayerOptions() + if po[funcName](po) ~= offValue then + setPlayerOptionsModValueAllLevels(funcName, offValue) + else + setPlayerOptionsModValueAllLevels(funcName, enabledValue) + end + end, + } + end + + -- for convenience to generate a choice table for a boolean interface setting + local function booleanSettingChoice(visibleName, funcName) + return { + Name = visibleName, + ChosenFunction = function() + local po = getPlayerOptions() + if po[funcName](po) == true then + setPlayerOptionsModValueAllLevels(funcName, false) + else + setPlayerOptionsModValueAllLevels(funcName, true) + end + end, + } + end + + -- for convenience to generate a direction table for a setting which goes in either direction and wraps via PREFSMAN + -- if the max value is reached, the min value is the next one + local function preferenceIncrementDecrementDirections(preferenceName, minValue, maxValue, increment) + return { + Left = function() + local x = clamp(PREFSMAN:GetPreference(preferenceName), minValue, maxValue) + x = notShit.round(x - increment, 3) + if x < minValue then x = maxValue end + PREFSMAN:SetPreference(preferenceName, notShit.round(x, 3)) + end, + Right = function() + local x = clamp(PREFSMAN:GetPreference(preferenceName), minValue, maxValue) + x = notShit.round(x + increment, 3) + if x > maxValue then x = minValue end + PREFSMAN:SetPreference(preferenceName, notShit.round(x, 3)) + end, + } + end + + local function basicNamedPreferenceChoice(preferenceName, displayName, chosenValue) + return { + Name = displayName, + ChosenFunction = function() + PREFSMAN:SetPreference(preferenceName, chosenValue) + end, + } + end + local function preferenceToggleDirections(preferenceName, trueValue, falseValue) + return { + Toggle = function() + if PREFSMAN:GetPreference(preferenceName) == trueValue then + PREFSMAN:SetPreference(preferenceName, falseValue) + else + PREFSMAN:SetPreference(preferenceName, trueValue) + end + end, + } + end + local function preferenceToggleIndexGetter(preferenceName, oneValue) + -- oneValue is what we expect for choice index 1 (the first one) + return function() + if PREFSMAN:GetPreference(preferenceName) == oneValue then + return 1 + else + return 2 + end + end + end + -- convenience to create a Choices table for a variable number of choice names + local function choiceSkeleton(...) + local o = {} + for _, name in ipairs({...}) do + o[#o+1] = { + Name = name, + } + end + return o + end + + local function getdataTHEME(category, propertyname) + return function() return themeConfig:get_data()[category][propertyname] end + end + local function setdataTHEME(category, propertyname, val) + themeConfig:get_data()[category][propertyname] = val + themeConfig:set_dirty() + themeConfig:save() + end + local function getdataPLAYER(propertyname) + return function() return playerConfig:get_data()[propertyname] end + end + local function setdataPLAYER(propertyname, val) + playerConfig:get_data()[propertyname] = val + playerConfig:set_dirty() + playerConfig:save() + end + local function themeoption(category, propertyname) + return {get = getdataTHEME(category, propertyname), set = function(x) setdataTHEME(category, propertyname, x) end} + end + local function playeroption(propertyname) + return {get = getdataPLAYER(propertyname), set = function(x) setdataPLAYER(propertyname, x) end} + end + + -- + -- ----- + + -- ----- + -- Extra data for option temporary storage or cross option interaction + -- + local optionData = { + speedMod = { + speed = getSpeedValueFromPlayerOptions(), + mode = getSpeedModeFromPlayerOptions(), + }, + noteSkins = { + names = NOTESKIN:GetNoteSkinNames(), + }, + gameMode = { + modes = GAMEMAN:GetEnabledGames(), + current = GAMESTATE:GetCurrentGame():GetName(), + }, + language = { + list = THEME:GetLanguages(), + current = THEME:GetCurLanguage(), + }, + wheelPosition = themeoption("global", "WheelPosition"), + wheelBanners = themeoption("global", "WheelBanners"), + showBackgrounds = themeoption("global", "ShowBackgrounds"), + showVisualizer = themeoption("global", "ShowVisualizer"), + tipType = themeoption("global", "TipType"), + allowBGChanges = themeoption("global", "StaticBackgrounds"), + videoBanners = themeoption("global", "VideoBanners"), + + -- gameplay elements + bpmDisplay = playeroption("BPMDisplay"), + displayPercent = playeroption("DisplayPercent"), + errorBar = playeroption("ErrorBar"), -- has on, off, ewma + errorBarCount = playeroption("ErrorBarCount"), -- how many bar count + fullProgressBar = playeroption("FullProgressBar"), + miniProgressBar = playeroption("MiniProgressBar"), + judgeCounter = playeroption("JudgeCounter"), + leaderboard = playeroption("leaderboardEnabled"), + displayMean = playeroption("DisplayMean"), + measureCounter = playeroption("MeasureCounter"), + npsDisplay = playeroption("NPSDisplay"), + npsGraph = playeroption("NPSGraph"), + playerInfo = playeroption("PlayerInfo"), + rateDisplay = playeroption("RateDisplay"), + targetTracker = playeroption("TargetTracker"), + targetTrackerMode = playeroption("TargetTrackerMode"), -- 0 is goal, anything else is pb + targetTrackerGoal = playeroption("TargetGoal"), -- only valid for TargetTrackerMode 0 unless no pb + laneCover = playeroption("LaneCover"), -- 0 off, 1 sudden, 2 hidden + judgmentText = playeroption("JudgmentText"), + judgmentTweens = playeroption("JudgmentTweens"), + comboText = playeroption("ComboText"), + comboGlow = playeroption("ComboGlow"), + comboLabel = playeroption("ComboLabel"), + + cbHighlight = playeroption("CBHighlight"), + screenFilter = playeroption("ScreenFilter"), -- [0,1] notefield bg + receptorSize = playeroption("ReceptorSize"), + + pickedTheme = THEME:GetCurThemeName(), + bWindowedBefore = PREFSMAN:GetPreference("Windowed"), + bWindowedNow = PREFSMAN:GetPreference("Windowed") or PREFSMAN:GetPreference("FullscreenIsBorderlessWindow"), + bBorderlessBefore = PREFSMAN:GetPreference("FullscreenIsBorderlessWindow"), + currentAspectRatio = PREFSMAN:GetPreference("DisplayAspectRatio"), + displayHeight = PREFSMAN:GetPreference("DisplayHeight"), + displayWidth = PREFSMAN:GetPreference("DisplayWidth"), + maxTextureResolutionBefore = PREFSMAN:GetPreference("MaxTextureResolution"), + displayColorDepthBefore = PREFSMAN:GetPreference("DisplayColorDepth"), + vsyncBefore = PREFSMAN:GetPreference("Vsync"), + } + + -- + -- ----- + + -- ----- + -- Extra utility functions that require optionData to be initialized first + local function resolutionChoices() + local specs = GetDisplaySpecs() + local isWindowed = optionData.bWindowedNow + local curDisplay = specs:ById(PREFSMAN:GetPreference("DisplayId")) + local curRatio = optionData.currentAspectRatio ~= 0 and optionData.currentAspectRatio + local resolutions = isWindowed and GetFeasibleWindowSizesForRatio(specs, curRatio) + or GetDisplayResolutionsForRatio(curDisplay, curRatio) + + local choices = {} + local vals = {} + table.sort(resolutions, function(a, b) return a.h < b.h end) + for i, r in ipairs(resolutions) do + vals[#vals + 1] = r + choices[#choices + 1] = tostring(r.w) .. 'x' .. tostring(r.h) + end + + return choices, vals + end + local function refreshRateChoices() + local specs = GetDisplaySpecs() + local isWindowed = optionData.bWindowedNow + local d = specs:ById(PREFSMAN:GetPreference("DisplayId")) + local w = optionData.displayWidth + local h = optionData.displayHeight + local rates = isWindowed and {} + or GetDisplayRatesForResolution(d, w, h) + + local choices = {"Default"} + local choiceVals = {REFRESH_DEFAULT} + table.sort(rates) + for i, r in ipairs(rates) do + choiceVals[#choiceVals + 1] = math.round(r) + choices[#choices + 1] = tostring(math.round(r)) + end + return choices, choiceVals + end + local function aspectRatioChoices() + local specs = GetDisplaySpecs() + local isWindowed = optionData.bWindowedNow + local curDisplayId = PREFSMAN:GetPreference("DisplayId") + local dRatios = GetDisplayAspectRatios(specs) + local wRatios = GetWindowAspectRatios() + local ratios = isWindowed and wRatios or (dRatios[curDisplayId] or dRatios[specs[1]:GetId()]) + local choices = {} + local vals = {} + local keys = {} + for k in pairs(ratios) do keys[#keys+1] = k end + for i, k in ipairs(keys) do + local r = ratios[k] + vals[#vals + 1] = r.n / r.d + choices[#choices + 1] = tostring(r.n) .. ':' .. tostring(r.d) + end + return choices, vals + end + local resolutionChoicest = 1 + local refreshRateChoicest = 1 + local aspectRatioChoicest = 1 + -- set the correct starting numbers here + do -- restrict scope pollution + local choices, vals = resolutionChoices() + local w = PREFSMAN:GetPreference("DisplayWidth") + local h = PREFSMAN:GetPreference("DisplayHeight") + local closest = nil + local closestI = 1 + local mindist = -1 + for i, v in ipairs(vals) do + local dist = math.sqrt((v.w - w)^2 + (v.h - h)^2) + if mindist == -1 or dist < mindist then + mindist = dist + closest = v + closestI = i + end + end + resolutionChoicest = closestI + end + do -- again + local choices, vals = refreshRateChoices() + local curRate = PREFSMAN:GetPreference("RefreshRate") + local threshold = 10 + local closestIdx = nil + local mindist = -1 + for i, r in ipairs(vals) do + local dist = math.abs(r - curRate) + if mindist == -1 or dist < mindist then + mindist = dist + closestIdx = i + end + end + refreshRateChoicest = mindist < threshold and closestIdx or 1 + end + do -- again again + local choices, vals = aspectRatioChoices() + local closestRatio = vals[1] + local closestDist = math.abs(vals[1] - optionData.currentAspectRatio) + local closI = 1 + for i, v in ipairs(vals) do + local dist = math.abs(v - optionData.currentAspectRatio) + if dist < closestDist then + closestRatio = v + closestDist = dist + closI = i + end + end + aspectRatioChoicest = closI + end + + + local function setSpeedValueFromOptionData() + local mode = optionData.speedMod.mode + local speed = optionData.speedMod.speed + if mode == "X" then + -- the way we store stuff, xmod must divide by 100 + -- theres no quirk to it, thats just because we store the number as an int (not necessarily an int but yeah) + -- so 0.01x XMod would be a CMod of 1 -- this makes even more sense if you just think about it + setPlayerOptionsModValueAllLevels("XMod", speed/100) + elseif mode == "C" then + setPlayerOptionsModValueAllLevels("CMod", speed) + elseif mode == "M" then + setPlayerOptionsModValueAllLevels("MMod", speed) + end + end + local function basicNamedOptionDataChoice(optionDataPropertyName, displayName, chosenValue) + return { + Name = displayName, + ChosenFunction = function() + optionData[optionDataPropertyName] = chosenValue + end, + } + end + local function optionDataToggleDirections(optionDataPropertyName, trueValue, falseValue) + return { + Toggle = function() + if optionData[optionDataPropertyName] == trueValue then + optionData[optionDataPropertyName] = falseValue + else + optionData[optionDataPropertyName] = trueValue + end + end, + } + end + local function optionDataToggleIndexGetter(optionDataPropertyName, oneValue) + -- oneValue is what we expect for choice index 1 (the first one) + return function() + if optionData[optionDataPropertyName] == oneValue then + return 1 + else + return 2 + end + end + end + local function optionDataToggleDirectionsFUNC(optionDataPropertyName, trueValue, falseValue) + return { + Toggle = function() + if optionData[optionDataPropertyName].get() == trueValue then + optionData[optionDataPropertyName].set(falseValue) + else + optionData[optionDataPropertyName].set(trueValue) + end + end, + } + end + local function optionDataToggleIndexGetterFUNC(optionDataPropertyName, oneValue) + -- oneValue is what we expect for choice index 1 (the first one) + return function() + if optionData[optionDataPropertyName].get() == oneValue then + return 1 + else + return 2 + end + end + end + local function customizeGameplayButton() + return { + Name = "Customize Playfield", + Type = "Button", + Explanation = "Customize Gameplay elements.", + Choices = { + { + Name = "Customize Playfield", + ChosenFunction = function() + -- activate customize gameplay + -- go into gameplay + playerConfig:get_data().CustomizeGameplay = true + + local wheel = SCREENMAN:GetTopScreen():GetChild("WheelFile") + if GAMESTATE:GetCurrentSong() ~= nil then + -- select current + wheel:playcommand("SelectCurrent") + else + -- select random + local group = WHEELDATA:GetRandomFolder() + local song = WHEELDATA:GetRandomSongInFolder(group) + wheel:playcommand("FindSong", {song = song}) + wheel:playcommand("SelectCurrent") + end + end, + } + } + } + end + -- + -- ----- + + local optionRowCount = 18 -- weird behavior if you mess with this and have too many options in a category + local maxChoicesVisibleMultiChoice = 4 -- max number of choices visible in a MultiChoice OptionRow + + -- the names and order of the option pages + -- these values must correspond to the keys of optionPageCategoryLists + local pageNames = { + "Player", + "Gameplay", + "Graphics", + "Sound", + "Input", + "Profiles", + } + + -- mappings of option page names to lists of categories + -- the keys in this table are option pages + -- the values are tables -- the categories of each page in that order + -- each category corresponds to a key in optionDefs (must be unique keys -- values of these tables have to be globally unique) + -- the options of each category are in the order of the value tables in optionDefs + local optionPageCategoryLists = { + Player = { + "Essential Options", + "Appearance Options", + "Invalidating Options", + }, + Gameplay = { + "Gameplay Elements 1", + "Gameplay Elements 2", + }, + Graphics = { + "Global Options", + "Theme Options", + }, + Sound = { + "Sound Options", + }, + Input = { + "Input Options", + }, + Profiles = { + "Profile Options", + }, + } + + -- the mother of all tables. + -- this is each option definition for every single option present in the right frame + -- mapping option categories to lists of options + -- LIMITATIONS: A category cannot have more sub options than the max number of lines minus the number of categories. + -- example: 25 lines? 2 categories? up to 23 options per category. + -- TYPE LIST: + -- SingleChoice -- scrolls through choices + -- SingleChoiceModifier -- scrolls through choices, shows 2 sets of arrows for each direction, allowing multiplier + -- MultiChoice -- shows all options at once, selecting any amount of them + -- Button -- it's a button. you press enter on it. + -- + -- OPTION DEFINITION EXAMPLE: + --[[ + { + Name = "option name" -- display name for the option + Type = "type name" -- determines how to generate the actor to display the choices + AssociatedOptions = {"other option name"} -- runs the index getter for these options when a choice is selected + Choices = { -- option choice definitions -- each entry is another table -- if no choices are defined, visible choice comes from ChoiceIndexGetter + { + Name = "choice1" -- display name for the choice + ChosenFunction = function() end -- what happens when choice is PICKED (not hovered) + }, + { + Name = "choice2" + ... + }, + ... + }, + Directions = { + -- table of direction functions -- these define what happens for each pressed direction button + -- most options have only Left and Right + -- if these functions are undefined and required by the option type, a default function moves the index of the choice rotationally + -- some option types may allow for more directions or direction multipliers + -- if Toggle is defined, this function is used for all direction presses + Left = function() end, + Right = function() end, + Toggle = function() end, --- OPTIONAL -- WILL REPLACE ALL DIRECTION FUNCTIONALITY IF PRESENT + ... + }, + ChoiceIndexGetter = function() end -- a function to run to get the choice index or text, or return a table for multi selection options + ChoiceGenerator = function() end -- an OPTIONAL function for generating the choices table if too long to write out (return a table) + Explanation = "" -- an explanation that appears at the bottom of the screen + } + ]] + local optionDefs = { + ----- + -- PLAYER OPTIONS + ["Essential Options"] = { + { + Name = "Scroll Type", + Type = "SingleChoice", + Explanation = "XMod - BPM multiplier based scrolling. CMod - Constant scrolling. MMod - BPM based with a max speed.", + AssociatedOptions = { + "Scroll Speed", + }, + Choices = choiceSkeleton("XMod", "CMod", "MMod"), + Directions = { + Left = function() + -- traverse list left, set the speed mod again + -- order: + -- XMOD - CMOD - MMOD + local mode = optionData.speedMod.mode + if mode == "C" then + mode = "X" + elseif mode == "M" then + mode = "C" + elseif mode == "X" then + mode = "M" + end + optionData.speedMod.mode = mode + setSpeedValueFromOptionData() + end, + Right = function() + -- traverse list right, set the speed mod again + -- order: + -- XMOD - CMOD - MMOD + local mode = optionData.speedMod.mode + if mode == "C" then + mode = "M" + elseif mode == "M" then + mode = "X" + elseif mode == "X" then + mode = "C" + end + optionData.speedMod.mode = mode + setSpeedValueFromOptionData() + end, + }, + ChoiceIndexGetter = function() + local mode = optionData.speedMod.mode + if mode == "X" then return 1 + elseif mode == "C" then return 2 + elseif mode == "M" then return 3 end + end, + }, + { + Name = "Scroll Speed", + Type = "SingleChoiceModifier", + Explanation = "Change scroll speed value/modifier in increments of 1 or 50.", + Directions = { + Left = function(multiplier) + local increment = -1 + if multiplier then increment = -50 end + optionData.speedMod.speed = optionData.speedMod.speed + increment + if optionData.speedMod.speed <= 0 then optionData.speedMod.speed = 1 end + setSpeedValueFromOptionData() + end, + Right = function(multiplier) + local increment = 1 + if multiplier then increment = 50 end + optionData.speedMod.speed = optionData.speedMod.speed + increment + if optionData.speedMod.speed <= 0 then optionData.speedMod.speed = 1 end + setSpeedValueFromOptionData() + end, + }, + ChoiceIndexGetter = function() + local mode = optionData.speedMod.mode + local speed = optionData.speedMod.speed + if mode == "X" then + return mode .. notShit.round((speed/100), 2) + else + return mode .. speed + end + end, + }, + { + Name = "Scroll Direction", + Type = "SingleChoice", + Explanation = "Direction of note scrolling: up or down.", + Choices = choiceSkeleton("Upscroll", "Downscroll"), + Directions = { + Toggle = function() + if not getPlayerOptions():UsingReverse() then + -- 1 is 100% reverse which means on + setPlayerOptionsModValueAllLevels("Reverse", 1) + else + -- 0 is 0% reverse which means off + setPlayerOptionsModValueAllLevels("Reverse", 0) + end + MESSAGEMAN:Broadcast("UpdateReverse") + end, + }, + ChoiceIndexGetter = function() + if getPlayerOptions():UsingReverse() then + return 2 + else + return 1 + end + end, + }, + { + Name = "Noteskin", + Type = "SingleChoice", + Explanation = "Skin of the notes.", + ChoiceIndexGetter = function() + local currentSkinName = getPlayerOptions():NoteSkin() + for i, name in ipairs(optionData.noteSkins.names) do + if name == currentSkinName then + return i + end + end + -- if function gets this far, look for the default skin + currentSkinName = THEME:GetMetric("Common", "DefaultNoteSkinName") + for i, name in ipairs(optionData.noteSkins.names) do + if name == currentSkinName then + return i + end + end + -- if function gets this far, cant find anything so just return the first skin + return 1 + end, + ChoiceGenerator = function() + local o = {} + local skinNames = NOTESKIN:GetNoteSkinNames() + for i, name in ipairs(skinNames) do + o[#o+1] = { + Name = name:upper(), + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("NoteSkin", name) + MESSAGEMAN:Broadcast("UpdateVisibleSkin", {name = name}) + end, + } + end + table.sort( + o, + function(a, b) + return a.Name:lower() < b.Name:lower() + end) + + return o + end, + }, + { + Name = "Receptor Size", + Type = "SingleChoice", + Explanation = "Size of receptors and notes. 50% Receptor Size may be called 100% Mini.", + Directions = { + Left = function() + local sz = optionData["receptorSize"].get() + sz = sz - 1 + if sz < 1 then sz = 200 end + optionData["receptorSize"].set(sz) + end, + Right = function() + local sz = optionData["receptorSize"].get() + sz = sz + 1 + if sz > 200 then sz = 1 end + optionData["receptorSize"].set(sz) + end, + }, + ChoiceIndexGetter = function() + return optionData["receptorSize"].get() .. "%" + end, + }, + { + Name = "Judge Difficulty", + Type = "SingleChoice", + Explanation = "Timing Window Difficulty. Higher is harder. All scores are converted to Judge 4 later.", + ChoiceIndexGetter = function() + local lowestJudgeDifficulty = 4 + return GetTimingDifficulty() - (lowestJudgeDifficulty-1) + end, + ChoiceGenerator = function() + local o = {} + for i = 4, 8 do + o[#o+1] = { + Name = tostring(i), + ChosenFunction = function() + -- set judge + local scale = ms.JudgeScalers[i] + if scale == nil then scale = 1 end + SetTimingDifficulty(scale) + end, + } + end + o[#o+1] = { + Name = "Justice", + ChosenFunction = function() + -- sets j9 + local scale = ms.JudgeScalers[9] + if scale == nil then scale = 1 end + SetTimingDifficulty(scale) + end, + } + return o + end, + }, + { + Name = "Global Offset", + Type = "SingleChoice", + Explanation = "Global Audio Offset in seconds. Negative numbers are early.", + Directions = preferenceIncrementDecrementDirections("GlobalOffsetSeconds", -5, 5, 0.001), + ChoiceIndexGetter = function() + return notShit.round(PREFSMAN:GetPreference("GlobalOffsetSeconds"), 3) .. "s" + end, + }, + { + Name = "Visual Delay", + Type = "SingleChoice", + Explanation = "Visual Note Delay in seconds. May be referred to as Judge Offset. Negative numbers are early.", + Directions = preferenceIncrementDecrementDirections("VisualDelaySeconds", -5, 5, 0.001), + ChoiceIndexGetter = function() + return notShit.round(PREFSMAN:GetPreference("VisualDelaySeconds"), 3) .. "s" + end, + }, + { + Name = "Game Mode", + Type = "SingleChoice", + Explanation = "Dance - 3k/4k/8k | Solo - 6k | Pump - 5k/6k/10k | Beat - 5k+1/7k+1/10k+2/14k+2 | Kb7 - 7k | Popn - 5k/9k", + ChoiceIndexGetter = function() + for i = 1, #optionData.gameMode.modes do + if optionData.gameMode.modes[i] == optionData.gameMode.current then + return i + end + end + return 1 + end, + ChoiceGenerator = function() + local o = {} + for i, name in ipairs(optionData.gameMode.modes) do + o[#o+1] = { + Name = strCapitalize(name), + ChosenFunction = function() + if name == GAMESTATE:GetCurrentGame():GetName() then + modsToApplyAtExit["GameMode"] = nil + else + modsToApplyAtExit["GameMode"] = { + Name = "Game", + Value = name, + SetGame = true, + } + end + optionData.gameMode.current = name + end, + } + end + return o + end, + }, + { + Name = "Fail Type", + Type = "SingleChoice", + Explanation = "Toggle failure in Gameplay. Setting Fail Off invalidates scores if a fail would have actually occurred.", + ChoiceIndexGetter = function() + local failtypes = FailType + local failtype = getPlayerOptions():FailSetting() + for i, name in ipairs(failtypes) do + if name == failtype then return i end + end + return 1 + end, + ChoiceGenerator = function() + -- get the list of fail types + local failtypes = FailType + local o = {} + for i, name in ipairs(failtypes) do + o[#o+1] = { + Name = THEME:GetString("OptionNames", ToEnumShortString(name)), + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("FailSetting", name) + end, + } + end + return o + end, + }, + customizeGameplayButton(), + { + Name = "Customize Keybinds", + Type = "Button", + Explanation = "Customize Keybinds.", + Choices = { + { + Name = "Customize Keybinds", + ChosenFunction = function() + -- activate keybind screen + MESSAGEMAN:Broadcast("ShowSettingsAlt", {name = "Customize Keybinds"}) + end, + } + } + }, + { + Name = "Enter Practice Mode", + Type = "Button", + Explanation = "Enter Practice Mode", + Choices = { + { + Name = "Enter Practice Mode", + ChosenFunction = function() + -- activate practice mode + -- go into gameplay + playerConfig:get_data().PracticeMode = true + GAMESTATE:SetPracticeMode(true) + + local wheel = SCREENMAN:GetTopScreen():GetChild("WheelFile") + if GAMESTATE:GetCurrentSong() ~= nil then + -- select current + wheel:playcommand("SelectCurrent") + else + -- select random + local group = WHEELDATA:GetRandomFolder() + local song = WHEELDATA:GetRandomSongInFolder(group) + wheel:playcommand("FindSong", {song = song}) + wheel:playcommand("SelectCurrent") + end + end, + } + } + } + }, + -- + ----- + -- APPEARANCE OPTIONS + ["Appearance Options"] = { + { + Name = "Appearance", + Type = "MultiChoice", + Explanation = "Hidden - Notes disappear before receptor. Sudden - Notes appear later than usual. Stealth - Invisible notes. Blink - Notes flash.", + AssociatedOptions = { + "Hidden Offset", + "Sudden Offset", + }, + Choices = { + -- multiple choices allowed + floatSettingChoice("Hidden", "Hidden", 1, 0), + floatSettingChoice("Sudden", "Sudden", 1, 0), + floatSettingChoice("Stealth", "Stealth", 1, 0), + floatSettingChoice("Blink", "Blink", 1, 0) + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Hidden() ~= 0 then o[1] = true end + if po:Sudden() ~= 0 then o[2] = true end + if po:Stealth() ~= 0 then o[3] = true end + if po:Blink() ~= 0 then o[4] = true end + return o + end, + }, + { + Name = "Hidden Offset", + Type = "SingleChoiceModifier", + Explanation = "Offset the Hidden position. Hidden hides notes just before they reach the receptors.", + AssociatedOptions = { + "Appearance", + }, + Directions = { + Left = function(multiplier) + local po = getPlayerOptions() + local increment = -0.01 + if multiplier then increment = -0.05 end + local v = wrapulo(po:HiddenOffset() * 100, increment * 100, -200, 200) / 100 + if v ~= 0 then + setPlayerOptionsModValueAllLevels("Hidden", 1) + setPlayerOptionsModValueAllLevels("HiddenOffset", notShit.round(v, 2)) + else + setPlayerOptionsModValueAllLevels("HiddenOffset", 0) + end + end, + Right = function(multiplier) + local po = getPlayerOptions() + local increment = 0.01 + if multiplier then increment = 0.05 end + local v = wrapulo(po:HiddenOffset() * 100, increment * 100, -200, 200) / 100 + if v ~= 0 then + setPlayerOptionsModValueAllLevels("Hidden", 1) + setPlayerOptionsModValueAllLevels("HiddenOffset", notShit.round(v, 2)) + else + setPlayerOptionsModValueAllLevels("HiddenOffset", 0) + end + end, + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local hv = po:Hidden() + if hv == 0 then + setPlayerOptionsModValueAllLevels("HiddenOffset", 0) + end + local v = po:HiddenOffset() + return notShit.round(v * 100, 0) .. "%" + end, + }, + { + Name = "Sudden Offset", + Type = "SingleChoiceModifier", + Explanation = "Offset the Sudden position. Sudden hides notes until they pass a certain distance across the screen.", + AssociatedOptions = { + "Appearance", + }, + Directions = { + Left = function(multiplier) + local po = getPlayerOptions() + local increment = -0.01 + if multiplier then increment = -0.05 end + local v = wrapulo(po:SuddenOffset() * 100, increment * 100, -200, 200) / 100 + if v ~= 0 then + setPlayerOptionsModValueAllLevels("Sudden", 1) + setPlayerOptionsModValueAllLevels("SuddenOffset", notShit.round(v, 2)) + else + setPlayerOptionsModValueAllLevels("SuddenOffset", 0) + end + end, + Right = function(multiplier) + local po = getPlayerOptions() + local increment = 0.01 + if multiplier then increment = 0.05 end + local v = wrapulo(po:SuddenOffset() * 100, increment * 100, -200, 200) / 100 + if v ~= 0 then + setPlayerOptionsModValueAllLevels("Sudden", 1) + setPlayerOptionsModValueAllLevels("SuddenOffset", notShit.round(v, 2)) + else + setPlayerOptionsModValueAllLevels("SuddenOffset", 0) + end + end, + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local sv = po:Sudden() + if sv == 0 then + setPlayerOptionsModValueAllLevels("SuddenOffset", 0) + end + local v = po:SuddenOffset() + return notShit.round(v * 100, 0) .. "%" + end, + }, + { + Name = "Perspective", + Type = "SingleChoice", + Explanation = "Controls tilt/skew of the NoteField.", + AssociatedOptions = { + "Perspective Intensity", + }, + Choices = { + -- the numbers in these defs are like the percentages you would put in metrics instead + -- 1 is 100% + -- Overhead does not use percentages. + -- adding an additional parameter to these functions does do something (approach rate) but is functionally useless + -- you are free to try these untested options for possible weird results: + -- setPlayerOptionsModValueAllLevels("Skew", x) + -- setPlayerOptionsModValueAllLevels("Tilt", x) + { + Name = "Overhead", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("Overhead", true) + end, + }, + { + Name = "Incoming", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("Incoming", 1) + end, + }, + { + Name = "Space", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("Space", 1) + end, + }, + { + Name = "Hallway", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("Hallway", 1) + end, + }, + { + Name = "Distant", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("Distant", 1) + end, + }, + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + -- we unfortunately choose to hardcode these options and not allow an additional custom one + -- but the above choice definitions allow customizing the specific Perspective to whatever extent you want + local o = {} + if po:Overhead() then return 1 + elseif po:Incoming() ~= nil then return 2 + elseif po:Space() ~= nil then return 3 + elseif po:Hallway() ~= nil then return 4 + elseif po:Distant() ~= nil then return 5 + end + return o + end, + }, + { + Name = "Perspective Intensity", + Type = "SingleChoiceModifier", + Explanation = "Controls the intensity of the tilt/skew of the NoteField.", + Directions = { + Left = function(multiplier) + local po = getPlayerOptions() + local increment = -0.01 + if multiplier then increment = -0.05 end + local vs = { + "Incoming", + "Space", + "Hallway", + "Distant", + } + local activeMod = "Overhead" + for _,v in ipairs(vs) do + if po[v](po) ~= nil then activeMod = v break end + end + if activeMod ~= "Overhead" then + local v = wrapulo(po[activeMod](po) * 100, increment * 100, 1, 200) / 100 + setPlayerOptionsModValueAllLevels(activeMod, notShit.round(v, 2)) + else + -- do nothing. overhead cannot be changed from 100% + end + end, + Right = function(multiplier) + local po = getPlayerOptions() + local increment = 0.01 + if multiplier then increment = 0.05 end + local vs = { + "Incoming", + "Space", + "Hallway", + "Distant", + } + local activeMod = "Overhead" + for _,v in ipairs(vs) do + if po[v](po) ~= nil then activeMod = v break end + end + if activeMod ~= "Overhead" then + local v = wrapulo(po[activeMod](po) * 100, increment * 100, 1, 200) / 100 + setPlayerOptionsModValueAllLevels(activeMod, notShit.round(v, 2)) + else + -- do nothing. overhead cannot be changed from 100% + end + end, + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + if po:Overhead() then + return "--" + end + local vs = { + "Incoming", + "Space", + "Hallway", + "Distant", + } + for _,v in ipairs(vs) do + local vv = po[v](po) + if vv ~= nil then return notShit.round(vv * 100, 0) .. "%" end + end + return "???" + end, + }, + { + Name = "Mirror", + Type = "SingleChoice", + Explanation = "Horizontally flip Notedata.", + Choices = choiceSkeleton("On", "Off"), + Directions = { + Toggle = function() + local po = getPlayerOptions() + if po:Mirror() then + setPlayerOptionsModValueAllLevels("Mirror", false) + else + setPlayerOptionsModValueAllLevels("Mirror", true) + end + end, + }, + ChoiceIndexGetter = function() + if getPlayerOptions():Mirror() then + return 1 + else + return 2 + end + end, + }, + { + Name = "Hide Player UI", + Type = "MultiChoice", + Explanation = "Hide certain sets of elements from the Gameplay UI.", + Choices = { + floatSettingChoice("Hide Receptors", "Dark", 1, 0), + floatSettingChoice("Hide Judgment & Combo", "Blind", 1, 0), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Dark() ~= 0 then o[1] = true end + if po:Blind() ~= 0 then o[2] = true end + return o + end, + }, + { + Name = "Hidenote Judgment", + Type = "SingleChoice", + Explanation = "Notes must be hit with this judgment or better to disappear.", + Choices = { + { + Name = "Miss", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "Miss") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "Miss") + end, + }, + { + Name = "Bad", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "W5") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "W5") + end, + }, + { + Name = "Good", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "W4") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "W4") + end, + }, + { + Name = "Great", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "W3") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "W3") + end, + }, + { + Name = "Perfect", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "W2") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "W2") + end, + }, + { + Name = "Marvelous", + ChosenFunction = function() + PREFSMAN:SetPreference("MinTNSToHideNotes", "W1") + setPlayerOptionsModValueAllLevels("MinTNSToHideNotes", "W1") + end, + }, + }, + ChoiceIndexGetter = function() + local opt = PREFSMAN:GetPreference("MinTNSToHideNotes") + if opt == "TapNoteScore_Miss" then return 1 + elseif opt == "TapNoteScore_W5" then return 2 + elseif opt == "TapNoteScore_W4" then return 3 + elseif opt == "TapNoteScore_W3" then return 4 + elseif opt == "TapNoteScore_W2" then return 5 + elseif opt == "TapNoteScore_W1" then return 6 + else + return 4 -- this is the default option so default to this + end + end, + }, + { + Name = "Default Centered NoteField", + Type = "SingleChoice", + Explanation = "Horizontally center the NoteField in Gameplay (Legacy Shortcut).", + Choices = choiceSkeleton("Yes", "No"), + Directions = preferenceToggleDirections("Center1Player", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("Center1Player", true), + }, + { + Name = "NoteField BG Opacity", + Type = "SingleChoice", + Explanation = "Set the opacity of the board behind the NoteField in Gameplay.", + ChoiceGenerator = function() + local o = {} + for i = 0, 10 do -- 11 choices + o[#o+1] = { + Name = notShit.round(i*10,0).."%", + ChosenFunction = function() + optionData["screenFilter"].set(notShit.round(i / 10, 1)) + end, + } + end + return o + end, + ChoiceIndexGetter = function() + local v = notShit.round(optionData["screenFilter"].get(), 1) + local ind = notShit.round(v * 10, 0) + 1 + if ind > 0 and ind < 11 then -- this 11 should match the number of choices above + return ind + else + if ind <= 0 then + return 1 + else + return 11 + end + end + end, + }, + { + Name = "Background Brightness", + Type = "SingleChoice", + Explanation = "Set the brightness of the background in Gameplay. 0% will disable background loading.", + ChoiceGenerator = function() + local o = {} + for i = 0, 10 do -- 11 choices + o[#o+1] = { + Name = notShit.round(i*10,0).."%", + ChosenFunction = function() + PREFSMAN:SetPreference("BGBrightness", notShit.round(i / 10, 1)) + end, + } + end + return o + end, + ChoiceIndexGetter = function() + local v = notShit.round(PREFSMAN:GetPreference("BGBrightness")) + local ind = notShit.round(v * 10, 0) + 1 + if ind > 0 and ind < 11 then -- this 11 should match the nubmer of choices above + return ind + else + if ind <= 0 then + return 1 + else + return 11 + end + end + end, + }, + { + Name = "Replay Mod Emulation", + Type = "SingleChoice", + Explanation = "Toggle temporarily using compatible mods that replays used when watching them.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("ReplaysUseScoreMods", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("ReplaysUseScoreMods", true), + }, + { + Name = "Extra Scroll Mods", + Type = "MultiChoice", + Explanation = "Change scroll direction in more interesting ways.", + Choices = { + floatSettingChoice("Split", "Split", 1, 0), + floatSettingChoice("Alternate", "Alternate", 1, 0), + floatSettingChoice("Cross", "Cross", 1, 0), + floatSettingChoice("Centered", "Centered", 1, 0), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Split() ~= 0 then o[1] = true end + if po:Alternate() ~= 0 then o[2] = true end + if po:Cross() ~= 0 then o[3] = true end + if po:Centered() ~= 0 then o[4] = true end + return o + end, + }, + { + Name = "Fun Effects", + Type = "MultiChoice", + Explanation = "Visual scroll mods that are not for practical use.", + Choices = { + floatSettingChoice("Drunk", "Drunk", 1, 0), + floatSettingChoice("Confusion", "Confusion", 1, 0), + floatSettingChoice("Tiny", "Tiny", 1, 0), + floatSettingChoice("Flip", "Flip", 1, 0), + floatSettingChoice("Invert", "Invert", 1, 0), + floatSettingChoice("Tornado", "Tornado", 1, 0), + floatSettingChoice("Tipsy", "Tipsy", 1, 0), + floatSettingChoice("Bumpy", "Bumpy", 1, 0), + floatSettingChoice("Beat", "Beat", 1, 0), + -- X-Mode is dead because it relies on player 2!! -- floatSettingChoice("X-Mode"), + floatSettingChoice("Twirl", "Twirl", 1, 0), + floatSettingChoice("Roll", "Roll", 1, 0), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Drunk() ~= 0 then o[1] = true end + if po:Confusion() ~= 0 then o[2] = true end + if po:Tiny() ~= 0 then o[3] = true end + if po:Flip() ~= 0 then o[4] = true end + if po:Invert() ~= 0 then o[5] = true end + if po:Tornado() ~= 0 then o[6] = true end + if po:Tipsy() ~= 0 then o[7] = true end + if po:Bumpy() ~= 0 then o[8] = true end + if po:Beat() ~= 0 then o[9] = true end + if po:Twirl() ~= 0 then o[10] = true end + if po:Roll() ~= 0 then o[11] = true end + return o + end, + }, + { + Name = "Acceleration", + Type = "MultiChoice", + Explanation = "Scroll speed mods usually not for practical use.", + Choices = { + floatSettingChoice("Boost", "Boost", 1, 0), + floatSettingChoice("Brake", "Brake", 1, 0), + floatSettingChoice("Wave", "Wave", 1, 0), + floatSettingChoice("Expand", "Expand", 1, 0), + floatSettingChoice("Boomerang", "Boomerang", 1, 0), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Boost() ~= 0 then o[1] = true end + if po:Brake() ~= 0 then o[2] = true end + if po:Wave() ~= 0 then o[3] = true end + if po:Expand() ~= 0 then o[4] = true end + if po:Boomerang() ~= 0 then o[5] = true end + return o + end, + } + }, + -- + ----- + -- INVALIDATING OPTIONS + ["Invalidating Options"] = { + { + Name = "Mines", + Type = "SingleChoice", + Explanation = "Toggle Mines. Extra Mines will replace entire rows of notes with mines.", + Choices = { + { + Name = "On", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("NoMines", false) + setPlayerOptionsModValueAllLevels("Mines", false) + end, + }, + { + Name = "Off", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("NoMines", true) + setPlayerOptionsModValueAllLevels("Mines", false) + end, + }, + { + Name = "Extra Mines", + ChosenFunction = function() + setPlayerOptionsModValueAllLevels("NoMines", false) + setPlayerOptionsModValueAllLevels("Mines", true) + end, + } + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + if po:Mines() then + -- additive mines, invalidating + return 3 + elseif po:NoMines() then + -- nomines, invalidating + return 2 + else + -- regular mines, not invalidating + return 1 + end + end, + }, + { + Name = "Turn", + Type = "MultiChoice", + Explanation = "Modify Notedata by either shifting all notes or randomizing them.", + Choices = { + booleanSettingChoice("Backwards", "Backwards"), + booleanSettingChoice("Left", "Left"), + booleanSettingChoice("Right", "Right"), + booleanSettingChoice("Shuffle", "Shuffle"), + booleanSettingChoice("Soft Shuffle", "SoftShuffle"), + booleanSettingChoice("Super Shuffle", "SuperShuffle"), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Backwards() then o[1] = true end + if po:Left() then o[2] = true end + if po:Right() then o[3] = true end + if po:Shuffle() then o[4] = true end + if po:SoftShuffle() then o[5] = true end + if po:SuperShuffle() then o[6] = true end + return o + end, + }, + { + Name = "Pattern Transform", + Type = "MultiChoice", + Explanation = "Modify Notedata by inserting extra notes to create certain patterns.", + Choices = { + booleanSettingChoice("Echo", "Echo"), + booleanSettingChoice("Stomp", "Stomp"), + booleanSettingChoice("Jack JS", "JackJS"), + booleanSettingChoice("Anchor JS", "AnchorJS"), + booleanSettingChoice("IcyWorld", "IcyWorld"), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Echo() then o[1] = true end + if po:Stomp() then o[2] = true end + if po:JackJS() then o[3] = true end + if po:AnchorJS() then o[4] = true end + if po:IcyWorld() then o[5] = true end + return o + end, + }, + { + Name = "Hold Transform", + Type = "MultiChoice", + Explanation = "Modify holds in Notedata.", + Choices = { + booleanSettingChoice("Planted", "Planted"), + booleanSettingChoice("Floored", "Floored"), + booleanSettingChoice("Twister", "Twister"), + booleanSettingChoice("Holds To Rolls", "HoldRolls"), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Planted() then o[1] = true end + if po:Floored() then o[2] = true end + if po:Twister() then o[3] = true end + if po:HoldRolls() then o[4] = true end + return o + end, + }, + { + Name = "Remove", + Type = "MultiChoice", + Explanation = "Remove certain notes, patterns, or types of notes.", + Choices = { + booleanSettingChoice("No Holds", "NoHolds"), + booleanSettingChoice("No Rolls", "NoRolls"), + booleanSettingChoice("No Jumps", "NoJumps"), + booleanSettingChoice("No Hands", "NoHands"), + booleanSettingChoice("No Lifts", "NoLifts"), + booleanSettingChoice("No Fakes", "NoFakes"), + booleanSettingChoice("No Quads", "NoQuads"), + booleanSettingChoice("No Stretch", "NoStretch"), + booleanSettingChoice("Little", "Little"), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:NoHolds() then o[1] = true end + if po:NoRolls() then o[2] = true end + if po:NoJumps() then o[3] = true end + if po:NoHands() then o[4] = true end + if po:NoLifts() then o[5] = true end + if po:NoFakes() then o[6] = true end + if po:NoQuads() then o[7] = true end + if po:NoStretch() then o[8] = true end + if po:Little() then o[9] = true end + return o + end, + }, + { + Name = "Insert", + Type = "MultiChoice", + Explanation = "Modify Notedata by inserting extra notes to provide a certain feeling.", + Choices = { + booleanSettingChoice("Wide", "Wide"), + booleanSettingChoice("Big", "Big"), + booleanSettingChoice("Quick", "Quick"), + booleanSettingChoice("BMR-ize", "BMRize"), + booleanSettingChoice("Skippy", "Skippy"), + }, + ChoiceIndexGetter = function() + local po = getPlayerOptions() + local o = {} + if po:Wide() then o[1] = true end + if po:Big() then o[2] = true end + if po:Quick() then o[3] = true end + if po:BMRize() then o[4] = true end + if po:Skippy() then o[5] = true end + return o + end, + } + }, + -- + ----- + -- GAMEPLAY ELEMENTS P1 + ["Gameplay Elements 1"] = { + customizeGameplayButton(), + { + Name = "BPM Display", + Type = "SingleChoice", + Explanation = "Toggle the BPM display.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("bpmDisplay", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("bpmDisplay", true), + }, + { + Name = "Rate Display", + Type = "SingleChoice", + Explanation = "Toggle the music rate display.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("rateDisplay", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("rateDisplay", true), + }, + { + Name = "Percent Display", + Type = "SingleChoice", + Explanation = "Toggle the wife percent display.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("displayPercent", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("displayPercent", true), + }, + { + Name = "Mean Display", + Type = "SingleChoice", + Explanation = "Toggle the tap mean display.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("displayMean", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("displayMean", true), + }, + { + Name = "Error Bar", + Type = "SingleChoice", + Explanation = "Toggle the error bar. Regular displays your recent tap offsets. EWMA displays the exponential mean weighted average of recent taps.", + Choices = { + { + Name = "Regular", + ChosenFunction = function() + optionData["errorBar"].set(1) + end, + }, + { + Name = "EWMA", + ChosenFunction = function() + optionData["errorBar"].set(2) + end, + }, + { + Name = "Off", + ChosenFunction = function() + optionData["errorBar"].set(0) + end, + }, + }, + ChoiceIndexGetter = function() + local v = optionData["errorBar"].get() + if v == 0 then + return 3 -- off + elseif v == 1 then + return 1 -- on + else + return 2 -- ewma + end + end, + }, + { + Name = "Error Bar Count", + Type = "SingleChoice", + Explanation = "Choose either how many taps are allowed to show for the Regular error bar, or how many taps are considered for the EWMA error bar.", + ChoiceGenerator = function() + local o = {} + for i = 1, 50 do + o[#o+1] = { + Name = i, + ChosenFunction = function() + optionData["errorBarCount"].set(i) + end, + } + end + return o + end, + ChoiceIndexGetter = function() + local v = optionData["errorBarCount"].get() + if v < 1 or v > 50 then + return 1 + else + return v + end + end, + }, + { + Name = "Full Progress Bar", + Type = "SingleChoice", + Explanation = "Toggle the large progress bar.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("fullProgressBar", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("fullProgressBar", true), + }, + { + Name = "Mini Progress Bar", + Type = "SingleChoice", + Explanation = "Toggle the small progress bar.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("miniProgressBar", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("miniProgressBar", true), + }, + + { + Name = "Leaderboard", + Type = "SingleChoice", + Explanation = "Toggle the gameplay leaderboard.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("leaderboard", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("leaderboard", true), + }, + + { + Name = "Player Info", + Type = "SingleChoice", + Explanation = "Toggle the miscellaneous player info display.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("playerInfo", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("playerInfo", true), + }, + { + Name = "Target Tracker", + Type = "SingleChoice", + Explanation = "Toggle the target tracker. This displays your score and points relative to a target.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("targetTracker", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("targetTracker", true), + }, + { + Name = "Target Tracker Mode", + Type = "SingleChoice", + Explanation = "Toggle the target tracker mode. PB uses your PB unless it is does not exist. Then it falls back to Goal Percent.", + Choices = { + { + Name = "Personal Best", + ChosenFunction = function() + optionData["targetTrackerMode"].set(1) + end, + }, + { + Name = "Goal Percent", + ChosenFunction = function() + optionData["targetTrackerMode"].set(0) + end, + }, + }, + ChoiceIndexGetter = function() + local v = optionData["targetTrackerMode"].get() + if v == 0 then + return 2 -- goal + else + return 1 -- pb + end + end, + }, + { + Name = "Target Tracker Goal", + Type = "SingleChoice", + Explanation = "Pick a goal percent for the target tracker.", + ChoiceGenerator = function() + local o = {} + local function cf(i) + return { + Name = tostring(notShit.round(i, 4)), + ChosenFunction = function() optionData["targetTrackerGoal"].set(notShit.round(i, 4)) end + } + end + for i = 0, 46 do + o[#o+1] = cf(i + 50) + end + -- extra choices to fit grades + o[#o+1] = cf(96.65) -- AA. + o[#o+1] = cf(97) + o[#o+1] = cf(98) + o[#o+1] = cf(99) -- AA: + o[#o+1] = cf(99.25) + o[#o+1] = cf(99.5) + o[#o+1] = cf(99.7) -- AAA + o[#o+1] = cf(99.8) + o[#o+1] = cf(99.9) + o[#o+1] = cf(99.955) -- AAAA + o[#o+1] = cf(99.97) + o[#o+1] = cf(99.98) + o[#o+1] = cf(99.9935) -- AAAAA + return o + end, + ChoiceIndexGetter = function() + local v = optionData["targetTrackerGoal"].get() + if v < 50 or v > 99.995 then + return 1 + elseif v <= 96 then + return v - 50 + 1 + elseif v > 96 then + local extra = { + 96.65, + 97, + 98, + 99, + 99.25, + 99.5, + 99.7, + 99.8, + 99.9, + 99.955, + 99.97, + 99.98, + 99.9935, + } + for i,vv in ipairs(extra) do + if v == vv then + return 47 + i + end + end + return 47 + end + end, + }, + { + Name = "Lane Cover", + Type = "SingleChoice", + Explanation = "Toggle the lane cover. Hidden is on top of the receptors. Sudden is away from the receptors.", + Choices = { + { + Name = "Hidden", + ChosenFunction = function() + optionData["laneCover"].set(2) + end, + }, + { + Name = "Sudden", + ChosenFunction = function() + optionData["laneCover"].set(1) + end, + }, + { + Name = "Off", + ChosenFunction = function() + optionData["laneCover"].set(0) + end, + } + }, + ChoiceIndexGetter = function() + local v = optionData["laneCover"].get() + if v == 0 then + return 3 -- off + elseif v == 1 then + return 2 -- sudden + else + return 1 -- hidden + end + end, + }, + }, + -- + ----- + -- GAMEPLAY ELEMENTS P2 + ["Gameplay Elements 2"] = { + customizeGameplayButton(), + { + Name = "Judge Counter", + Type = "SingleChoice", + Explanation = "Toggle the judgment counter.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("judgeCounter", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("judgeCounter", true), + }, + { + Name = "Judgment Text", + Type = "SingleChoice", + Explanation = "Toggle the judgment text.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("judgmentText", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("judgmentText", true), + }, + { + Name = "Judgment Animations", + Type = "SingleChoice", + Explanation = "Toggle the judgment text animations.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("judgmentTweens", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("judgmentTweens", true), + }, + { + Name = "Combo Text", + Type = "SingleChoice", + Explanation = "Toggle the combo text.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("comboText", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("comboText", true), + }, + { + Name = "Combo Glow", + Type = "SingleChoice", + Explanation = "Toggle the extra white glow on the Combo numbers during MFCs and PFCs.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("comboGlow", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("comboGlow", true), + }, + { + Name = "Combo Label", + Type = "SingleChoice", + Explanation = "Toggle the word 'Combo' as part of the combo text.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("comboLabel", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("comboLabel", true), + }, + { + Name = "Combo-Breaker Highlights", + Type = "SingleChoice", + Explanation = "Toggle showing which column a combo breaker occurs.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("cbHighlight", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("cbHighlight", true), + }, + { + Name = "Measure Counter", + Type = "SingleChoice", + Explanation = "Toggle the measure counter. This shows up for longer runs of relatively high NPS.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("measureCounter", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("measureCounter", true), + }, + { + Name = "NPS Display", + Type = "SingleChoice", + Explanation = "Toggle the notes per second display. Displays just NPS and max NPS.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("npsDisplay", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("npsDisplay", true), + }, + { + Name = "NPS Graph", + Type = "SingleChoice", + Explanation = "Toggle the notes per second graph. Displays the NPS over time.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("npsGraph", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("npsGraph", true), + }, + }, + -- + ----- + -- GLOBAL GRAPHICS OPTIONS + ["Global Options"] = { + { + Name = "Language", + Type = "SingleChoice", + Explanation = "Modify the game language.", + ChoiceIndexGetter = function() + for i, l in ipairs(optionData.language.list) do + if l == optionData.language.current then + return i + end + end + return 1 + end, + ChoiceGenerator = function() + local o = {} + for i, l in ipairs(optionData.language.list) do + o[#o+1] = { + Name = l:upper(), + ChosenFunction = function() + if l == THEME:GetCurLanguage() then + modsToApplyAtExit["Language"] = nil + else + modsToApplyAtExit["Language"] = { + Name = "Language", + Value = l, + SetLanguage = true, + } + end + optionData.language.current = l + end, + } + end + return o + end, + }, + { + Name = "Theme", + Type = "SingleChoice", + Explanation = "Change the overall skin of the game.", + ChoiceIndexGetter = function() + local cur = optionData.pickedTheme + for i, name in ipairs(THEME:GetSelectableThemeNames()) do + if name == cur then return i end + end + return 1 + end, + ChoiceGenerator = function() + local o = {} + for _, name in ipairs(THEME:GetSelectableThemeNames()) do + o[#o+1] = { + Name = name, + ChosenFunction = function() + if name == THEME:GetCurThemeName() then + modsToApplyAtExit["Theme"] = nil + else + modsToApplyAtExit["Theme"] = { + Name = "Theme", + Value = name, + SetTheme = true, + } + end + optionData.pickedTheme = name + end, + } + end + return o + end, + }, + { + Name = "Display Mode", + Type = "SingleChoice", + Explanation = "Change the game display mode. Borderless requires that you select your native fullscreen resolution.", + AssociatedOptions = { + "Aspect Ratio", + "Display Resolution", + "Refresh Rate", + }, + -- the idea behind Display Mode is to also allow selecting a Display to show the game + -- it is written into the lua side of the c++ options conf but unused everywhere as far as i know except maybe in linux + -- so here lets just hardcode windowed/fullscreen until that feature becomes a certain reality + -- and lets add borderless here so that the options are simplified just a bit + Choices = { + { + Name = "Windowed", + ChosenFunction = function() + PREFSMAN:SetPreference("Windowed", true) + PREFSMAN:SetPreference("FullscreenIsBorderlessWindow", false) + optionData.bWindowedNow = true + if optionData.bWindowedBefore and not optionData.bBorderlessBefore then + modsToApplyAtExit["Windowed"] = nil + modsToApplyAtExit["Borderless"] = nil + else + modsToApplyAtExit["Windowed"] = { + Name = "Windowed", + Value = true, + SetGraphics = true, + } + modsToApplyAtExit["Borderless"] = { + Name = "FullscreenIsBorderlessWindow", + Value = false, + SetGraphics = true, + } + end + end, + }, + { + Name = "Fullscreen", + ChosenFunction = function() + PREFSMAN:SetPreference("Windowed", false) + PREFSMAN:SetPreference("FullscreenIsBorderlessWindow", false) + optionData.bWindowedNow = false + if not optionData.bWindowedBefore and not optionData.bBorderlessBefore then + modsToApplyAtExit["Windowed"] = nil + modsToApplyAtExit["Borderless"] = nil + else + modsToApplyAtExit["Windowed"] = { + Name = "Windowed", + Value = false, + SetGraphics = true, + } + modsToApplyAtExit["Borderless"] = { + Name = "FullscreenIsBorderlessWindow", + Value = false, + SetGraphics = true, + } + end + end, + }, + { + -- funny thing about this preference is that it doesnt force fullscreen + -- so you have to pick the right resolution for it to work + Name = "Borderless", + ChosenFunction = function() + PREFSMAN:SetPreference("Windowed", false) + PREFSMAN:SetPreference("FullscreenIsBorderlessWindow", true) + optionData.bWindowedNow = true + if not optionData.bWindowedBefore and optionData.bBorderlessBefore then + modsToApplyAtExit["Windowed"] = nil + modsToApplyAtExit["Borderless"] = nil + else + modsToApplyAtExit["Windowed"] = { + Name = "Windowed", + Value = false, + SetGraphics = true, + } + modsToApplyAtExit["Borderless"] = { + Name = "FullscreenIsBorderlessWindow", + Value = true, + SetGraphics = true, + } + end + end, + } + + }, + ChoiceIndexGetter = function() + if PREFSMAN:GetPreference("FullscreenIsBorderlessWindow") then + return 3 + elseif PREFSMAN:GetPreference("Windowed") then + return 1 + else + -- fullscreen exclusive + return 2 + end + end, + }, + { + Name = "Aspect Ratio", + Type = "SingleChoice", + Explanation = "Change the game aspect ratio.", + AssociatedOptions = { + "Display Resolution", + "Refresh Rate", + }, + Directions = { + Left = function() + aspectRatioChoicest = aspectRatioChoicest - 1 + end, + Right = function() + aspectRatioChoicest = aspectRatioChoicest + 1 + end, + }, + ChoiceIndexGetter = function() + local choices, vals = aspectRatioChoices() + if aspectRatioChoicest > #choices then aspectRatioChoicest = 1 end + if aspectRatioChoicest < 1 then aspectRatioChoicest = #choices end + local v = vals[aspectRatioChoicest] + local choice = choices[aspectRatioChoicest] + + if math.abs(v - PREFSMAN:GetPreference("DisplayAspectRatio")) < 0.044 then + -- same + modsToApplyAtExit["AspectRatio"] = nil + else + -- not + modsToApplyAtExit["AspectRatio"] = { + Name = "DisplayAspectRatio", + Value = v, + SetGraphics = true, + } + end + optionData.currentAspectRatio = v + + return choice + end, + }, + { + Name = "Display Resolution", + Type = "SingleChoice", + Explanation = "Change the game display resolution.", + AssociatedOptions = { + "Aspect Ratio", + "Refresh Rate", + }, + Directions = { + Left = function() + resolutionChoicest = resolutionChoicest - 1 + end, + Right = function() + resolutionChoicest = resolutionChoicest + 1 + end, + }, + ChoiceIndexGetter = function() + local choices, vals = resolutionChoices() + if resolutionChoicest > #choices then resolutionChoicest = 1 end + if resolutionChoicest < 1 then resolutionChoicest = #choices end + local v = vals[resolutionChoicest] + local choice = choices[resolutionChoicest] + + if v.w == PREFSMAN:GetPreference("DisplayWidth") and v.h == PREFSMAN:GetPreference("DisplayHeight") then + modsToApplyAtExit["DisplayWidth"] = nil + modsToApplyAtExit["DisplayHeight"] = nil + else + modsToApplyAtExit["DisplayWidth"] = { + Name = "DisplayWidth", + Value = v.w, + SetGraphics = true, + } + modsToApplyAtExit["DisplayHeight"] = { + Name = "DisplayHeight", + Value = v.h, + SetGraphics = true, + } + end + optionData.displayHeight = v.h + optionData.displayWidth = v.w + + return choice + end, + }, + { + Name = "Refresh Rate", + Type = "SingleChoice", + Explanation = "Change the game refresh rate. Set to default in most cases. Changes the refresh rate, but not the FPS cap. Only applies in exclusive fullscreen.", + AssociatedOptions = { + "Aspect Ratio", + "Display Resolution", + }, + Directions = { + Left = function() + refreshRateChoicest = refreshRateChoicest - 1 + end, + Right = function() + refreshRateChoicest = refreshRateChoicest + 1 + end, + }, + ChoiceIndexGetter = function() + local choices, vals = refreshRateChoices() + if refreshRateChoicest > #choices then refreshRateChoicest = 1 end + if refreshRateChoicest < 1 then refreshRateChoicest = #choices end + local v = vals[refreshRateChoicest] + local choice = choices[refreshRateChoicest] + + if v == PREFSMAN:GetPreference("RefreshRate") then + modsToApplyAtExit["RefreshRate"] = nil + else + modsToApplyAtExit["RefreshRate"] = { + Name = "RefreshRate", + Value = v, + SetGraphics = true, + } + end + + return choice + end, + }, + { + Name = "Display Color Depth", + Type = "SingleChoice", + Explanation = "Change the color depth of the game according to your display. Usually not worth changing.", + Choices = { + basicNamedPreferenceChoice("DisplayColorDepth", "16bit", 16), + basicNamedPreferenceChoice("DisplayColorDepth", "32bit", 32), + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("DisplayColorDepth") + if v == optionData.displayColorDepthBefore then + modsToApplyAtExit["DisplayColorDepth"] = nil + else + modsToApplyAtExit["DisplayColorDepth"] = { + Name = "DisplayColorDepth", + Value = v, + SetGraphics = true, + } + end + if v == 16 then return 1 + elseif v == 32 then return 2 + end + return 1 + end, + }, + { + Name = "Force High Resolution Textures", + Type = "SingleChoice", + Explanation = "Force high resolution textures. Turning this off disables the (doubleres) image tag.", + Choices = { + { + Name = "Auto", + ChosenFunction = function() + local v = "HighResolutionTextures_Auto" + PREFSMAN:SetPreference("HighResolutionTextures", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["HighResolutionTextures"] = nil + else + modsToApplyAtExit["HighResolutionTextures"] = { + Name = "HighResolutionTextures", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "Force On", + ChosenFunction = function() + local v = "HighResolutionTextures_ForceOn" + PREFSMAN:SetPreference("HighResolutionTextures", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["HighResolutionTextures"] = nil + else + modsToApplyAtExit["HighResolutionTextures"] = { + Name = "HighResolutionTextures", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "Force Off", + ChosenFunction = function() + local v = "HighResolutionTextures_ForceOff" + PREFSMAN:SetPreference("HighResolutionTextures", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["HighResolutionTextures"] = nil + else + modsToApplyAtExit["HighResolutionTextures"] = { + Name = "HighResolutionTextures", + Value = v, + SetGraphics = true, + } + end + end, + }, + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("HighResolutionTextures") + if v == "HighResolutionTextures_Auto" then return 1 + elseif v == "HighResolutionTextures_ForceOn" then return 2 + else return 3 end + end, + }, + { + Name = "Texture Resolution", + Type = "SingleChoice", + Explanation = "Modify general texture resolution. Lower number will lower quality but may increase FPS.", + -- FUN FACT YOU CAN PUT ANY NUMBER IN FOR THESE + -- AS LONG AS IT ISNT INSANE OR 0 IT SHOULD WORK + Choices = { + { + Name = "256", + ChosenFunction = function() + local v = 256 + PREFSMAN:SetPreference("MaxTextureResolution", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["MaxTextureResolution"] = nil + else + modsToApplyAtExit["MaxTextureResolution"] = { + Name = "MaxTextureResolution", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "512", + ChosenFunction = function() + local v = 512 + PREFSMAN:SetPreference("MaxTextureResolution", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["MaxTextureResolution"] = nil + else + modsToApplyAtExit["MaxTextureResolution"] = { + Name = "MaxTextureResolution", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "1024", + ChosenFunction = function() + local v = 1024 + PREFSMAN:SetPreference("MaxTextureResolution", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["MaxTextureResolution"] = nil + else + modsToApplyAtExit["MaxTextureResolution"] = { + Name = "MaxTextureResolution", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "2048", + ChosenFunction = function() + local v = 2048 + PREFSMAN:SetPreference("MaxTextureResolution", v) + if v == optionData.maxTextureResolutionBefore then + modsToApplyAtExit["MaxTextureResolution"] = nil + else + modsToApplyAtExit["MaxTextureResolution"] = { + Name = "MaxTextureResolution", + Value = v, + SetGraphics = true, + } + end + end, + }, + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("MaxTextureResolution") + if v == 256 then return 1 + elseif v == 512 then return 2 + elseif v == 1024 then return 3 + elseif v == 2048 then return 4 + end + return 1 + end, + }, + --[[ + { + Name = "Texture Color Depth", + Type = "SingleChoice", + Explanation = "Change the color depth of the textures in the game. Usually not worth changing.", + Choices = { + basicNamedPreferenceChoice("TextureColorDepth", "16bit", 16), + basicNamedPreferenceChoice("TextureColorDepth", "32bit", 32), + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("TextureColorDepth") + if v == 16 then return 1 + elseif v == 32 then return 2 + end + return 1 + end, + }, + { + Name = "Movie Color Depth", + Type = "SingleChoice", + Explanation = "Change the color depth of the movie textures in the game. Usually not worth changing.", + Choices = { + basicNamedPreferenceChoice("MovieColorDepth", "16bit", 16), + basicNamedPreferenceChoice("MovieColorDepth", "32bit", 32), + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("MovieColorDepth") + if v == 16 then return 1 + elseif v == 32 then return 2 + end + return 1 + end, + }, + ]] + { + Name = "VSync", + Type = "SingleChoice", + Explanation = "Restrict the game refresh rate and FPS to the refresh rate you have set.", + Choices = { + { + Name = "On", + ChosenFunction = function() + local v = true + PREFSMAN:SetPreference("Vsync", v) + if v == optionData.vsyncBefore then + modsToApplyAtExit["Vsync"] = nil + else + modsToApplyAtExit["Vsync"] = { + Name = "Vsync", + Value = v, + SetGraphics = true, + } + end + end, + }, + { + Name = "Off", + ChosenFunction = function() + local v = false + PREFSMAN:SetPreference("Vsync", v) + if v == optionData.vsyncBefore then + modsToApplyAtExit["Vsync"] = nil + else + modsToApplyAtExit["Vsync"] = { + Name = "Vsync", + Value = v, + SetGraphics = true, + } + end + end, + }, + }, + ChoiceIndexGetter = function() + local v = PREFSMAN:GetPreference("Vsync") + if v then return 1 else return 2 end + end, + }, + { + Name = "Fast Note Rendering", + Type = "SingleChoice", + Explanation = "Optimize gameplay note rendering. Disable snap based noteskin features (not snaps themselves). Major boost to FPS.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("FastNoteRendering", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("FastNoteRendering", true), + }, + { + Name = "Show Stats", + Type = "SingleChoice", + Explanation = "Show FPS display on screen.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("ShowStats", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("ShowStats", true), + }, + { + Name = "Tap Glow", + Type = "SingleChoice", + Explanation = "Show a white flash before notes disappear when using Hidden or Sudden.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("NoGlow", false, true), + ChoiceIndexGetter = preferenceToggleIndexGetter("NoGlow", false), + } + }, + -- + ----- + -- THEME OPTIONS + ["Theme Options"] = { + { + Name = "Music Wheel Position", + Type = "SingleChoice", + Explanation = "Set the side of the screen for the music wheel.", + Choices = choiceSkeleton("Left", "Right"), + Directions = optionDataToggleDirectionsFUNC("wheelPosition", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("wheelPosition", true), + }, + { + Name = "Music Wheel Banners", + Type = "SingleChoice", + Explanation = "Toggle the banners on the music wheel.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("wheelBanners", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("wheelBanners", true), + }, + { + Name = "Video Banners", + Type = "SingleChoice", + Explanation = "Toggle allowing video banners to play at all on the wheel and other locations in music select.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("videoBanners", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("videoBanners", true), + }, + { + Name = "Show Backgrounds", + Type = "SingleChoice", + Explanation = "Toggle showing backgrounds everywhere.", + Choices = choiceSkeleton("Yes", "No"), + Directions = optionDataToggleDirectionsFUNC("showBackgrounds", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("showBackgrounds", true), + }, + { + Name = "Allow Background Changes", + Type = "SingleChoice", + Explanation = "Toggle gameplay backgrounds changing.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("allowBGChanges", false, true), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("allowBGChanges", false), + }, + { + Name = "Easter Eggs & Toasties", + Type = "SingleChoice", + Explanation = "Toggle showing secret jokes and toasties.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("EasterEggs", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("EasterEggs", true), + }, + { + Name = "Music Visualizer", + Type = "SingleChoice", + Explanation = "Toggle showing the visualizer in the song select screen.", + Choices = choiceSkeleton("On", "Off"), + Directions = optionDataToggleDirectionsFUNC("showVisualizer", true, false), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("showVisualizer", true), + }, + { + Name = "Mid Grades", + Type = "SingleChoice", + Explanation = "Toggle showing the grades in between the major grades. Requires game restart.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("UseMidGrades", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("UseMidGrades", true), + }, + { + Name = "SSRNorm Sort", + Type = "SingleChoice", + Explanation = "Toggle automatically sorting by and defaulting to the SSRNorm globally. The SSRNorm is the Judge 4 value of a highscore. Requires game restart.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("SortBySSRNormPercent", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("SortBySSRNormPercent", true), + }, + { + Name = "Show Lyrics", + Type = "SingleChoice", + Explanation = "Toggle showing lyrics for songs which contain compatible .lrc files.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("ShowLyrics", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("ShowLyrics", true), + }, + { + Name = "Transliteration", + Type = "SingleChoice", + Explanation = "Toggle showing author-defined translations on song metadata fields.", + Choices = choiceSkeleton("On", "Off"), + Directions = { + Toggle = function() + if PREFSMAN:GetPreference("ShowNativeLanguage") then + PREFSMAN:SetPreference("ShowNativeLanguage", false) + else + PREFSMAN:SetPreference("ShowNativeLanguage", true) + end + MESSAGEMAN:Broadcast("DisplayLanguageChanged") + end, + }, + ChoiceIndexGetter = preferenceToggleIndexGetter("ShowNativeLanguage", true), + }, + { + Name = "Tip Type", + Type = "SingleChoice", + Explanation = "Change the quips shown at the bottom of the evaluation screen.", + Choices = choiceSkeleton("Tips", "Quotes"), + Directions = optionDataToggleDirectionsFUNC("tipType", 1, 2), + ChoiceIndexGetter = optionDataToggleIndexGetterFUNC("tipType", 1), + }, + { + Name = "Set BG Fit Mode", + Type = "SingleChoice", + Explanation = "Change the cropping strategy of background images.", + ChoiceGenerator = function() + local o = {} + for _, fit in ipairs(BackgroundFitMode) do + o[#o+1] = { + Name = THEME:GetString("ScreenSetBGFit", ToEnumShortString(fit)), + ChosenFunction = function() + PREFSMAN:SetPreference("BackgroundFitMode", ToEnumShortString(fit)) + end, + } + end + return o + end, + ChoiceIndexGetter = function() + local cur = PREFSMAN:GetPreference("BackgroundFitMode") + for i, fit in ipairs(BackgroundFitMode) do + if "BackgroundFitMode_"..ToEnumShortString(fit) == cur then + return i + end + end + return 1 + end, + }, + { + Name = "Color Config", + Type = "Button", + Explanation = "Modify the colors of this theme.", + Choices = { + { + Name = "Color Config", + ChosenFunction = function() + -- activate color config screen + MESSAGEMAN:Broadcast("ShowSettingsAlt", {name = "Color Config"}) + end, + }, + } + }, + { + Name = "Asset Settings", + Type = "Button", + Explanation = "Set your avatar, judgments, and toasty.", + Choices = { + { + Name = "Asset Settings", + ChosenFunction = function() + -- activate asset settings screen + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "AssetSettings", prevScreen = "Settings"}) + end, + }, + } + }, + }, + -- + ----- + -- SOUND OPTIONS + ["Sound Options"] = { + { + Name = "Volume", + Type = "SingleChoice", + Explanation = "All sound volume.", + Directions = { + Left = function() + local x = PREFSMAN:GetPreference("SoundVolume") + x = notShit.round(x - 0.01, 3) + if x < 0 then x = 1 end + SOUND:SetVolume(notShit.round(x, 3)) + end, + Right = function() + local x = PREFSMAN:GetPreference("SoundVolume") + x = notShit.round(x + 0.01, 3) + if x > 1 then x = 0 end + SOUND:SetVolume(notShit.round(x, 3)) + end, + }, + ChoiceIndexGetter = function() + return notShit.round(PREFSMAN:GetPreference("SoundVolume") * 100, 0) .. "%" + end, + }, + { + Name = "Menu Sounds", + Type = "SingleChoice", + Explanation = "Toggle sounds on menu items.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("MuteActions", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("MuteActions", false), + }, + { + Name = "Mine Sounds", + Type = "SingleChoice", + Explanation = "Toggle sounds for mine explosions.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("EnableMineHitSound", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("EnableMineHitSound", true), + }, + { + Name = "Pitch on Rates", + Type = "SingleChoice", + Explanation = "Toggle pitch changes for songs when using rates.", + Choices = choiceSkeleton("On", "Off"), + Directions = preferenceToggleDirections("EnablePitchRates", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("EnablePitchRates", true), + }, + { + Name = "Calibrate Audio Sync", + Type = "Button", + Explanation = "Calibrate the audio sync for the entire game.", + Choices = { + { + Name = "Calibrate Audio Sync", + ChosenFunction = function() + -- go to machine sync screen + SCUFF.screenAfterSyncMachine = SCREENMAN:GetTopScreen():GetName() + SCREENMAN:set_input_redirected(PLAYER_1, false) + SCREENMAN:SetNewScreen("ScreenGameplaySyncMachine") + end, + }, + }, + }, + }, + -- + ----- + -- INPUT OPTIONS + ["Input Options"] = { + { + Name = "Back Delayed", + Type = "SingleChoice", + Explanation = "Modify the behavior of the back button in gameplay.", + Choices = choiceSkeleton("Hold", "Instant"), + Directions = preferenceToggleDirections("DelayedBack", true, false), + ChoiceIndexGetter = preferenceToggleIndexGetter("DelayedBack", true), + }, + { + Name = "Input Debounce Time", + Type = "SingleChoice", + Explanation = "Set the amount of time required between each repeated input.", + Directions = preferenceIncrementDecrementDirections("InputDebounceTime", 0, 1, 0.01), + ChoiceIndexGetter = function() + return notShit.round(PREFSMAN:GetPreference("InputDebounceTime"), 2) .. "s" + end, + }, + { + Name = "Test Input", + Type = "Button", + Explanation = "Enter a screen to test all input devices.", + Choices = { + { + Name = "Test Input", + ChosenFunction = function() + -- go to test input screen + SCUFF.screenAfterSyncMachine = SCREENMAN:GetTopScreen():GetName() + SCREENMAN:set_input_redirected(PLAYER_1, false) + SCREENMAN:SetNewScreen("ScreenTestInput") + end, + } + } + }, + }, + -- + ----- + -- PROFILE OPTIONS + ["Profile Options"] = { + { + Name = "Create Profile", + Type = "Button", + Explanation = "Create a new profile.", + Choices = { + { + Name = "Create Profile", + ChosenFunction = function() + -- make a profile + -- make profile, rename new profile + local new = PROFILEMAN:CreateDefaultProfile() + renameProfileDialogue(new, true) + end, + } + } + }, + { + Name = "Rename Profile", + Type = "Button", + Explanation = "Rename an existing profile.", + Choices = { + { + Name = "Rename Profile", + ChosenFunction = function() + -- rename a profile + renameProfileDialogue(GetPlayerOrMachineProfile(PLAYER_1)) + end, + } + } + }, + }, + } + -- check for choice generators on any option definitions and execute them + for categoryName, categoryDefinition in pairs(optionDefs) do + for i, optionDef in ipairs(categoryDefinition) do + if optionDef.Choices == nil and optionDef.ChoiceGenerator ~= nil then + optionDefs[categoryName][i].Choices = optionDef.ChoiceGenerator() + end + end + end + + -- internal tracker for where the cursor can be and has been within a row + -- the index of each entry is simply the row number on the right side of the screen + -- for a context switch to the left, those are managed by each respective panel separately + -- format: (each entry) + --[[{ + NumChoices = x, -- number of choices, simply. 0 means this is a button to press. 1 is a SingleChoice. N is MultiChoice + HighlightedChoice = x, -- position of the highlighted choice. 1 for Single/Button. N for MultiChoice. Account for the pagination (in other visual representations, not here). + LinkedItem = x, -- either a category name or an optionDef + } ]] + local availableCursorPositions = {} + local rightPaneCursorPosition = 1 -- current index of the above table + + -- container function/frame for all option rows + local function createOptionRows() + -- Unfortunate design choice: + -- For every option row, we are going to place every single possible row type. + -- This means there's a ton of invisible elements. + -- Is this worth doing? This is better than telling the C++ to let us generate and destroy arbitrary Actors at runtime + -- (I consider this dangerous and also too complex to implement) + -- So instead we "carefully" manage all pieces of an option row... + -- Luckily we can be intelligent about wasting space. + -- First, we parse all of the optionData to see which choices need what elements. + -- We pass that information on to the rows (we can precalculate which rows have what choices) + -- This way we can avoid generating Actor elements which will never be used in a row + + -- Alternative to doing the above and below: + -- just use ActorFrame.RemoveChild and ActorFrame.AddChildFromPath + + -- table of row index keys to lists of row types + -- valid row types are in the giant option definition comment block + local rowTypes = {} + -- table of row index keys to counts of how many text objects to generate + -- this should correlate to how many choices are possible in a row on any option page + local rowChoiceCount = {} + for _, optionPage in ipairs(pageNames) do + for i, categoryName in ipairs(optionPageCategoryLists[optionPage]) do + local categoryDefinition = optionDefs[categoryName] + + -- declare certain rows are categories + -- (current row and the remaining rows after the set of options in this category) + if rowTypes[i] ~= nil then + rowTypes[i]["Category"] = true + else + rowTypes[i] = {Category = true} + end + for ii = (i+1), (#optionPageCategoryLists[optionPage]) do + local categoryRowIndex = ii + #categoryDefinition + if rowTypes[categoryRowIndex] ~= nil then + rowTypes[categoryRowIndex]["Category"] = true + else + rowTypes[categoryRowIndex] = {Category = true} + end + end + + for j, optionDef in ipairs(categoryDefinition) do + local rowIndex = j + i -- skip the rows for option category names + + -- option types for every row + if rowTypes[rowIndex] ~= nil then + rowTypes[rowIndex][optionDef.Type] = true + else + rowTypes[rowIndex] = {[optionDef.Type] = true} + end + + -- option choice count for every row + local rcc = rowChoiceCount[rowIndex] + if rcc == nil then + rowChoiceCount[rowIndex] = 0 + rcc = 0 + end + local defcount = #(optionDef.Choices or {}) + -- the only case we should show multiple choices is for MultiChoice... + if optionDef.Type ~= "MultiChoice" then defcount = 1 end + if rcc < defcount then + rowChoiceCount[rowIndex] = defcount + end + end + end + end + + -- updates the explanation text. + local function updateExplainText(self) + if self.defInUse ~= nil and self.defInUse.Explanation ~= nil then + if explanationHandle ~= nil then + if explanationHandle.txt ~= self.defInUse.Explanation then + explanationHandle:playcommand("SetExplanation", {text = self.defInUse.Explanation}) + end + else + explanationHandle:playcommand("SetExplanation", {text = ""}) + end + else + explanationHandle:playcommand("SetExplanation", {text = ""}) + end + end + + ----- state variables, dont mess + -- currently selected options page - from pageNames + local selectedPageName = pageNames[1] -- default to first + local selectedPageDef = optionPageCategoryLists[selectedPageName] + -- currently opened option category - from optionPageCategoryLists + local openedCategoryName = selectedPageDef[1] -- default to first + local openedCategoryDef = optionDefs[openedCategoryName] + -- index of the opened option category to know the index of the first valid option row to assign option defs + local openedCategoryIndex = 1 + local optionRowContainer = nil + + -- fills out availableCursorPositions based on current conditions of the above variables + local function generateCursorPositionMap() + availableCursorPositions = {} + rightPaneCursorPosition = 1 + + -- theres a list of categories on the page (selectedPageDef) + -- theres a category that is opened on this page (openedCategoryDef) + -- add each category up to and including the opened category to the list + -- then add each option to the list + -- then add the rest of the categories to the list + -- (this is the same as how we display the options below somewhere) + -- we assume openedCategoryIndex is correct at all times + -- also assume you cannot close an opened Category except by opening a different category or page + + -- add each category up to and including the opened category + for i = 1, openedCategoryIndex do + local opened = false + if i == openedCategoryIndex then opened = true end + availableCursorPositions[#availableCursorPositions+1] = { + NumChoices = 0, + HighlightedChoice = 1, + LinkedItem = { + Opened = opened, + Name = selectedPageDef[i], + }, + } + end + + -- put the cursor on the first option after the opened category + rightPaneCursorPosition = openedCategoryIndex+1 + + -- add each option in the category + for i = 1, #openedCategoryDef do + local def = openedCategoryDef[i] + local nchoices = 0 + if def.Type == "Button" then + nchoices = 0 + elseif def.Type == "SingleChoice" or def.Type == "SingleChoiceModifier" then + -- naturally we would let people hover and press the second set of buttons in SingleChoiceModifier but i would rather force that to be a ctrl+direction instead + -- that seems a little more fluid than moving to the directional button and pressing it + nchoices = 1 + elseif def.Type == "MultiChoice" then + nchoices = #(def.Choices or {}) + end + availableCursorPositions[#availableCursorPositions+1] = { + NumChoices = nchoices, + HighlightedChoice = 1, + LinkedItem = def, + } + end + + -- add each category remaining after the last option + for i = openedCategoryIndex+1, #selectedPageDef do + availableCursorPositions[#availableCursorPositions+1] = { + NumChoices = 0, + HighlightedChoice = 1, + LinkedItem = { + Opened = false, + Name = selectedPageDef[i], + } + } + end + + -- and if things turn out broken at this point it isnt my fault + end + + -- find the ActorFrame for an OptionRow by an Option Name + local function getRowForCursorByName(name) + if optionRowContainer == nil then return nil end + + for i, row in ipairs(optionRowContainer:GetChildren()) do + if row.defInUse ~= nil and row.defInUse.Name == name then + return row + end + end + return nil + end + + -- find the (cursor) index of an OptionRow by an Option Name + local function getRowIndexByName(name) + if availableCursorPositions == nil then return nil end + + for i, cursorRowDef in ipairs(availableCursorPositions) do + if cursorRowDef.LinkedItem ~= nil and cursorRowDef.LinkedItem.Name == name then + return i + end + end + return 1 + end + + -- find the ActorFrame for the OptionRow that is currently hovered by the cursor + local function getRowForCursorByCurrentPosition() + -- correct error or just do index wrap around + if rightPaneCursorPosition > #availableCursorPositions then rightPaneCursorPosition = 1 end + if rightPaneCursorPosition < 1 then rightPaneCursorPosition = #availableCursorPositions end + + return optionRowContainer:GetChild("OptionRow_"..rightPaneCursorPosition) + end + + local function getActorForCursorToHoverByCurrentConditions() + local optionRowFrame = getRowForCursorByCurrentPosition() + if optionRowFrame == nil then ms.ok("BAD CURSOR REPORT TO DEVELOPER") return end + local optionRowDef = optionRowFrame.defInUse + if optionRowDef == nil then ms.ok("BAD CURSOR ROWDEF REPORT TO DEVELOPER") return end + + -- place the cursor to highlight this item (usually ActorFrame containing BitmapText as child "Text") + local actorToHover = nil + + -- based on the type, place the cursor in specific positions (the positions are memorized in availableCursorPositions too) + if optionRowDef.Type == nil then + -- optionDefs without Type should always be Category defs + -- simply hover the title in this case + -- pressing enter would open the category unless it is already opened + actorToHover = optionRowFrame:GetChild("TitleText") + else + -- these are Option defs, not Categories + if optionRowDef.Type == "Button" then + -- Button hovers the title text + -- pressing enter on it is a single action + actorToHover = optionRowFrame:GetChild("TitleText") + elseif optionRowDef.Type == "SingleChoice" or optionRowDef.Type == "SingleChoiceModifier" then + -- SingleChoice[Modifier] hovers the single visible choice + -- pressing enter does nothing, only left and right function + actorToHover = optionRowFrame:safeGetChild("ChoiceFrame", "Choice_1") + elseif optionRowDef.Type == "MultiChoice" then + -- MultiChoice hovers one of the visible choices + -- the visible choice is dependent on the value of availableCursorPositions[i].HighlightedChoice + -- account here, rather than in stored data, for pagination of the choices + -- otherwise a dead choice is picked and we look dumb + local cursorPosDef = availableCursorPositions[rightPaneCursorPosition] + local pagesize = math.min(maxChoicesVisibleMultiChoice, cursorPosDef.NumChoices) + if pagesize > cursorPosDef.HighlightedChoice then + -- if the cursor is on the first page no special math required + actorToHover = optionRowFrame:safeGetChild("ChoiceFrame", "Choice_"..cursorPosDef.HighlightedChoice) + else + -- if the cursor is not on the first page check to see where it lands + -- (i already spent 5 minutes thinking on the math for this and i got bored so what follows is the best you get) + local choiceIndex = cursorPosDef.HighlightedChoice % pagesize + if choiceIndex == 0 then choiceIndex = pagesize end -- really intuitive, right? + actorToHover = optionRowFrame:safeGetChild("ChoiceFrame", "Choice_"..choiceIndex) + end + else + ms.ok("BAD CURSOR ROWDEF TYPE REPORT TO DEVELOPER") + return nil + end + end + return actorToHover + end + + -- place the cursor based on the current conditions of rightPaneCursorPosition and availableCursorPositions + local function setCursorPositionByCurrentConditions() + local optionRowFrame = getRowForCursorByCurrentPosition() + if optionRowFrame == nil then ms.ok("BAD CURSOR REPORT TO DEVELOPER") return end + local optionRowDef = optionRowFrame.defInUse + if optionRowDef == nil then ms.ok("BAD CURSOR ROWDEF REPORT TO DEVELOPER") return end + local actorToHover = getActorForCursorToHoverByCurrentConditions() + + if actorToHover == nil then + ms.ok("BAD CURSOR PLACEMENT LOGIC OR DEF REPORT TO DEVELOPER") + return + end + + -- at the time of writing all actorToHover should be an ActorFrame with a child "Text" + -- this is a TextButton + local txt = actorToHover:GetChild("Text") + local cursorActor = optionRowContainer:GetChild("OptionCursor") + local xp = txt:GetTrueX() - optionRowContainer:GetTrueX() + local beforeYPos = cursorActor:GetY() + + -- these positions should be relative to optionRowContainer so it should work out fine + cursorActor:finishtweening() + cursorActor:smooth(animationSeconds) + cursorActor:xy(xp, optionRowFrame:GetY() + actorToHover:GetY() + txt:GetY()) + cursorActor:zoomto(txt:GetZoomedWidth(), txt:GetZoomedHeight() * 1.5) + + -- tell the game that we moved the option cursor to this row + -- dont care if it didnt move + MESSAGEMAN:Broadcast("OptionCursorUpdated", {name = optionRowDef.Name, choiceName = txt:GetText()}) + end + + -- function for pressing enter wherever the cursor is + local function invokeCurrentCursorPosition() + local actorToHover = getActorForCursorToHoverByCurrentConditions() + local cursorPosDef = availableCursorPositions[rightPaneCursorPosition] + + if actorToHover == nil or cursorPosDef == nil or cursorPosDef.LinkedItem == nil then return end + local linkdef = cursorPosDef.LinkedItem + + if linkdef.Opened == true then + -- this means it is an opened category + -- do nothing. + elseif (linkdef.Opened ~= nil and linkdef.Opened == false) or linkdef.Type == "Button" then + -- this means it is a closed category or it is a Button + -- invoke on the text + actorToHover:playcommand("Invoke") + elseif linkdef.Type == "SingleChoice" or linkdef.Type == "SingleChoiceModifier" then + -- this means it is a SingleChoice or SingleChoiceModifier + -- do nothing. + elseif linkdef.Type == "MultiChoice" then + -- this means it is a MultiChoice + -- invoke on the hovered Choice + actorToHover:playcommand("Invoke") + else + -- ???? + end + end + + -- function to set the cursor VERTICAL position + local function setCursorPos(n) + -- do nothing if not moving cursor + if rightPaneCursorPosition == n then return end + rightPaneCursorPosition = n + + local rowframe = getRowForCursorByCurrentPosition() + updateExplainText(rowframe) + + -- update visible cursor + setCursorPositionByCurrentConditions() + end + + -- move the cursor position by a distance if needed + local function changeCursorPos(n) + local newpos = rightPaneCursorPosition + n + -- not worth doing math to figure out if you moved 5 down from the last slide to put you on the 4th option from the top ...... + if newpos > #availableCursorPositions then newpos = 1 end + if newpos < 1 then newpos = #availableCursorPositions end + setCursorPos(newpos) + end + + -- move the cursor left or right (IM OUT OF FUNCTION NAMES AND DIDNT PLAN TO MAKE THIS ONE UNTIL RIGHT NOW DONT KNOW WHAT I WAS THINKING NOT SORRY) + local function cursorLateralMovement(n, useMultiplier) + local currentCursorRowDef = availableCursorPositions[rightPaneCursorPosition] + if currentCursorRowDef == nil then return end + local currentCursorRowOptionDef = currentCursorRowDef.LinkedItem + + if currentCursorRowOptionDef == nil or currentCursorRowOptionDef.Type == nil or currentCursorRowOptionDef.Type == "Button" then + -- Buttons and Categories dont have lateral movement actions + return + elseif currentCursorRowOptionDef.Type == "SingleChoice" then + -- moving a SingleChoice left or right actually invokes it (same as clicking the arrows) + local optionRowFrame = getRowForCursorByCurrentPosition() + local invoker = nil + if n > 0 then + -- run invoke on the right single arrow + invoker = optionRowFrame:GetChild("RightBigTriangleFrame") + else + -- run invoke on the left single arrow + invoker = optionRowFrame:GetChild("LeftBigTriangleFrame") + end + if invoker == nil then ms.ok("TRIED TO MOVE OPTION WITHOUT ARROWS. HOW? CONTACT DEVELOPER") return end + invoker:playcommand("Invoke") + elseif currentCursorRowOptionDef.Type == "SingleChoiceModifier" then + -- moving a SingleChoiceModifier left or right actually invokes it (same as clicking the arrows) + local optionRowFrame = getRowForCursorByCurrentPosition() + local invoker = nil + if useMultiplier then + if n > 0 then + -- run invoke on the right double arrow + invoker = optionRowFrame:GetChild("RightTrianglePairFrame") + else + -- run invoke on the left double arrow + invoker = optionRowFrame:GetChild("LeftTrianglePairFrame") + end + else + if n > 0 then + -- run invoke on the right single arrow + invoker = optionRowFrame:GetChild("RightBigTriangleFrame") + else + -- run invoke on the left single arrow + invoker = optionRowFrame:GetChild("LeftBigTriangleFrame") + end + end + if invoker == nil then ms.ok("TRIED TO MOVE OPTION WITHOUT ARROWS. HOW? CONTACT DEVELOPER") return end + invoker:playcommand("Invoke") + elseif currentCursorRowOptionDef.Type == "MultiChoice" then + -- moving a MultiChoice does not invoke it, only moves the cursor. Enter would invoke on a Choice instead + local newpos = currentCursorRowDef.HighlightedChoice + n + -- wrap around + if newpos > currentCursorRowDef.NumChoices then newpos = 1 end + if newpos < 1 then newpos = currentCursorRowDef.NumChoices end + currentCursorRowDef.HighlightedChoice = newpos + + -- heres the weird thing: + -- if we move the cursor here so that it ends up on another page, we need to redraw the stuff + -- so do a big brain and invoke the appropriate big triangle if that scenario arises + local optionRowFrame = getRowForCursorByCurrentPosition() + local validLower = 1 + (optionRowFrame.choicePage-1) * maxChoicesVisibleMultiChoice + local validUpper = optionRowFrame.choicePage * maxChoicesVisibleMultiChoice + if validUpper > #currentCursorRowOptionDef.Choices then validUpper = #currentCursorRowOptionDef.Choices end -- if last page missing elements + if newpos < validLower or newpos > validUpper then + -- changed page, find it + local newpage = math.ceil(newpos / math.min(#currentCursorRowOptionDef.Choices, maxChoicesVisibleMultiChoice)) + optionRowFrame:playcommand("SetChoicePage", {page = newpage}) + else + -- didnt change page probably + end + else + -- impossible? + return + end + + -- update visible cursor + setCursorPositionByCurrentConditions() + end + + -- shortcuts for changeCursorPos + local function cursorUp(n) + changeCursorPos(-n) + end + local function cursorDown(n) + changeCursorPos(n) + end + -- shortcuts for cursorLateralMovement + local function cursorLeft(n, useMultiplier) + cursorLateralMovement(-n, useMultiplier) + end + local function cursorRight(n, useMultiplier) + cursorLateralMovement(n, useMultiplier) + end + + -- function specifically for mouse hovering moving the cursor to run logic found in the above functions and more + local function setCursorVerticalHorizontalPos(rowFrame, choice) + if rowFrame == nil or rowFrame.defInUse == nil then return end -- apparently these can be nil? DONT KNOW HOW THATS PROBABLY REALLY BAD + local n = getRowIndexByName(rowFrame.defInUse.Name) + if choice == nil then choice = availableCursorPositions[n].HighlightedChoice end + + -- dont needlessly update + if rightPaneCursorPosition == n and availableCursorPositions[n].HighlightedChoice == choice then + return + end + + rightPaneCursorPosition = n + local rowframe = getRowForCursorByCurrentPosition() + updateExplainText(rowframe) + availableCursorPositions[n].HighlightedChoice = choice + setCursorPositionByCurrentConditions() + end + + -- putting these functions here to save on space below, less copy pasting, etc + local function onHover(self) + if self:IsInvisible() then return end + self:diffusealpha(buttonHoverAlpha) + local rowframe = self:GetParent() + updateExplainText(rowframe) + + -- only the category triangle uses this which means the choice is 1 + setCursorVerticalHorizontalPos(rowframe, 1) + end + local function onUnHover(self) + if self:IsInvisible() then return end + self:diffusealpha(1) + end + local function onHoverParent(self) + if self:GetParent():IsInvisible() then return end + self:GetParent():diffusealpha(buttonHoverAlpha) + local rowframe = self:GetParent():GetParent() + updateExplainText(rowframe) + + -- only triangles use this which means use the choice that is already set + setCursorVerticalHorizontalPos(rowframe, nil) + end + local function onUnHoverParent(self) + if self:GetParent():IsInvisible() then return end + self:GetParent():diffusealpha(1) + end + local function broadcastOptionUpdate(optionDef, choiceIndex) + if type(choiceIndex) == "number" then + if optionDef.Choices ~= nil and optionDef.Choices[choiceIndex] ~= nil then + -- a normal SingleChoice or SingleChoiceModifier + MESSAGEMAN:Broadcast("OptionUpdated", {name = optionDef.Name, choiceName = optionDef.Choices[choiceIndex].Name}) + else + -- a non-indexed option being updated directly + MESSAGEMAN:Broadcast("OptionUpdated", {name = optionDef.Name, choiceName = choiceIndex}) + end + elseif type(choiceIndex) == "string" then + -- a non-indexed option being updated directly + MESSAGEMAN:Broadcast("OptionUpdated", {name = optionDef.Name, choiceName = choiceIndex}) + elseif type(choiceIndex) == "table" then + -- in this case it is a MultiChoice being selected + if choiceIndex.Name ~= nil then + MESSAGEMAN:Broadcast("OptionUpdated", {name = optionDef.Name, choiceName = choiceIndex.Name}) + end + end + end + -- + + local t = Def.ActorFrame { + Name = "OptionRowContainer", + InitCommand = function(self) + self:y(actuals.TopLipHeight * 2 + actuals.OptionTextListTopGap) + optionRowContainer = self + self:playcommand("OpenPage", {page = 1}) + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + + -- cursor input management + CONTEXTMAN:RegisterToContextSet(snm, "Settings", anm) + CONTEXTMAN:ToggleContextSet(snm, "Settings", false) + + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if locked out, dont allow + if not CONTEXTMAN:CheckContextSet(snm, "Settings") then return end + if event.type ~= "InputEventType_Release" then -- allow Repeat and FirstPress + local gameButton = event.button + local key = event.DeviceInput.button + local up = gameButton == "Up" or gameButton == "MenuUp" + local down = gameButton == "Down" or gameButton == "MenuDown" + local right = gameButton == "MenuRight" or gameButton == "Right" + local left = gameButton == "MenuLeft" or gameButton == "Left" + local enter = gameButton == "Start" + local ctrl = INPUTFILTER:IsBeingPressed("left ctrl") or INPUTFILTER:IsBeingPressed("right ctrl") + local previewbutton = key == "DeviceButton_space" + local back = key == "DeviceButton_escape" + + if up then + cursorUp(1) + elseif down then + cursorDown(1) + elseif left then + cursorLeft(1, ctrl) + elseif right then + cursorRight(1, ctrl) + elseif enter then + invokeCurrentCursorPosition() + elseif previewbutton then + -- allow turning off chart preview if on + -- allow turning it on if not in a position where doing so is impossible + if SCUFF.showingPreview then + MESSAGEMAN:Broadcast("PlayerInfoFrameTabSet", {tab = "Settings"}) + elseif not SCUFF.showingPreview and not SCUFF.showingKeybinds and not SCUFF.showingNoteskins and not SCUFF.showingColor then + MESSAGEMAN:Broadcast("ShowSettingsAlt", {name = "Preview"}) + end + elseif back then + -- shortcut to exit back to general + MESSAGEMAN:Broadcast("GeneralTabSet") + else + -- nothing happens + return + end + end + end) + + -- initial cursor load + generateCursorPositionMap() + setCursorPositionByCurrentConditions() + updateExplainText(getRowForCursorByCurrentPosition()) + end, + OptionTabSetMessageCommand = function(self, params) + self:playcommand("OpenPage", params) + end, + OpenPageCommand = function(self, params) + local pageIndexToOpen = params.page + selectedPageName = pageNames[pageIndexToOpen] + selectedPageDef = optionPageCategoryLists[selectedPageName] + self:playcommand("OpenCategory", {categoryName = selectedPageDef[1]}) + end, + OpenCategoryCommand = function(self, params) + local categoryNameToOpen = params.categoryName + openedCategoryName = categoryNameToOpen + openedCategoryDef = optionDefs[openedCategoryName] + self:playcommand("UpdateRows") + end, + UpdateRowsCommand = function(self) + openedCategoryIndex = 1 + for i = 1, #selectedPageDef do + if selectedPageDef[i] == openedCategoryName then + openedCategoryIndex = i + end + end + + -- update all rows, redraw + for i = 1, optionRowCount do + local row = self:GetChild("OptionRow_"..i) + row:playcommand("UpdateRow") + end + + -- redrawing the rows means need to update the mapping of cursor positions + -- this resets the cursor position also + -- must take place after UpdateRow because cursor position is reliant on the row choice positions + generateCursorPositionMap() + setCursorPositionByCurrentConditions() + updateExplainText(getRowForCursorByCurrentPosition()) + end, + + Def.Quad { + Name = "OptionCursor", + InitCommand = function(self) + self:halign(0) + self:zoomto(100,100) + self:diffusealpha(0.6) + registerActorToColorConfigElement(self, "options", "Cursor") + end, + } + } + local function createOptionRow(i) + local types = rowTypes[i] or {} + -- SingleChoice 1 arrow, 1 choice + -- SingleChoiceModifier 2 arrow, 1 choice + -- MultiChoice 2 arrow, N choices + -- Button no arrow, 1 choice + -- generate elements based on how many choices and how many directional arrows are needed + local arrowCount = (types["SingleChoiceModifier"] or types["MultiChoice"]) and 2 or (types["SingleChoice"] and 1 or 0) + local choiceCount = rowChoiceCount[i] or 0 + + local optionDef = nil + local categoryDef = nil + local previousDef = nil -- for tracking def changes to animate things in a slightly more intelligent way + local previousPage = 1 -- for tracking page changes to animate things in a slightly more intelligent way + local rowHandle = {} -- for accessing the row frame from other points of reference (within this function) instantly + -- MultiChoice pagination + rowHandle.choicePage = 1 + rowHandle.maxChoicePage = 1 + + -- convenience to hit the AssociatedOptions optionDef stuff (primarily for speed mods but can be used for whatever) + -- hyper inefficient function (dont care) (yes i do) + local function updateAssociatedElements(thisDef) + if thisDef ~= nil and thisDef.AssociatedOptions ~= nil then + -- for each option + for _, optionName in ipairs(thisDef.AssociatedOptions) do + -- for each possible row to match + for rowIndex = 1, optionRowCount do + local row = rowHandle:GetParent():GetChild("OptionRow_"..rowIndex) + if row ~= nil then + if row.defInUse ~= nil and row.defInUse.Name == optionName then + row:playcommand("DrawRow") + + -- update cursor sizing and stuff + -- (i know without testing it that this will break if the associated element is a MultiChoice. please dont do that thanks) + -- retrospective comment: i tested this and it does work DONT KNOW WHY + local cursorRow = getRowForCursorByCurrentPosition() + if cursorRow ~= nil and cursorRow:GetName() == row:GetName() then + setCursorPositionByCurrentConditions() + end + end + end + end + end + end + end + + -- convenience + local function redrawChoiceRelatedElements() + local rightpair = rowHandle:GetChild("RightTrianglePairFrame") + local right = rowHandle:GetChild("RightBigTriangleFrame") + local choices = rowHandle:GetChild("ChoiceFrame") + if choices ~= nil then + choices:finishtweening() + choices:diffusealpha(0) + -- only animate the redraw for non single choices + -- the choice item shouldnt move so this isnt so weird + if optionDef ~= nil and optionDef.Type ~= "SingleChoice" and optionDef.Type ~= "SingleChoiceModifier" then + choices:smooth(optionRowQuickAnimationSeconds) + end + choices:diffusealpha(1) + choices:playcommand("DrawElement") + end + if right ~= nil then + right:playcommand("DrawElement") + end + if rightpair ~= nil then + rightpair:playcommand("DrawElement") + end + updateAssociatedElements(optionDef) + + -- if the cursor is on this row, update it because the width may have changed or something + -- and for a multichoice if the cursor was in some position and we changed page, move it to a sane position + local cursorRow = getRowForCursorByCurrentPosition() + if cursorRow == nil then return end + if cursorRow:GetName() == rowHandle:GetName() then + -- at this point we can assume rightPaneCursorPosition is the current cursor position + if optionDef.Type == "MultiChoice" then + local choicesPerPage = math.min(choiceCount, maxChoicesVisibleMultiChoice) + local cursorChoicePos = availableCursorPositions[rightPaneCursorPosition].HighlightedChoice + -- only have to take action if there is more than 1 page implied + if choicesPerPage < #optionDef.Choices then + local validLower = 1 + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice + local validUpper = rowHandle.choicePage * maxChoicesVisibleMultiChoice + if validUpper > #optionDef.Choices then validUpper = #optionDef.Choices end -- if last page missing elements + if cursorChoicePos < validLower then + -- highlight is too high, move to the last one + availableCursorPositions[rightPaneCursorPosition].HighlightedChoice = validLower + elseif cursorChoicePos > validUpper then + -- highlight is too low, move to first one + availableCursorPositions[rightPaneCursorPosition].HighlightedChoice = validUpper + else + -- probably dont have to do anything? its in valid range... + end + end + end + + setCursorPositionByCurrentConditions() + end + end + + -- index of the choice for this option, if no choices then this is useless + -- this can also be a table of indices for MultiChoice + -- this can also just be a random number or text for some certain implementations of optionDefs as long as they conform + local currentChoiceSelection = 1 + -- move SingleChoice selection index (assuming a list of choices is present -- if not, another methodology is used) + local function moveChoiceSelection(n) + if optionDef == nil then return end + + -- make selection loop both directions + local nn = currentChoiceSelection + n + if nn <= 0 then + nn = n > 0 and 1 or #optionDef.Choices + elseif nn > #optionDef.Choices then + nn = 1 + end + currentChoiceSelection = nn + if optionDef.Choices ~= nil and optionDef.Choices[currentChoiceSelection] ~= nil then + optionDef.Choices[currentChoiceSelection].ChosenFunction() + broadcastOptionUpdate(optionDef, currentChoiceSelection) + end + if rowHandle ~= nil then + redrawChoiceRelatedElements() + end + end + + -- paginate choices according to maxChoicesVisibleMultiChoice + local function moveChoicePage(n) + if rowHandle.maxChoicePage <= 1 then + return + end + + -- math to make pages loop both directions + local nn = (rowHandle.choicePage + n) % (rowHandle.maxChoicePage + 1) + if nn == 0 then + nn = n > 0 and 1 or rowHandle.maxChoicePage + end + rowHandle.choicePage = nn + if rowHandle ~= nil then + redrawChoiceRelatedElements() + end + end + + -- getter for all relevant children of the row + -- expects that self is OptionRow_i + local function getRowElements(self) + -- directional arrows + local leftpair = self:GetChild("LeftTrianglePairFrame") + local left = self:GetChild("LeftBigTriangleFrame") + local rightpair = self:GetChild("RightTrianglePairFrame") + local right = self:GetChild("RightBigTriangleFrame") + -- choices + local choices = self:GetChild("ChoiceFrame") + local title = self:GetChild("TitleText") + local categorytriangle = self:GetChild("CategoryTriangle") + return leftpair, left, rightpair, right, choices, title, categorytriangle + end + + local t = Def.ActorFrame { + Name = "OptionRow_"..i, + InitCommand = function(self) + self:x(actuals.EdgePadding) + self:y((actuals.OptionAllottedHeight / #rowChoiceCount) * (i-1) + (actuals.OptionAllottedHeight / #rowChoiceCount / 2)) + rowHandle = self + end, + SetChoicePageCommand = function(self, params) + local newpage = clamp(params.page, 1, rowHandle.maxChoicePage) + rowHandle.choicePage = newpage + redrawChoiceRelatedElements() + end, + UpdateRowCommand = function(self) + -- update row information, draw (this will reset the state of the row according to "global" conditions) + local firstOptionRowIndex = openedCategoryIndex + 1 + local lastOptionRowIndex = firstOptionRowIndex + #openedCategoryDef - 1 + + -- track previous definition + previousDef = nil + if optionDef ~= nil then previousDef = optionDef end + if categoryDef ~= nil then previousDef = categoryDef end + previousPage = rowHandle.choicePage + + -- reset state + optionDef = nil + categoryDef = nil + self.defInUse = nil + rowHandle.choicePage = 1 + rowHandle.maxChoicePage = 1 + + if i >= firstOptionRowIndex and i <= lastOptionRowIndex then + -- this is an option and has an optionDef + local optionDefIndex = i - firstOptionRowIndex + 1 + optionDef = openedCategoryDef[optionDefIndex] + if optionDef.Choices ~= nil then + rowHandle.maxChoicePage = math.ceil(#optionDef.Choices / maxChoicesVisibleMultiChoice) + end + self.defInUse = optionDef + else + -- this is a category or nothing at all + -- maybe generate a "categoryDef" which is really just a summary of what to display instead + local lastValidPossibleIndex = lastOptionRowIndex + (#selectedPageDef - openedCategoryIndex) + if i > lastValidPossibleIndex then + -- nothing. + else + -- this has a categoryDef + local adjustedCategoryIndex = i + -- subtract the huge list of optionDefs to grab the position of the category in the original list + if i > lastOptionRowIndex then + adjustedCategoryIndex = (i) - #openedCategoryDef + end + categoryDef = { + Opened = (openedCategoryIndex == i) and true or false, + Name = selectedPageDef[adjustedCategoryIndex] + } + self.defInUse = categoryDef + end + end + + self:playcommand("DrawRow") + end, + DrawRowCommand = function(self) + -- redraw row + local leftPairArrows, leftArrow, rightPairArrows, rightArrow, choiceFrame, titleText, categoryTriangle = getRowElements(self) + + if optionDef ~= nil and optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + + -- blink the row if it updated + self:finishtweening() + self:diffusealpha(0) + -- if def was just defined, or def just changed, or choice page just changed -- show animation + if previousDef == nil or (optionDef ~= nil and optionDef.Name ~= previousDef.Name) or (categoryDef ~= nil and categoryDef.Name ~= previousDef.Name) or previousPage ~= rowHandle.choicePage then + self:smooth(optionRowAnimationSeconds) + end + self:diffusealpha(1) + + -- this is done so that the redraw can be done in a particular order, left to right + -- also, not all of these actors are guaranteed to exist + -- and each actor may or may not rely on the previous one to be positioned in order to correctly draw + -- the strict ordering is required as a result + if categoryTriangle ~= nil then + categoryTriangle:playcommand("DrawElement") + end + + if titleText ~= nil then + titleText:playcommand("DrawElement") + end + + if leftPairArrows ~= nil then + leftPairArrows:playcommand("DrawElement") + end + + if leftArrow ~= nil then + leftArrow:playcommand("DrawElement") + end + + if choiceFrame ~= nil then + choiceFrame:playcommand("DrawElement") + end + + if rightArrow ~= nil then + rightArrow:playcommand("DrawElement") + end + + if rightPairArrows ~= nil then + rightPairArrows:playcommand("DrawElement") + end + end, + + -- category title and option name + UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "TitleText", + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + txt:zoom(optionTitleTextSize) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "PrimaryText") + + bg:halign(0) + bg:zoomto(0, txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + DrawElementCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + if optionDef ~= nil then + self:x(0) + txt:settext(optionDef.Name) + txt:maxwidth(actuals.OptionTextWidth / optionTitleTextSize - textZoomFudge) + elseif categoryDef ~= nil then + local newx = actuals.OptionBigTriangleWidth + actuals.OptionTextBuffer / 2 + self:x(newx) + txt:settext(categoryDef.Name) + txt:maxwidth((actuals.OptionTextWidth - newx) / optionTitleTextSize - textZoomFudge) + else + txt:settext("") + end + bg:zoomx(txt:GetZoomedWidth()) + end, + InvokeCommand = function(self) + -- behavior for interacting with the Option Row Title Text + if categoryDef ~= nil then + rowHandle:GetParent():playcommand("OpenCategory", {categoryName = categoryDef.Name}) + elseif optionDef ~= nil then + if optionDef.Type == "Button" then + -- button + if optionDef.Choices and #optionDef.Choices >= 1 then + optionDef.Choices[1].ChosenFunction() + broadcastOptionUpdate(optionDef, 1) + end + else + -- ? + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + updateExplainText(rowHandle) + setCursorVerticalHorizontalPos(rowHandle, nil) + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if optionDef ~= nil or categoryDef ~= nil then + self:playcommand("Invoke") + end + end + end, + }, + UIElements.QuadButton(0, 1) .. { + Name = "MouseWheelRegion", + InitCommand = function(self) + self:halign(0) + self:diffusealpha(0) + self:zoomto(500, actuals.OptionAllottedHeight / optionRowCount) + end, + MouseScrollMessageCommand = function(self, params) + if isOver(self) and focused and (optionDef ~= nil or categoryDef ~= nil) then + if optionDef ~= nil then + if optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" or optionDef.Type == "MultiChoice" then + if params.direction == "Up" then + rowHandle:GetChild("RightBigTriangleFrame"):playcommand("Invoke") + else + rowHandle:GetChild("LeftBigTriangleFrame"):playcommand("Invoke") + end + end + end + end + end, + MouseOverCommand = function(self) + if not focused or optionDef == nil then return end + updateExplainText(rowHandle) + -- uncomment to update cursor position when hovering the invisible area + -- seems like an annoying and buggy looking behavior + -- although it is correct, it is just weird + --setCursorVerticalHorizontalPos(rowHandle, nil) + end, + } + } + + -- category arrow + if types["Category"] then + t[#t+1] = UIElements.SpriteButton(1, 1, THEME:GetPathG("", "_triangle")) .. { + Name = "CategoryTriangle", + InitCommand = function(self) + self:x(actuals.OptionBigTriangleWidth/2) + self:zoomto(actuals.OptionBigTriangleWidth, actuals.OptionBigTriangleHeight) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + DrawElementCommand = function(self) + if categoryDef ~= nil then + if categoryDef.Opened then + self:rotationz(180) + else + self:rotationz(90) + end + self:diffusealpha(1) + self:z(1) + else + self:diffusealpha(0) + self:z(-1) + end + end, + InvokeCommand = function(self) + -- behavior for interacting with the Option Row Title Text + if categoryDef ~= nil and not categoryDef.Opened then + rowHandle:GetParent():playcommand("OpenCategory", {categoryName = categoryDef.Name}) + end + end, + MouseOverCommand = onHover, + MouseOutCommand = onUnHover, + MouseDownCommand = function(self, params) + if self:IsInvisible() then return end + self:playcommand("Invoke") + end, + } + end + + -- smaller double arrow, left/right + if arrowCount == 2 then + -- copy paste territory + t[#t+1] = Def.ActorFrame { + Name = "LeftTrianglePairFrame", + DrawElementCommand = function(self) + if optionDef ~= nil and optionDef.Type == "SingleChoiceModifier" then + -- only visible in this case + -- offset by half the triangle size due to center aligning + self:x(actuals.OptionTextWidth + actuals.OptionTextBuffer + actuals.OptionSmallTriangleHeight/2) + self:diffusealpha(1) + self:z(1) + else + -- always invisible + self:diffusealpha(0) + self:z(-1) + end + end, + + Def.Sprite { + Name = "LeftTriangle", -- outermost triangle + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(-90) + self:zoomto(actuals.OptionSmallTriangleWidth, actuals.OptionSmallTriangleHeight) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + Def.Sprite { + Name = "RightTriangle", -- innermost triangle + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(-90) + self:zoomto(actuals.OptionSmallTriangleWidth, actuals.OptionSmallTriangleHeight) + -- subtract by 25% triangle height because image is 25% invisible + self:x(actuals.OptionSmallTriangleHeight + actuals.OptionSmallTriangleGap - actuals.OptionSmallTriangleHeight/4) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + UIElements.QuadButton(1, 1) .. { + Name = "LeftTrianglePairButton", + InitCommand = function(self) + self:diffusealpha(0) + self:x(actuals.OptionSmallTriangleHeight/2) + self:zoomto(actuals.OptionSmallTriangleHeight * 2 + actuals.OptionSmallTriangleGap, actuals.OptionBigTriangleWidth) + end, + InvokeCommand = function(self) + if optionDef ~= nil then + if optionDef.Type == "SingleChoiceModifier" then + -- SingleChoiceModifier selection mover + if optionDef.Directions ~= nil and optionDef.Directions.Toggle ~= nil then + -- Toggle SingleChoice (multiplier) + optionDef.Directions.Toggle(true) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif optionDef.Directions ~= nil and optionDef.Directions.Left ~= nil then + -- Move Left (multiplier) + optionDef.Directions.Left(true) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + end + + if optionDef.Choices ~= nil then + moveChoiceSelection(-2) + else + ms.ok("ERROR REPORT TO DEVELOPER") + end + end + end + end, + MouseDownCommand = function(self, params) + if self:GetParent():IsInvisible() then return end + if optionDef ~= nil then + self:playcommand("Invoke") + end + end, + MouseOverCommand = onHoverParent, + MouseOutCommand = onUnHoverParent, + } + } + t[#t+1] = Def.ActorFrame { + Name = "RightTrianglePairFrame", + DrawElementCommand = function(self) + if optionDef ~= nil and optionDef.Type == "SingleChoiceModifier" then + -- only visible in this case + local optionRowChoiceFrame = rowHandle:GetChild("ChoiceFrame") + if choiceCount < 1 then self:diffusealpha(0):z(-1) return end + -- offset by the position of the choice text and the size of the big triangles + -- the logic/ordering of the positioning is visible in the math + -- choice xpos + width + buffer + big triangle size + buffer + -- we pick choice 1 because only SingleChoice is allowed to show these arrows + -- offset by half triangle size due to center aligning (edit: nvm?) + -- okay actually im gonna be honest I DONT KNOW WHAT IS HAPPENING HERE + -- but it completely mirrors the behavior of the other side so it works + -- help + self:x(optionRowChoiceFrame:GetX() + optionRowChoiceFrame:GetChild("Choice_1"):GetChild("Text"):GetZoomedWidth() + actuals.OptionChoiceDirectionGap + actuals.OptionBigTriangleHeight*0.9 + actuals.OptionChoiceDirectionGap) + self:diffusealpha(1) + self:z(1) + else + -- always invisible + self:diffusealpha(0) + self:z(-1) + end + end, + + Def.Sprite { + Name = "RightTriangle", -- outermost triangle + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(90) + self:zoomto(actuals.OptionSmallTriangleWidth, actuals.OptionSmallTriangleHeight) + -- subtract by 25% triangle height because image is 25% invisible + self:x(actuals.OptionSmallTriangleHeight + actuals.OptionSmallTriangleGap - actuals.OptionSmallTriangleHeight/4) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + Def.Sprite { + Name = "LeftTriangle", -- innermost triangle + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(90) + self:zoomto(actuals.OptionSmallTriangleWidth, actuals.OptionSmallTriangleHeight) + self:x(0) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + UIElements.QuadButton(1, 1) .. { + Name = "RightTrianglePairButton", + InitCommand = function(self) + self:diffusealpha(0) + self:x(actuals.OptionSmallTriangleHeight/2) + self:zoomto(actuals.OptionSmallTriangleHeight * 2 + actuals.OptionSmallTriangleGap, actuals.OptionBigTriangleWidth) + end, + InvokeCommand = function(self) + if optionDef ~= nil then + if optionDef.Type == "SingleChoiceModifier" then + -- SingleChoiceModifier selection mover + if optionDef.Directions ~= nil and optionDef.Directions.Toggle ~= nil then + -- Toggle SingleChoice (multiplier) + optionDef.Directions.Toggle(true) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif optionDef.Directions ~= nil and optionDef.Directions.Right ~= nil then + -- Move Right (multiplier) + optionDef.Directions.Right(true) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + end + + if optionDef.Choices ~= nil then + moveChoiceSelection(2) + else + ms.ok("ERROR REPORT TO DEVELOPER") + end + end + end + end, + MouseDownCommand = function(self, params) + if self:GetParent():IsInvisible() then return end + if optionDef ~= nil then + self:playcommand("Invoke") + end + end, + MouseOverCommand = onHoverParent, + MouseOutCommand = onUnHoverParent, + } + } + end + + -- single large arrow, left/right + if arrowCount >= 1 then + t[#t+1] = Def.ActorFrame { + Name = "LeftBigTriangleFrame", + DrawElementCommand = function(self) + if optionDef ~= nil and (optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" or (optionDef.Type == "MultiChoice" and rowHandle.maxChoicePage > 1)) then + -- visible for SingleChoice(Modifier) and MultiChoice + -- only visible on MultiChoice if we need to paginate the choices + -- offset by half height due to center aligning + local minXPos = actuals.OptionTextWidth + actuals.OptionTextBuffer + actuals.OptionBigTriangleHeight/2 + if optionDef.Type == "SingleChoice" or optionDef.Type == "MultiChoice" then + -- SingleChoice/MultiChoice is on the very left + self:x(minXPos) + else + -- SingleChoiceModifier is to the right of the LeftTrianglePairFrame + -- subtract by 25% triangle height twice because 25% of the image is invisible + self:x(minXPos + actuals.OptionSmallTriangleHeight * 2 - actuals.OptionSmallTriangleHeight/2 + actuals.OptionSmallTriangleGap + actuals.OptionChoiceDirectionGap) + end + self:diffusealpha(1) + self:z(1) + else + -- always invisible + self:diffusealpha(0) + self:z(-1) + end + end, + + Def.Sprite { + Name = "Triangle", + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(-90) + self:zoomto(actuals.OptionBigTriangleWidth, actuals.OptionBigTriangleHeight) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + UIElements.QuadButton(1, 1) .. { + Name = "TriangleButton", + InitCommand = function(self) + self:diffusealpha(0) + self:zoomto(actuals.OptionBigTriangleWidth, actuals.OptionBigTriangleHeight) + end, + InvokeCommand = function(self) + if optionDef ~= nil then + if optionDef.Type == "MultiChoice" then + -- MultiChoice pagination + moveChoicePage(-1) + elseif optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" then + -- SingleChoice selection mover + if optionDef.Directions ~= nil and optionDef.Directions.Toggle ~= nil then + -- Toggle SingleChoices + optionDef.Directions.Toggle() + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif optionDef.Directions ~= nil and optionDef.Directions.Left ~= nil then + -- Move Left (no multiplier) + optionDef.Directions.Left(false) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + end + + if optionDef.Choices ~= nil then + moveChoiceSelection(-1) + else + ms.ok("ERROR REPORT TO DEVELOPER") + end + end + end + end, + MouseDownCommand = function(self, params) + if self:GetParent():IsInvisible() then return end + if optionDef ~= nil then + self:playcommand("Invoke") + end + end, + MouseOverCommand = onHoverParent, + MouseOutCommand = onUnHoverParent, + } + } + t[#t+1] = Def.ActorFrame { + Name = "RightBigTriangleFrame", + DrawElementCommand = function(self) + if optionDef ~= nil and (optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" or (optionDef.Type == "MultiChoice" and rowHandle.maxChoicePage > 1)) then + -- visible for SingleChoice(Modifier) and MultiChoice + local optionRowChoiceFrame = rowHandle:GetChild("ChoiceFrame") + if choiceCount < 1 then self:diffusealpha(0):z(-1) return end + -- offset by the position of the choice text and appropriate buffer + -- the logic/ordering of the positioning is visible in the math + -- choice xpos + width + buffer + -- we pick choice 1 because only SingleChoice is allowed to show these arrows + -- subtract by 25% triangle height because 25% of the image is invisible + -- offset by half height due to center aligning + if optionDef.Type == "MultiChoice" then + -- offset to the right of the last visible choice (up to the 4th one) + local lastChoiceIndex = math.min(maxChoicesVisibleMultiChoice, #optionDef.Choices) -- last choice if not on first or last page + if rowHandle.choicePage > 1 and rowHandle.choicePage >= rowHandle.maxChoicePage then + -- last if on last (first) page + lastChoiceIndex = #optionDef.Choices % maxChoicesVisibleMultiChoice + if lastChoiceIndex == 0 then lastChoiceIndex = maxChoicesVisibleMultiChoice end + end + local lastChoice = optionRowChoiceFrame:GetChild("Choice_"..lastChoiceIndex) + local finalX = optionRowChoiceFrame:GetX() + lastChoice:GetX() + lastChoice:GetChild("Text"):GetZoomedWidth() + actuals.OptionChoiceDirectionGap + actuals.OptionBigTriangleHeight/4 + self:x(finalX) + else + self:x(optionRowChoiceFrame:GetX() + optionRowChoiceFrame:GetChild("Choice_1"):GetChild("Text"):GetZoomedWidth() + actuals.OptionChoiceDirectionGap + actuals.OptionBigTriangleHeight/4) + end + self:diffusealpha(1) + self:z(1) + else + -- always invisible + self:diffusealpha(0) + self:z(-1) + end + end, + + Def.Sprite { + Name = "Triangle", + Texture = THEME:GetPathG("", "_triangle"), + InitCommand = function(self) + self:rotationz(90) + self:zoomto(actuals.OptionBigTriangleWidth, actuals.OptionBigTriangleHeight) + registerActorToColorConfigElement(self, "options", "Arrows") + end, + }, + UIElements.QuadButton(1, 1) .. { + Name = "TriangleButton", + InitCommand = function(self) + self:diffusealpha(0) + self:zoomto(actuals.OptionBigTriangleWidth, actuals.OptionBigTriangleHeight) + end, + InvokeCommand = function(self) + if optionDef ~= nil then + if optionDef.Type == "MultiChoice" then + -- MultiChoice pagination + moveChoicePage(1) + elseif optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" then + -- SingleChoice selection mover + if optionDef.Directions ~= nil and optionDef.Directions.Toggle ~= nil then + -- Toggle SingleChoices + optionDef.Directions.Toggle() + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif optionDef.Directions ~= nil and optionDef.Directions.Right ~= nil then + -- Move Right (no multiplier) + optionDef.Directions.Right(false) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + end + + if optionDef.Choices ~= nil then + moveChoiceSelection(1) + else + ms.ok("ERROR REPORT TO DEVELOPER") + end + end + end + end, + MouseDownCommand = function(self, params) + if self:GetParent():IsInvisible() then return end + if optionDef ~= nil then + self:playcommand("Invoke") + end + end, + MouseOverCommand = onHoverParent, + MouseOutCommand = onUnHoverParent, + } + } + end + + -- choice text + local function createOptionRowChoices() + local t = Def.ActorFrame { + Name = "ChoiceFrame", + DrawElementCommand = function(self) + if optionDef ~= nil then + self:diffusealpha(1) + + local minXPos = actuals.OptionTextWidth + actuals.OptionTextBuffer + local finalXPos = minXPos + -- triangle width buffer thing .... the distance from minX to ... the choices ... across the one big triangle ... + local triangleWidthBufferThing = actuals.OptionBigTriangleHeight + actuals.OptionChoiceDirectionGap - actuals.OptionBigTriangleHeight/4 + if optionDef.Type == "SingleChoice" or (optionDef.Type == "MultiChoice" and rowHandle.maxChoicePage > 1) then + -- leftmost xpos + big triangle + gap + -- subtract by 25% of the big triangle size because the image is actually 25% invisible + finalXPos = finalXPos + triangleWidthBufferThing + elseif optionDef.Type == "SingleChoiceModifier" then + -- leftmost xpos + big triangle + gap + 2 small triangles + gap between 2 small triangles + last gap + -- subtract by 25% of big triangle and 25% of small triangle twice because the image is 25% invisible + finalXPos = finalXPos + triangleWidthBufferThing + actuals.OptionSmallTriangleHeight * 2 - actuals.OptionSmallTriangleHeight/2 + actuals.OptionSmallTriangleGap + actuals.OptionChoiceDirectionGap + end + self:x(finalXPos) + + -- to force the choices to update left to right + -- update the text of all of them first to see what the width would be + local lastFilledChoiceIndex = 1 + for i = 1, math.min(choiceCount, maxChoicesVisibleMultiChoice) do + local child = self:GetChild("Choice_"..i) + child:playcommand("SetChoiceText") + if #child:GetChild("Text"):GetText() > 0 then + lastFilledChoiceIndex = i + end + end + + -- so basically this bad line of math evenly splits the given area including the buffer zones in between + -- it also takes into account whether or not we have the triangles on the edges (so if missing, take up more room to equal in width) + -- (it doesnt produce a great result and all this garbage is for nothing if you think about it) + -- (leaving it here anyways in case this method of setting text and then drawing can be used) + local allowedWidth = (actuals.OptionChoiceAllottedWidth - (lastFilledChoiceIndex-1) * actuals.OptionTextBuffer) / lastFilledChoiceIndex + (rowHandle.maxChoicePage <= 1 and triangleWidthBufferThing or 0) + for i = 1, math.min(choiceCount, maxChoicesVisibleMultiChoice) do + local child = self:GetChild("Choice_"..i) + child:GetChild("Text"):maxwidth(allowedWidth / choiceTextSize) + child:playcommand("DrawChoice") + end + + + else + -- missing optionDef means no choices possible + self:diffusealpha(0) + end + end, + } + for n = 1, math.min(choiceCount, maxChoicesVisibleMultiChoice) do + -- each of these tt's are ActorFrames named Choice_n + -- they have 3 children, Text, BG, Underline + local tt = UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "Choice_"..n, + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + txt:halign(0) + txt:zoom(optionChoiceTextSize) + txt:settext(" ") + registerActorToColorConfigElement(txt, "main", "SecondaryText") + + bg:halign(0) + bg:zoomto(0, txt:GetZoomedHeight() * textButtonHeightFudgeScalarMultiplier) + end, + SetChoiceTextCommand = function(self) + -- THIS DOES NOT DO BUTTON WORK + -- RUN COMMANDS IN THIS ORDER: SetChoiceText -> ??? -> DrawChoice + -- That will properly update the text and choices and everything "nicely" + local txt = self:GetChild("Text") + txt:maxwidth(actuals.OptionChoiceAllottedWidth / choiceTextSize) + if optionDef ~= nil then + if optionDef.Type == "MultiChoice" then + local choiceIndex = n + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice + local choice = optionDef.Choices[choiceIndex] + if choice ~= nil then + txt:settext(choice.Name) + else + txt:settext("") + end + elseif optionDef.Type == "Button" then + txt:settext("") + else + if n == 1 then + -- several cases involving the ChoiceIndexGetter for single choices... + if optionDef.ChoiceIndexGetter ~= nil and optionDef.Choices == nil then + -- getter with no choices means the getter supplies the visible information + txt:settext(currentChoiceSelection) + elseif optionDef.Choices ~= nil then + -- choices present means the getter supplies the choice index that contains the information + txt:settext(optionDef.Choices[currentChoiceSelection].Name) + else + txt:settext("INVALID CONTACT DEVELOPER") + end + else + txt:settext("") + end + end + else + txt:settext("") + end + end, + DrawChoiceCommand = function(self) + if optionDef ~= nil then + if optionDef.Type == "MultiChoice" then + -- for Multi choice mode + local choiceIndex = n + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice + local choice = optionDef.Choices[choiceIndex] + if choice ~= nil then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- get the x position of this element using the position of the element to the left + -- this requires all elements be updated in order, left to right + local xPos = 0 + if n > 1 then + local choiceJustToTheLeftOfThisOne = self:GetParent():GetChild("Choice_"..(n-1)) + xPos = choiceJustToTheLeftOfThisOne:GetX() + choiceJustToTheLeftOfThisOne:GetChild("Text"):GetZoomedWidth() + actuals.OptionTextBuffer + end + self:x(xPos) + bg:zoomx(txt:GetZoomedWidth()) + bg:diffusealpha(0.1) + + self:diffusealpha(1) + self:z(1) + else + -- choice does not exist for this option but does for another + self:x(0) + self:diffusealpha(0) + self:z(-1) + end + elseif optionDef.Type == "Button" then + -- Button is just one choice but lets use the option title as the choice (hide all choices) + self:x(0) + self:diffusealpha(0) + self:z(-1) + else + -- for Single choice mode only show first choice + if n == 1 then + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + bg:zoomx(txt:GetZoomedWidth()) + bg:diffusealpha(0) + self:x(0) -- for consistency but makes no difference + self:diffusealpha(1) + self:z(1) + else + self:x(0) + self:diffusealpha(0) + self:z(-1) + end + end + end + end, + InvokeCommand = function(self, params) + if optionDef ~= nil then + if optionDef.Type == "SingleChoice" or optionDef.Type == "SingleChoiceModifier" then + -- SingleChoice left clicks will move the option forward + -- SingleChoice right clicks will move the option backward + if params and params.direction then + local fwd = params.direction == "forward" + local bwd = params.direction == "backward" + + -- SingleChoice selection mover + if optionDef.Directions ~= nil and optionDef.Directions.Toggle ~= nil then + -- Toggle SingleChoices + optionDef.Directions.Toggle() + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif fwd and optionDef.Directions ~= nil and optionDef.Directions.Right ~= nil then + -- Move Right (no multiplier) + optionDef.Directions.Right(false) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + elseif bwd and optionDef.Directions ~= nil and optionDef.Directions.Left ~= nil then + -- Move Left (no multiplier) + optionDef.Directions.Left(false) + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, currentChoiceSelection) + redrawChoiceRelatedElements() + return + end + + if optionDef.Choices ~= nil then + moveChoiceSelection(1 * (fwd and 1 or -1)) + else + ms.ok("ERROR REPORT TO DEVELOPER") + end + end + elseif optionDef.Type == "MultiChoice" then + -- multichoice clicks will toggle the option + local choiceIndex = n + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice + local choice = optionDef.Choices[choiceIndex] + if choice ~= nil then + choice.ChosenFunction() + if optionDef.ChoiceIndexGetter ~= nil then + currentChoiceSelection = optionDef.ChoiceIndexGetter() + end + broadcastOptionUpdate(optionDef, choice) + updateAssociatedElements(optionDef) + self:playcommand("DrawChoice") + end + end + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + updateExplainText(rowHandle) + if optionDef.Type == "MultiChoice" then + setCursorVerticalHorizontalPos(rowHandle, n + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice) + else + setCursorVerticalHorizontalPos(rowHandle, 1) + end + else + self:diffusealpha(1) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + if optionDef ~= nil then + local direction = params.event == "DeviceButton_left mouse button" and "forward" or "backward" + self:playcommand("Invoke", {direction = direction}) + end + end + end, + } + tt[#tt+1] = Def.Quad { + Name = "Underline", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(0,actuals.OptionChoiceUnderlineThickness) + self:diffusealpha(0) + registerActorToColorConfigElement(self, "main", "SeparationDivider") + end, + DrawChoiceCommand = function(self) + -- assumption: this Actor is later in the command execution order than the rest of the frame + -- that should let it use the attributes after they are set + if optionDef == nil or optionDef.Type ~= "MultiChoice" then + self:diffusealpha(0) + else + -- optionDef present and is MultiChoice + -- determine if this choice is selected + local choiceIndex = n + (rowHandle.choicePage-1) * maxChoicesVisibleMultiChoice + local isSelected = currentChoiceSelection[choiceIndex] + if isSelected == true then + local bg = self:GetParent():GetChild("BG") + local text = self:GetParent():GetChild("Text") + self:diffusealpha(1) + self:y(bg:GetZoomedHeight()/2 + bg:GetY()) + self:zoomx(bg:GetZoomedWidth()) + else + self:diffusealpha(0) + end + end + end, + } + t[#t+1] = tt + end + return t + end + t[#t+1] = createOptionRowChoices() + return t + end + for i = 1, optionRowCount do + t[#t+1] = createOptionRow(i) + end + return t + end + + local function createOptionPageChoices() + local selectedIndex = 1 + + local function createChoice(i) + return UIElements.TextButton(1, 1, "Common Normal") .. { + Name = "ButtonTab_"..pageNames[i], + InitCommand = function(self) + local txt = self:GetChild("Text") + local bg = self:GetChild("BG") + + -- this position is the center of the text + -- divides the space into slots for the choices then places them half way into them + -- should work for any count of choices + -- and the maxwidth will make sure they stay nonoverlapping + self:x((actuals.RightWidth / #pageNames) * (i-1) + (actuals.RightWidth / #pageNames / 2)) + txt:zoom(choiceTextSize) + txt:maxwidth(actuals.RightWidth / #pageNames / choiceTextSize - textZoomFudge) + txt:settext(pageNames[i]) + self:playcommand("ColorConfigUpdated") + bg:zoomto(actuals.RightWidth / #pageNames, actuals.TopLipHeight) + end, + ColorConfigUpdatedMessageCommand = function(self) + local txt = self:GetChild("Text") + txt:diffuse(COLORS:getMainColor("PrimaryText")) + txt:diffusealpha(1) + self:playcommand("UpdateSelectedIndex") + end, + UpdateSelectedIndexCommand = function(self) + local txt = self:GetChild("Text") + if selectedIndex == i then + txt:strokecolor(Brightness(COLORS:getMainColor("PrimaryText"), 0.75)) + else + txt:strokecolor(color("0,0,0,0")) + end + end, + ClickCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "OnMouseDown" then + selectedIndex = i + MESSAGEMAN:Broadcast("OptionTabSet", {page = i}) + self:GetParent():playcommand("UpdateSelectedIndex") + end + end, + RolloverUpdateCommand = function(self, params) + if self:IsInvisible() then return end + if params.update == "in" then + self:diffusealpha(buttonHoverAlpha) + else + self:diffusealpha(1) + end + end + } + end + local t = Def.ActorFrame { + Name = "Choices", + InitCommand = function(self) + self:y(actuals.TopLipHeight * 1.5) + self:playcommand("UpdateSelectedIndex") + end, + BeginCommand = function(self) + local snm = SCREENMAN:GetTopScreen():GetName() + local anm = self:GetName() + + CONTEXTMAN:RegisterToContextSet(snm, "Settings", anm) + CONTEXTMAN:ToggleContextSet(snm, "Settings", false) + + -- enable the possibility to press the keyboard to switch tabs + SCREENMAN:GetTopScreen():AddInputCallback(function(event) + -- if locked out, dont allow + -- pressing a number with ctrl should lead to the general tab stuff + -- otherwise, typing numbers will put you into that settings context and reposition the cursor + if not CONTEXTMAN:CheckContextSet(snm, "Settings") then return end + if event.type == "InputEventType_FirstPress" then + local char = inputToCharacter(event) + local num = nil + + -- if ctrl is pressed with a number, let the general tab input handler deal with it + if char ~= nil and tonumber(char) and INPUTFILTER:IsControlPressed() then + return + end + + if tonumber(char) then + num = tonumber(char) + end + + -- cope with number presses to change option pages + if num ~= nil then + if num == 0 then num = 10 end + if num == selectedIndex then return end + if num < 1 or num > #pageNames then return end + selectedIndex = num + MESSAGEMAN:Broadcast("OptionTabSet", {page = num}) + self:playcommand("UpdateSelectedIndex") + end + end + end) + end + } + for i = 1, #pageNames do + t[#t+1] = createChoice(i) + end + return t + end + + t[#t+1] = createOptionRows() + t[#t+1] = createOptionPageChoices() + + return t +end + +t[#t+1] = leftFrame() +t[#t+1] = rightFrame() + +return t diff --git a/Themes/Rebirth/Fonts/Common Large.redir b/Themes/Rebirth/Fonts/Common Large.redir new file mode 100644 index 0000000000..c90cefb126 --- /dev/null +++ b/Themes/Rebirth/Fonts/Common Large.redir @@ -0,0 +1 @@ +_theFont 48px \ No newline at end of file diff --git a/Themes/Rebirth/Fonts/Common Normal.redir b/Themes/Rebirth/Fonts/Common Normal.redir new file mode 100644 index 0000000000..a51f3a6d6c --- /dev/null +++ b/Themes/Rebirth/Fonts/Common Normal.redir @@ -0,0 +1 @@ +_theFont 24px \ No newline at end of file diff --git a/Themes/Rebirth/Fonts/FONTNOTES.txt b/Themes/Rebirth/Fonts/FONTNOTES.txt new file mode 100644 index 0000000000..9006588b1a --- /dev/null +++ b/Themes/Rebirth/Fonts/FONTNOTES.txt @@ -0,0 +1,2 @@ +_theFont 24px - microsoft yi baiti Bold 24px numbers & darker grotesque Bold 24px text & bokutachinogothic 24px symbols2+3 +_theFont 48px - microsoft yi baiti Bold 48px numbers & darker grotesque Bold 48px text & bokutachinogothic 48px symbols2+3 \ No newline at end of file diff --git a/Themes/Rebirth/Fonts/Menu Bold.redir b/Themes/Rebirth/Fonts/Menu Bold.redir new file mode 100644 index 0000000000..ed0dbacc11 --- /dev/null +++ b/Themes/Rebirth/Fonts/Menu Bold.redir @@ -0,0 +1 @@ +_open sans Bold 48px \ No newline at end of file diff --git a/Themes/Rebirth/Fonts/Menu Normal.redir b/Themes/Rebirth/Fonts/Menu Normal.redir new file mode 100644 index 0000000000..8e82c062a8 --- /dev/null +++ b/Themes/Rebirth/Fonts/Menu Normal.redir @@ -0,0 +1 @@ +_open sans 48px \ No newline at end of file diff --git a/Themes/Rebirth/Fonts/_japanese 24px [ank-stroke] 16x10 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [ank-stroke] 16x10 (doubleres).png new file mode 100644 index 0000000000..fe569271b7 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [ank-stroke] 16x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [ank] 16x10 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [ank] 16x10 (doubleres).png new file mode 100644 index 0000000000..0fd97a56bf Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [ank] 16x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [compatible-stroke] 4x2 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [compatible-stroke] 4x2 (doubleres).png new file mode 100644 index 0000000000..e0741cd999 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [compatible-stroke] 4x2 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [compatible] 4x2 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [compatible] 4x2 (doubleres).png new file mode 100644 index 0000000000..3d55e237e8 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [compatible] 4x2 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm-stroke] 32x12 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm-stroke] 32x12 (doubleres).png new file mode 100644 index 0000000000..d7ad9d1aa2 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm-stroke] 32x12 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm] 32x12 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm] 32x12 (doubleres).png new file mode 100644 index 0000000000..b80514b97b Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-ibm] 32x12 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1-stroke] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1-stroke] 32x32 (doubleres).png new file mode 100644 index 0000000000..7894153d0c Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1-stroke] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1] 32x32 (doubleres).png new file mode 100644 index 0000000000..34857718d4 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis1] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1-stroke] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1-stroke] 32x32 (doubleres).png new file mode 100644 index 0000000000..8b57c617d3 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1-stroke] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1] 32x32 (doubleres).png new file mode 100644 index 0000000000..814256772a Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 1] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2-stroke] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2-stroke] 32x32 (doubleres).png new file mode 100644 index 0000000000..c65ef71537 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2-stroke] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2] 32x32 (doubleres).png new file mode 100644 index 0000000000..1228dff7df Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 2] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3-stroke] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3-stroke] 32x32 (doubleres).png new file mode 100644 index 0000000000..04b84b86c1 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3-stroke] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3] 32x32 (doubleres).png new file mode 100644 index 0000000000..361b294980 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 3] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4-stroke] 32x10 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4-stroke] 32x10 (doubleres).png new file mode 100644 index 0000000000..ff3097a759 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4-stroke] 32x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4] 32x10 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4] 32x10 (doubleres).png new file mode 100644 index 0000000000..a88e669d76 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-jis2 4] 32x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1-stroke] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1-stroke] 32x32 (doubleres).png new file mode 100644 index 0000000000..ad084eb958 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1-stroke] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1] 32x32 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1] 32x32 (doubleres).png new file mode 100644 index 0000000000..9b334e404a Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 1] 32x32 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2-stroke] 32x29 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2-stroke] 32x29 (doubleres).png new file mode 100644 index 0000000000..158b5f0222 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2-stroke] 32x29 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2] 32x29 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2] 32x29 (doubleres).png new file mode 100644 index 0000000000..4812ac0d50 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [kanji-regular 2] 32x29 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [main-stroke] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [main-stroke] 16x16 (doubleres).png new file mode 100644 index 0000000000..f07b865fd1 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [main-stroke] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [main] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [main] 16x16 (doubleres).png new file mode 100644 index 0000000000..684de62c7c Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [main] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol-stroke] 26x3 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol-stroke] 26x3 (doubleres).png new file mode 100644 index 0000000000..b679229e88 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol-stroke] 26x3 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol2-stroke] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol2-stroke] 16x16 (doubleres).png new file mode 100644 index 0000000000..d96424cb7c Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol2-stroke] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol2] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol2] 16x16 (doubleres).png new file mode 100644 index 0000000000..8804a9731e Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol2] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol3-stroke] 16x6 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol3-stroke] 16x6 (doubleres).png new file mode 100644 index 0000000000..77bc4991ba Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol3-stroke] 16x6 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol3] 16x6 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol3] 16x6 (doubleres).png new file mode 100644 index 0000000000..1bb94b4922 Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol3] 16x6 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px [symbol] 26x3 (doubleres).png b/Themes/Rebirth/Fonts/_japanese 24px [symbol] 26x3 (doubleres).png new file mode 100644 index 0000000000..5a8f20874a Binary files /dev/null and b/Themes/Rebirth/Fonts/_japanese 24px [symbol] 26x3 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_japanese 24px.ini b/Themes/Rebirth/Fonts/_japanese 24px.ini new file mode 100644 index 0000000000..49b89a22af --- /dev/null +++ b/Themes/Rebirth/Fonts/_japanese 24px.ini @@ -0,0 +1,7848 @@ +[common] +Baseline=26 +Top=7 +LineSpacing=32 +DrawExtraPixelsLeft=2 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=1 +DefaultStrokeColor=#FFFFFF00 + +[main] +range basic-japanese=0 + +0=9 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=1365022312 +47=1365022312 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=16 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=16 +152=16 +153=0 +154=0 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 + +[ank] +Line 0= !"#$%&'()*+,-./ +Line 1=0123456789:;<=>? +Line 2=@ABCDEFGHIJKLMNO +Line 3=PQRSTUVWXYZ[\]^_ +Line 4=`abcdefghijklmno +Line 5=pqrstuvwxyz{|}~ +Line 6=。「」、・ヲァィゥェォャュョッー +Line 7=アイウエオカキクケコサシスセソタ +Line 8=チツテトナニヌネノハヒフヘホマミ +Line 9=ムメモヤユヨラリルレロワン゙゚ + +0=7 +1=7 +2=9 +3=16 +4=13 +5=20 +6=18 +7=6 +8=7 +9=7 +10=11 +11=17 +12=6 +13=10 +14=6 +15=10 +16=13 +17=9 +18=13 +19=13 +20=14 +21=13 +22=13 +23=13 +24=13 +25=13 +26=6 +27=6 +28=17 +29=17 +30=17 +31=11 +32=24 +33=16 +34=14 +35=16 +36=17 +37=13 +38=12 +39=17 +40=18 +41=6 +42=8 +43=14 +44=12 +45=22 +46=18 +47=20 +48=14 +49=20 +50=14 +51=13 +52=13 +53=17 +54=16 +55=23 +56=15 +57=14 +58=15 +59=7 +60=10 +61=7 +62=17 +63=11 +64=6 +65=13 +66=15 +67=12 +68=15 +69=13 +70=7 +71=15 +72=14 +73=5 +74=5 +75=12 +76=5 +77=21 +78=14 +79=15 +80=15 +81=15 +82=9 +83=10 +84=8 +85=14 +86=12 +87=18 +88=11 +89=12 +90=12 +91=7 +92=6 +93=7 +94=17 +95=7 +96=12 +97=12 +98=12 +99=12 +100=12 +101=12 +102=12 +103=12 +104=12 +105=12 +106=12 +107=12 +108=12 +109=12 +110=12 +111=12 +112=12 +113=12 +114=12 +115=12 +116=12 +117=12 +118=12 +119=12 +120=12 +121=12 +122=12 +123=12 +124=12 +125=12 +126=12 +127=12 +128=12 +129=12 +130=12 +131=12 +132=12 +133=12 +134=12 +135=12 +136=12 +137=12 +138=12 +139=12 +140=12 +141=12 +142=12 +143=12 +144=12 +145=12 +146=12 +147=12 +148=12 +149=12 +150=12 +151=12 +152=12 +153=12 +154=12 +155=12 +156=12 +157=12 +158=12 + +[symbol] +Line 0=&()?!/‥^…@$#”+=¥;<>・「」{}%* +Line 1=abcdefghijklmnopqrstuvwxyz +Line 2=ABCDEFGHIJKLMNOPQRSTUVWXYZ + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=11 +7=24 +8=17 +9=24 +10=24 +11=24 +12=9 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 + +[symbol2] +Line 0=,.:´`¨‾_仝—‐\‖|‘’ +Line 1=“[]−±×÷≠≦≧∞∴♂♀°′ +Line 2=″℃¢£§☆★○●◎◇◆□■△▲ +Line 3=▽▼※→←↑↓∈∋⊆⊇⊂⊃∪∩∧ +Line 4=∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√ +Line 5=∽∝∵∫∬ʼn♯♭♪†‡¶◯01 +Line 6=23456789ΑΒΓΔΕΖΗΘ +Line 7=ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ +Line 8=αβγδεζηθικλμνξοπ +Line 9=ρστυχψωАБВГДЕЁЖЗ +Line 10=ИЙКЛМНОПРСТУФХЦЧ +Line 11=ШЩЪЫЬЭЮЯабвгдеёж +Line 12=зийклмнопрстуфхц +Line 13=чшщъыьэюя─│┌┐┘└├ +Line 14=┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠ +Line 15=┯┨┷┿┝┰┥┸╂ + +0=24 +1=24 +2=24 +3=6 +4=24 +5=10 +6=11 +7=24 +8=24 +9=26 +10=10 +11=24 +12=10 +13=24 +14=6 +15=6 +16=9 +17=24 +18=24 +19=17 +20=17 +21=17 +22=17 +23=17 +24=17 +25=17 +26=21 +27=18 +28=18 +29=18 +30=10 +31=6 +32=10 +33=24 +34=13 +35=13 +36=12 +37=20 +38=20 +39=21 +40=15 +41=14 +42=14 +43=14 +44=15 +45=21 +46=14 +47=21 +48=14 +49=21 +50=20 +51=22 +52=22 +53=12 +54=12 +55=20 +56=16 +57=16 +58=16 +59=16 +60=16 +61=18 +62=18 +63=17 +64=17 +65=17 +66=16 +67=16 +68=16 +69=16 +70=18 +71=18 +72=26 +73=13 +74=16 +75=17 +76=17 +77=16 +78=16 +79=17 +80=17 +81=21 +82=18 +83=13 +84=16 +85=16 +86=31 +87=16 +88=16 +89=16 +90=10 +91=10 +92=12 +93=16 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=16 +105=14 +106=12 +107=16 +108=13 +109=15 +110=18 +111=20 +112=6 +113=14 +114=16 +115=22 +116=18 +117=13 +118=20 +119=18 +120=14 +121=13 +122=13 +123=14 +124=20 +125=15 +126=20 +127=20 +128=15 +129=13 +130=13 +131=15 +132=11 +133=11 +134=14 +135=15 +136=7 +137=14 +138=13 +139=14 +140=14 +141=12 +142=15 +143=16 +144=15 +145=15 +146=13 +147=15 +148=14 +149=19 +150=21 +151=16 +152=14 +153=14 +154=11 +155=17 +156=13 +157=13 +158=21 +159=14 +160=18 +161=18 +162=14 +163=17 +164=22 +165=18 +166=20 +167=18 +168=14 +169=16 +170=13 +171=14 +172=18 +173=15 +174=18 +175=16 +176=22 +177=23 +178=18 +179=20 +180=14 +181=16 +182=25 +183=14 +184=13 +185=14 +186=12 +187=9 +188=14 +189=13 +190=13 +191=17 +192=12 +193=14 +194=14 +195=11 +196=13 +197=16 +198=14 +199=15 +200=14 +201=15 +202=12 +203=10 +204=12 +205=17 +206=11 +207=15 +208=14 +209=19 +210=20 +211=14 +212=17 +213=12 +214=12 +215=20 +216=13 +217=17 +218=17 +219=17 +220=17 +221=17 +222=17 +223=17 +224=17 +225=17 +226=17 +227=17 +228=14 +229=14 +230=14 +231=14 +232=14 +233=14 +234=14 +235=14 +236=14 +237=14 +238=14 +239=14 +240=14 +241=14 +242=14 +243=14 +244=14 +245=14 +246=14 +247=14 +248=14 + +[symbol3] +Line 0=①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯ +Line 1=⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔ +Line 2=㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝ +Line 3=㎞㎎㎏㏄㎡㍻№㏍℡㊤㊥㊦㊧㊨㈱㈲ +Line 4=㈹㍾㍽㍼∮∟⊿ⅰⅱⅲⅳⅴⅵⅶⅷⅸ +Line 5=ⅹ¦'" + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=16 +11=16 +12=16 +13=16 +14=16 +15=16 +16=16 +17=16 +18=16 +19=16 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=16 +31=16 +32=16 +33=16 +34=16 +35=16 +36=16 +37=16 +38=16 +39=16 +40=16 +41=16 +42=16 +43=16 +44=16 +45=16 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=16 +54=29 +55=16 +56=24 +57=16 +58=16 +59=16 +60=16 +61=16 +62=24 +63=16 +64=16 +65=16 +66=16 +67=16 +68=13 +69=18 +70=18 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 + +[kanji-regular 1] +Line 0=亜哀愛悪握圧扱安暗案以位依偉囲委威尉意慰易為異移維緯胃衣違遺医井 +Line 1=域育一壱逸稲芋印員因姻引飲院陰隠韻右宇羽雨渦浦運雲営影映栄永泳英 +Line 2=衛詠鋭液疫益駅悦謁越閲円園宴延援沿演炎煙猿縁遠鉛塩汚凹央奥往応押 +Line 3=横欧殴王翁黄沖億屋憶乙卸恩温穏音下化仮何価佳加可夏嫁家寡科暇果架 +Line 4=歌河火禍稼箇花荷華菓課貨過蚊我画芽賀雅餓介会解回塊壊快怪悔懐戒拐 +Line 5=改械海灰界皆絵開階貝劾外害慨概涯街該垣嚇各拡格核殻獲確穫覚角較郭 +Line 6=閣隔革学岳楽額掛潟割喝括活渇滑褐轄且株刈乾冠寒刊勘勧巻喚堪完官寛 +Line 7=干幹患感慣憾換敢棺款歓汗漢環甘監看管簡緩缶肝艦観貫還鑑間閑関陥館 +Line 8=丸含岸眼岩頑顔願企危喜器基奇寄岐希幾忌揮机旗既期棋棄機帰気汽祈季 +Line 9=紀規記貴起軌輝飢騎鬼偽儀宜戯技擬欺犠疑義議菊吉喫詰却客脚虐逆丘久 +Line 10=休及吸宮弓急救朽求泣球究窮級糾給旧牛去居巨拒拠挙虚許距漁魚享京供 +Line 11=競共凶協叫境峡強恐恭挟教橋況狂狭矯胸脅興郷鏡響驚仰凝暁業局曲極玉 +Line 12=勤均斤琴禁筋緊菌襟謹近金吟銀九句区苦駆具愚虞空偶遇隅屈掘靴繰桑勲 +Line 13=君薫訓群軍郡係傾刑兄啓型契形径恵慶憩掲携敬景渓系経継茎蛍計警軽鶏 +Line 14=芸迎鯨劇撃激傑欠決潔穴結血月件倹健兼券剣圏堅嫌建憲懸検権犬献研絹 +Line 15=県肩見謙賢軒遣険顕験元原厳幻弦減源玄現言限個古呼固孤己庫弧戸故枯 +Line 16=湖誇雇顧鼓五互午呉娯後御悟碁語誤護交侯候光公功効厚口向后坑好孔孝 +Line 17=工巧幸広康恒慌抗拘控攻更校構江洪港溝甲皇硬稿紅絞綱耕考肯航荒行衡 +Line 18=講貢購郊酵鉱鋼降項香高剛号合拷豪克刻告国穀酷黒獄腰骨込今困墾婚恨 +Line 19=懇昆根混紺魂佐唆左差査砂詐鎖座債催再最妻宰彩才採栽歳済災砕祭斎細 +Line 20=菜裁載際剤在材罪財坂咲崎作削搾昨策索錯桜冊刷察撮擦札殺雑皿三傘参 +Line 21=山惨散桟産算蚕賛酸暫残仕伺使刺司史嗣四士始姉姿子市師志思指支施旨 +Line 22=枝止死氏祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時 +Line 23=次滋治璽磁示耳自辞式識軸七執失室湿漆疾質実芝舎写射捨赦斜煮社者謝 +Line 24=車遮蛇邪借勺尺爵酌釈若寂弱主取守手朱殊狩珠種趣酒首儒受寿授樹需囚 +Line 25=収周宗就州修愁拾秀秋終習臭舟衆襲週酬集醜住充十従柔汁渋獣縦重銃叔 +Line 26=宿淑祝縮粛塾熟出術述俊春瞬准循旬殉準潤盾純巡遵順処初所暑庶緒署書 +Line 27=諸助叙女序徐除傷償勝匠升召商唱奨宵将小少尚床彰承抄招掌昇昭晶松沼 +Line 28=消渉焼焦照症省硝礁祥称章笑粧紹肖衝訟証詔詳象賞鐘障上丈乗冗剰城場 +Line 29=壌嬢常情条浄状畳蒸譲醸錠嘱飾植殖織職色触食辱伸信侵唇娠寝審心慎振 +Line 30=新森浸深申真神紳臣薪親診身辛進針震人仁刃尋甚尽迅陣酢図吹垂帥推水 +Line 31=炊睡粋衰遂酔錘随髄崇数枢据杉澄寸世瀬畝是制勢姓征性成政整星晴正清 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 +921=24 +922=24 +923=24 +924=24 +925=24 +926=24 +927=24 +928=24 +929=24 +930=24 +931=24 +932=24 +933=24 +934=24 +935=24 +936=24 +937=24 +938=24 +939=24 +940=24 +941=24 +942=24 +943=24 +944=24 +945=24 +946=24 +947=24 +948=24 +949=24 +950=24 +951=24 +952=24 +953=24 +954=24 +955=24 +956=24 +957=24 +958=24 +959=24 +960=24 +961=24 +962=24 +963=24 +964=24 +965=24 +966=24 +967=24 +968=24 +969=24 +970=24 +971=24 +972=24 +973=24 +974=24 +975=24 +976=24 +977=24 +978=24 +979=24 +980=24 +981=24 +982=24 +983=24 +984=24 +985=24 +986=24 +987=24 +988=24 +989=24 +990=24 +991=24 +992=24 +993=24 +994=24 +995=24 +996=24 +997=24 +998=24 +999=24 +1000=24 +1001=24 +1002=24 +1003=24 +1004=24 +1005=24 +1006=24 +1007=24 +1008=24 +1009=24 +1010=24 +1011=24 +1012=24 +1013=24 +1014=24 +1015=24 +1016=24 +1017=24 +1018=24 +1019=24 +1020=24 +1021=24 +1022=24 +1023=24 + +[kanji-regular 2] +Line 0=牲生盛精聖声製西誠誓請逝青静斉税隻席惜斥昔析石積籍績責赤跡切拙接 +Line 1=摂折設窃節説雪絶舌仙先千占宣専川戦扇栓泉浅洗染潜旋線繊船薦践選遷 +Line 2=銭銑鮮前善漸然全禅繕塑措疎礎祖租粗素組訴阻僧創双倉喪壮奏層想捜掃 +Line 3=挿操早曹巣槽燥争相窓総草荘葬藻装走送遭霜騒像増憎臓蔵贈造促側則即 +Line 4=息束測足速俗属賊族続卒存孫尊損村他多太堕妥惰打駄体対耐帯待怠態替 +Line 5=泰滞胎袋貸退逮隊代台大第題滝卓宅択拓沢濯託濁諾但達奪脱棚谷丹単嘆 +Line 6=担探淡炭短端胆誕鍛団壇弾断暖段男談値知地恥池痴稚置致遅築畜竹蓄逐 +Line 7=秩窒茶嫡着中仲宙忠抽昼柱注虫衷鋳駐著貯丁兆帳庁弔張彫徴懲挑朝潮町 +Line 8=眺聴脹腸調超跳長頂鳥勅直朕沈珍賃鎮陳津墜追痛通塚漬坪釣亭低停偵貞 +Line 9=呈堤定帝底庭廷弟抵提程締艇訂逓邸泥摘敵滴的笛適哲徹撤迭鉄典天展店 +Line 10=添転点伝殿田電吐塗徒斗渡登途都努度土奴怒倒党冬凍刀唐塔島悼投搭東 +Line 11=桃棟盗湯灯当痘等答筒糖統到討謄豆踏逃透陶頭騰闘働動同堂導洞童胴道 +Line 12=銅峠匿得徳特督篤毒独読凸突届屯豚曇鈍内縄南軟難二尼弐肉日乳入如尿 +Line 13=任妊忍認寧猫熱年念燃粘悩濃納能脳農把覇波派破婆馬俳廃拝排敗杯背肺 +Line 14=輩配倍培媒梅買売賠陪伯博拍泊白舶薄迫漠爆縛麦箱肌畑八鉢発髪伐罰抜 +Line 15=閥伴判半反帆搬板版犯班畔繁般藩販範煩頒飯晩番盤蛮卑否妃彼悲扉批披 +Line 16=比泌疲皮碑秘罷肥被費避非飛備尾微美鼻匹必筆姫百俵標氷漂票表評描病 +Line 17=秒苗品浜貧賓頻敏瓶不付夫婦富布府怖扶敷普浮父符腐膚譜負賦赴附侮武 +Line 18=舞部封風伏副復幅服福腹複覆払沸仏物分噴墳憤奮粉紛雰文聞丙併兵塀幣 +Line 19=平弊柄並閉陛米壁癖別偏変片編辺返遍便勉弁保舗捕歩補穂募墓慕暮母簿 +Line 20=倣俸包報奉宝峰崩抱放方法泡砲縫胞芳褒訪豊邦飽乏亡傍剖坊妨帽忘忙房 +Line 21=暴望某棒冒紡肪膨謀貿防北僕墨撲朴牧没堀奔本翻凡盆摩磨魔麻埋妹枚毎 +Line 22=幕膜又抹末繭万慢満漫味未魅岬密脈妙民眠務夢無矛霧婿娘名命明盟迷銘 +Line 23=鳴滅免綿面模茂妄毛猛盲網耗木黙目戻問紋門匁夜野矢厄役約薬訳躍柳愉 +Line 24=油癒諭輸唯優勇友幽悠憂有猶由裕誘遊郵雄融夕予余与誉預幼容庸揚揺擁 +Line 25=曜様洋溶用窯羊葉要謡踊陽養抑欲浴翌翼羅裸来頼雷絡落酪乱卵欄濫覧利 +Line 26=吏履理痢裏里離陸律率立略流留硫粒隆竜慮旅虜了僚両寮料涼猟療糧良量 +Line 27=陵領力緑倫厘林臨輪隣塁涙累類令例冷励礼鈴隷零霊麗齢暦歴列劣烈裂廉 +Line 28=恋練連錬炉路露労廊朗楼浪漏老郎六録論和話賄惑枠湾腕 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 + +[kanji-jis1] +Line 0=唖娃阿挨姶逢葵茜穐渥旭葦芦鯵梓斡宛姐虻飴絢綾鮎或粟袷庵按闇鞍杏伊 +Line 1=夷惟椅畏萎謂亥郁磯溢茨鰯允咽淫胤蔭吋烏迂卯鵜窺丑碓臼嘘唄欝蔚鰻姥 +Line 2=厩瓜閏噂云荏餌叡嬰曳洩瑛盈穎頴榎厭堰奄怨掩焔燕艶苑薗鴛於甥旺襖鴬 +Line 3=鴎岡荻臆桶牡俺伽嘉珂禾苛茄蝦嘩迦霞俄峨牙臥蛾駕廻恢魁晦芥蟹凱咳崖 +Line 4=碍蓋鎧骸浬馨蛙柿蛎鈎劃廓撹赫顎笠樫橿梶鰍恰葛鰹叶椛樺鞄兜竃蒲釜鎌 +Line 5=噛鴨栢茅萱粥苅瓦侃姦柑桓澗潅竿翰莞諌韓舘巌玩癌翫贋雁伎嬉毅畿稀徽 +Line 6=亀妓祇蟻誼掬鞠吃桔橘砧杵黍仇汲灸笈渠鋸禦亨侠僑兇匡卿喬彊怯蕎饗尭 +Line 7=桐粁僅巾錦欣欽禽芹衿倶狗玖矩躯駈駒喰寓串櫛釧屑窟沓轡窪熊隈粂栗鍬 +Line 8=卦袈祁圭珪慧桂畦稽繋罫荊詣頚戟隙桁訣倦喧拳捲牽硯鍵鹸絃舷諺乎姑狐 +Line 9=糊袴股胡菰虎跨鈷伍吾梧檎瑚醐乞鯉佼倖勾喉垢宏巷庚弘昂晃杭梗浩糠紘 +Line 10=肱腔膏砿閤鴻劫壕濠轟麹鵠漉甑忽惚狛此頃坤昏梱痕艮些叉嵯沙瑳裟坐挫 +Line 11=哉塞采犀砦冴阪堺榊肴埼碕鷺咋朔柵窄鮭笹匙拶薩皐鯖捌錆鮫晒撒燦珊纂 +Line 12=讃餐斬仔屍孜斯獅爾痔而蒔汐鹿鴫竺宍雫叱嫉悉蔀篠偲柴屡蕊縞紗杓灼錫 +Line 13=惹腫呪綬洲繍蒐讐蹴輯酋什戎夙峻竣舜駿楯淳醇曙渚薯藷恕鋤哨嘗妾娼庄 +Line 14=廠捷昌梢樟樵湘菖蒋蕉裳醤鉦鍾鞘丞擾杖穣埴拭燭蝕尻晋榛疹秦芯塵壬腎 +Line 15=訊靭笥諏須厨逗翠錐瑞嵩趨雛椙菅頗雀裾摺凄棲栖醒脆戚脊蹟碩蝉尖撰栴 +Line 16=煎煽穿箭羨腺舛詮賎閃膳糎噌岨曾曽楚狙疏蘇遡鼠叢爽宋匝惣掻槍漕痩糟 +Line 17=綜聡蒼鎗捉袖其揃遜汰詑唾柁舵楕陀騨堆岱戴腿苔黛鯛醍鷹瀧啄托琢鐸茸 +Line 18=凧蛸只叩辰巽竪辿狸鱈樽誰坦旦歎湛箪綻耽蛋檀弛智蜘馳筑註酎樗瀦猪苧 +Line 19=凋喋寵帖暢牒蝶諜銚捗椎槌鎚栂掴槻佃柘辻蔦綴鍔椿潰壷嬬紬爪吊鶴剃悌 +Line 20=挺梯汀碇禎諦蹄鄭釘鼎擢鏑溺轍填纏甜貼顛澱兎堵妬屠杜菟賭鍍砥砺塘套 +Line 21=宕嶋梼淘涛燈祷董蕩藤鐙憧撞瞳萄鴇涜禿栃橡椴鳶苫寅酉瀞噸惇敦沌遁頓 +Line 22=呑奈那乍凪薙謎灘捺鍋楢馴畷楠汝迩匂賑虹廿韮濡禰祢葱捻撚乃廼之埜嚢 +Line 23=膿覗蚤巴播杷琶罵芭盃牌楳煤狽這蝿秤矧萩剥柏箔粕曝莫駁函硲箸肇筈櫨 +Line 24=幡畠溌醗筏鳩噺塙蛤隼叛斑氾汎釆挽磐蕃匪庇斐緋誹樋簸枇毘琵眉柊稗疋 +Line 25=髭彦膝菱肘弼畢逼桧媛紐謬彪瓢豹廟錨鋲蒜蛭鰭彬斌瀕埠冨斧芙阜撫葡蕪 +Line 26=楓葺蕗淵弗鮒吻扮焚糞蔽頁僻碧瞥蔑箆篇娩鞭鋪圃甫輔戊菩呆峯庖捧朋烹 +Line 27=萌蓬蜂鋒鳳鵬貌鉾吠頬卜睦穆釦勃殆幌昧哩槙枕鮪柾鱒桝亦俣沫迄侭麿蔓 +Line 28=巳箕蜜湊蓑稔粍牟鵡椋冥姪牝棉緬麺摸孟蒙儲杢勿餅尤籾貰悶也冶爺耶弥 +Line 29=靖薮鑓愈佑宥揖柚湧涌猷祐邑輿傭妖楊熔耀蓉遥慾沃淀螺莱洛嵐藍蘭李梨 +Line 30=璃裡葎掠劉溜琉龍侶亮凌梁瞭稜諒遼淋燐琳鱗麟瑠伶嶺怜玲苓憐漣煉簾聯 +Line 31=蓮呂魯櫓賂婁弄榔牢狼篭聾蝋麓禄肋倭歪脇鷲亙亘鰐詫藁蕨椀碗 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 +921=24 +922=24 +923=24 +924=24 +925=24 +926=24 +927=24 +928=24 +929=24 +930=24 +931=24 +932=24 +933=24 +934=24 +935=24 +936=24 +937=24 +938=24 +939=24 +940=24 +941=24 +942=24 +943=24 +944=24 +945=24 +946=24 +947=24 +948=24 +949=24 +950=24 +951=24 +952=24 +953=24 +954=24 +955=24 +956=24 +957=24 +958=24 +959=24 +960=24 +961=24 +962=24 +963=24 +964=24 +965=24 +966=24 +967=24 +968=24 +969=24 +970=24 +971=24 +972=24 +973=24 +974=24 +975=24 +976=24 +977=24 +978=24 +979=24 +980=24 +981=24 +982=24 +983=24 +984=24 +985=24 +986=24 +987=24 +988=24 +989=24 +990=24 +991=24 +992=24 +993=24 +994=24 +995=24 +996=24 +997=24 +998=24 +999=24 +1000=24 +1001=24 +1002=24 +1003=24 +1004=24 +1005=24 +1006=24 +1007=24 +1008=24 +1009=24 +1010=24 +1011=24 +1012=24 +1013=24 +1014=24 +1015=24 +1016=24 +1017=24 +1018=24 +1019=24 + +[kanji-jis2 1] +Line 0=弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞 +Line 1=仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥 +Line 2=倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲僉僊 +Line 3=傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮 +Line 4=冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭凰凵凾 +Line 5=刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼 +Line 6=劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍 +Line 7=凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮 +Line 8=吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨咫哂咤咾咼哘 +Line 9=哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼 +Line 10=喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸噫噤嘯噬噪嚆嚀 +Line 11=嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉圈國圍圓團圖嗇圜 +Line 12=圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡 +Line 13=塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽 +Line 14=夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩奸妁妝佞侫妣妲姆姨姜 +Line 15=妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖 +Line 16=嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀它宦宸寃寇寉寔寐寤實寢 +Line 17=寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓屐屏孱屬屮乢屶屹岌岑岔妛 +Line 18=岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬 +Line 19=嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀 +Line 20=幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏廖廣廝廚廛廢廡廨廩廬廱廳廰廴 +Line 21=廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇 +Line 22=從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷 +Line 23=恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵 +Line 24=惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚 +Line 25=慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣 +Line 26=懶懺懴懿懽懼懾戀戈戉戍戌戔戛戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找 +Line 27=抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍 +Line 28=搜捏掖掎掀掫捶掣掏掉掟掵捫捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨 +Line 29=搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺 +Line 30=攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆 +Line 31=旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 +921=24 +922=24 +923=24 +924=24 +925=24 +926=24 +927=24 +928=24 +929=24 +930=24 +931=24 +932=24 +933=24 +934=24 +935=24 +936=24 +937=24 +938=24 +939=24 +940=24 +941=24 +942=24 +943=24 +944=24 +945=24 +946=24 +947=24 +948=24 +949=24 +950=24 +951=24 +952=24 +953=24 +954=24 +955=24 +956=24 +957=24 +958=24 +959=24 +960=24 +961=24 +962=24 +963=24 +964=24 +965=24 +966=24 +967=24 +968=24 +969=24 +970=24 +971=24 +972=24 +973=24 +974=24 +975=24 +976=24 +977=24 +978=24 +979=24 +980=24 +981=24 +982=24 +983=24 +984=24 +985=24 +986=24 +987=24 +988=24 +989=24 +990=24 +991=24 +992=24 +993=24 +994=24 +995=24 +996=24 +997=24 +998=24 +999=24 +1000=24 +1001=24 +1002=24 +1003=24 +1004=24 +1005=24 +1006=24 +1007=24 +1008=24 +1009=24 +1010=24 +1011=24 +1012=24 +1013=24 +1014=24 +1015=24 +1016=24 +1017=24 +1018=24 +1019=24 +1020=24 +1021=24 +1022=24 +1023=24 + +[kanji-jis2 2] +Line 0=暎暉暄暘暝曁暹曉暾暼曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸 +Line 1=朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆 +Line 2=柧檜栞框栩桀桍栲桎梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁 +Line 3=棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔 +Line 4=楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴 +Line 5=槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢 +Line 6=檐檍檠檄檢檣檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹 +Line 7=飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓 +Line 8=毟毬毫毳毯麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅 +Line 9=泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕 +Line 10=渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃 +Line 11=渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯 +Line 12=漲滌漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔 +Line 13=濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋 +Line 14=烝烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼 +Line 15=燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎 +Line 16=狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻 +Line 17=珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱瓠瓣 +Line 18=瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭 +Line 19=畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿痼瘁痰 +Line 20=痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發 +Line 21=皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥 +Line 22=眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮 +Line 23=矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬磧磚磽磴礇礒 +Line 24=礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙 +Line 25=稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰窶竅竄窿邃竇竊 +Line 26=竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥 +Line 27=筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇 +Line 28=簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫 +Line 29=粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆紂紜紕紊絅絋紮紲紿紵 +Line 30=絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤 +Line 31=緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷縲縺繧繝繖繞繙繚繹繪繩 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 +921=24 +922=24 +923=24 +924=24 +925=24 +926=24 +927=24 +928=24 +929=24 +930=24 +931=24 +932=24 +933=24 +934=24 +935=24 +936=24 +937=24 +938=24 +939=24 +940=24 +941=24 +942=24 +943=24 +944=24 +945=24 +946=24 +947=24 +948=24 +949=24 +950=24 +951=24 +952=24 +953=24 +954=24 +955=24 +956=24 +957=24 +958=24 +959=24 +960=24 +961=24 +962=24 +963=24 +964=24 +965=24 +966=24 +967=24 +968=24 +969=24 +970=24 +971=24 +972=24 +973=24 +974=24 +975=24 +976=24 +977=24 +978=24 +979=24 +980=24 +981=24 +982=24 +983=24 +984=24 +985=24 +986=24 +987=24 +988=24 +989=24 +990=24 +991=24 +992=24 +993=24 +994=24 +995=24 +996=24 +997=24 +998=24 +999=24 +1000=24 +1001=24 +1002=24 +1003=24 +1004=24 +1005=24 +1006=24 +1007=24 +1008=24 +1009=24 +1010=24 +1011=24 +1012=24 +1013=24 +1014=24 +1015=24 +1016=24 +1017=24 +1018=24 +1019=24 +1020=24 +1021=24 +1022=24 +1023=24 + +[kanji-jis2 3] +Line 0=繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐网罕罔罘罟罠罨 +Line 1=罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜 +Line 2=耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚 +Line 3=肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋隋腆脾腓腑胼腱腮腥腦腴膃膈膊 +Line 4=膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂 +Line 5=舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬 +Line 6=苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪 +Line 7=莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸 +Line 8=蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡 +Line 9=蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾 +Line 10=薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋 +Line 11=蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥 +Line 12=蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯 +Line 13=蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵 +Line 14=衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪 +Line 15=褫襁襄褻褶褸襌褝襠襞襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿 +Line 16=觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦 +Line 17=誚誣諄諍諂諚諫諳諧諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁 +Line 18=譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸 +Line 19=豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍 +Line 20=贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐 +Line 21=踟蹂踵踰踴蹊蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡 +Line 22=躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌 +Line 23=轉轆轎轗轜轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧 +Line 24=逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤 +Line 25=扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟 +Line 26=釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺 +Line 27=鍄錮錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓 +Line 28=鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘 +Line 29=閙閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞 +Line 30=陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎 +Line 31=霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=24 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=24 +342=24 +343=24 +344=24 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=24 +358=24 +359=24 +360=24 +361=24 +362=24 +363=24 +364=24 +365=24 +366=24 +367=24 +368=24 +369=24 +370=24 +371=24 +372=24 +373=24 +374=24 +375=24 +376=24 +377=24 +378=24 +379=24 +380=24 +381=24 +382=24 +383=24 +384=24 +385=24 +386=24 +387=24 +388=24 +389=24 +390=24 +391=24 +392=24 +393=24 +394=24 +395=24 +396=24 +397=24 +398=24 +399=24 +400=24 +401=24 +402=24 +403=24 +404=24 +405=24 +406=24 +407=24 +408=24 +409=24 +410=24 +411=24 +412=24 +413=24 +414=24 +415=24 +416=24 +417=24 +418=24 +419=24 +420=24 +421=24 +422=24 +423=24 +424=24 +425=24 +426=24 +427=24 +428=24 +429=24 +430=24 +431=24 +432=24 +433=24 +434=24 +435=24 +436=24 +437=24 +438=24 +439=24 +440=24 +441=24 +442=24 +443=24 +444=24 +445=24 +446=24 +447=24 +448=24 +449=24 +450=24 +451=24 +452=24 +453=24 +454=24 +455=24 +456=24 +457=24 +458=24 +459=24 +460=24 +461=24 +462=24 +463=24 +464=24 +465=24 +466=24 +467=24 +468=24 +469=24 +470=24 +471=24 +472=24 +473=24 +474=24 +475=24 +476=24 +477=24 +478=24 +479=24 +480=24 +481=24 +482=24 +483=24 +484=24 +485=24 +486=24 +487=24 +488=24 +489=24 +490=24 +491=24 +492=24 +493=24 +494=24 +495=24 +496=24 +497=24 +498=24 +499=24 +500=24 +501=24 +502=24 +503=24 +504=24 +505=24 +506=24 +507=24 +508=24 +509=24 +510=24 +511=24 +512=24 +513=24 +514=24 +515=24 +516=24 +517=24 +518=24 +519=24 +520=24 +521=24 +522=24 +523=24 +524=24 +525=24 +526=24 +527=24 +528=24 +529=24 +530=24 +531=24 +532=24 +533=24 +534=24 +535=24 +536=24 +537=24 +538=24 +539=24 +540=24 +541=24 +542=24 +543=24 +544=24 +545=24 +546=24 +547=24 +548=24 +549=24 +550=24 +551=24 +552=24 +553=24 +554=24 +555=24 +556=24 +557=24 +558=24 +559=24 +560=24 +561=24 +562=24 +563=24 +564=24 +565=24 +566=24 +567=24 +568=24 +569=24 +570=24 +571=24 +572=24 +573=24 +574=24 +575=24 +576=24 +577=24 +578=24 +579=24 +580=24 +581=24 +582=24 +583=24 +584=24 +585=24 +586=24 +587=24 +588=24 +589=24 +590=24 +591=24 +592=24 +593=24 +594=24 +595=24 +596=24 +597=24 +598=24 +599=24 +600=24 +601=24 +602=24 +603=24 +604=24 +605=24 +606=24 +607=24 +608=24 +609=24 +610=24 +611=24 +612=24 +613=24 +614=24 +615=24 +616=24 +617=24 +618=24 +619=24 +620=24 +621=24 +622=24 +623=24 +624=24 +625=24 +626=24 +627=24 +628=24 +629=24 +630=24 +631=24 +632=24 +633=24 +634=24 +635=24 +636=24 +637=24 +638=24 +639=24 +640=24 +641=24 +642=24 +643=24 +644=24 +645=24 +646=24 +647=24 +648=24 +649=24 +650=24 +651=24 +652=24 +653=24 +654=24 +655=24 +656=24 +657=24 +658=24 +659=24 +660=24 +661=24 +662=24 +663=24 +664=24 +665=24 +666=24 +667=24 +668=24 +669=24 +670=24 +671=24 +672=24 +673=24 +674=24 +675=24 +676=24 +677=24 +678=24 +679=24 +680=24 +681=24 +682=24 +683=24 +684=24 +685=24 +686=24 +687=24 +688=24 +689=24 +690=24 +691=24 +692=24 +693=24 +694=24 +695=24 +696=24 +697=24 +698=24 +699=24 +700=24 +701=24 +702=24 +703=24 +704=24 +705=24 +706=24 +707=24 +708=24 +709=24 +710=24 +711=24 +712=24 +713=24 +714=24 +715=24 +716=24 +717=24 +718=24 +719=24 +720=24 +721=24 +722=24 +723=24 +724=24 +725=24 +726=24 +727=24 +728=24 +729=24 +730=24 +731=24 +732=24 +733=24 +734=24 +735=24 +736=24 +737=24 +738=24 +739=24 +740=24 +741=24 +742=24 +743=24 +744=24 +745=24 +746=24 +747=24 +748=24 +749=24 +750=24 +751=24 +752=24 +753=24 +754=24 +755=24 +756=24 +757=24 +758=24 +759=24 +760=24 +761=24 +762=24 +763=24 +764=24 +765=24 +766=24 +767=24 +768=24 +769=24 +770=24 +771=24 +772=24 +773=24 +774=24 +775=24 +776=24 +777=24 +778=24 +779=24 +780=24 +781=24 +782=24 +783=24 +784=24 +785=24 +786=24 +787=24 +788=24 +789=24 +790=24 +791=24 +792=24 +793=24 +794=24 +795=24 +796=24 +797=24 +798=24 +799=24 +800=24 +801=24 +802=24 +803=24 +804=24 +805=24 +806=24 +807=24 +808=24 +809=24 +810=24 +811=24 +812=24 +813=24 +814=24 +815=24 +816=24 +817=24 +818=24 +819=24 +820=24 +821=24 +822=24 +823=24 +824=24 +825=24 +826=24 +827=24 +828=24 +829=24 +830=24 +831=24 +832=24 +833=24 +834=24 +835=24 +836=24 +837=24 +838=24 +839=24 +840=24 +841=24 +842=24 +843=24 +844=24 +845=24 +846=24 +847=24 +848=24 +849=24 +850=24 +851=24 +852=24 +853=24 +854=24 +855=24 +856=24 +857=24 +858=24 +859=24 +860=24 +861=24 +862=24 +863=24 +864=24 +865=24 +866=24 +867=24 +868=24 +869=24 +870=24 +871=24 +872=24 +873=24 +874=24 +875=24 +876=24 +877=24 +878=24 +879=24 +880=24 +881=24 +882=24 +883=24 +884=24 +885=24 +886=24 +887=24 +888=24 +889=24 +890=24 +891=24 +892=24 +893=24 +894=24 +895=24 +896=24 +897=24 +898=24 +899=24 +900=24 +901=24 +902=24 +903=24 +904=24 +905=24 +906=24 +907=24 +908=24 +909=24 +910=24 +911=24 +912=24 +913=24 +914=24 +915=24 +916=24 +917=24 +918=24 +919=24 +920=24 +921=24 +922=24 +923=24 +924=24 +925=24 +926=24 +927=24 +928=24 +929=24 +930=24 +931=24 +932=24 +933=24 +934=24 +935=24 +936=24 +937=24 +938=24 +939=24 +940=24 +941=24 +942=24 +943=24 +944=24 +945=24 +946=24 +947=24 +948=24 +949=24 +950=24 +951=24 +952=24 +953=24 +954=24 +955=24 +956=24 +957=24 +958=24 +959=24 +960=24 +961=24 +962=24 +963=24 +964=24 +965=24 +966=24 +967=24 +968=24 +969=24 +970=24 +971=24 +972=24 +973=24 +974=24 +975=24 +976=24 +977=24 +978=24 +979=24 +980=24 +981=24 +982=24 +983=24 +984=24 +985=24 +986=24 +987=24 +988=24 +989=24 +990=24 +991=24 +992=24 +993=24 +994=24 +995=24 +996=24 +997=24 +998=24 +999=24 +1000=24 +1001=24 +1002=24 +1003=24 +1004=24 +1005=24 +1006=24 +1007=24 +1008=24 +1009=24 +1010=24 +1011=24 +1012=24 +1013=24 +1014=24 +1015=24 +1016=24 +1017=24 +1018=24 +1019=24 +1020=24 +1021=24 +1022=24 +1023=24 + +[kanji-jis2 4] +Line 0=鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰顱顴 +Line 1=顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌 +Line 2=饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃騾驕驍 +Line 3=驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚 +Line 4=鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆 +Line 5=鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾 +Line 6=鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈鵝鵞鵤鵑鵐鵙 +Line 7=鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁 +Line 8=麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯黴黶黷黹黻黼黽 +Line 9=鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=24 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=24 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=24 +185=24 +186=24 +187=24 +188=24 +189=24 +190=24 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=24 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=24 +223=24 +224=24 +225=24 +226=24 +227=24 +228=24 +229=24 +230=24 +231=24 +232=24 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=24 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=24 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=24 +276=24 +277=24 +278=24 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 + +[kanji-ibm] +Line 0=纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔 +Line 1=僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚 +Line 2=增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝 +Line 3=悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲 +Line 4=暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯 +Line 5=涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷 +Line 6=玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥 +Line 7=禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡 +Line 8=蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥 +Line 9=鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗 +Line 10=鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏 +Line 11=鮱鮻鰀鵰鵫鶴鸙黑 + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=16 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 +84=24 +85=24 +86=24 +87=24 +88=24 +89=24 +90=24 +91=24 +92=24 +93=24 +94=24 +95=24 +96=24 +97=24 +98=24 +99=24 +100=24 +101=24 +102=24 +103=24 +104=24 +105=24 +106=24 +107=24 +108=24 +109=24 +110=24 +111=24 +112=24 +113=24 +114=24 +115=24 +116=24 +117=24 +118=24 +119=24 +120=24 +121=24 +122=24 +123=16 +124=24 +125=24 +126=24 +127=24 +128=24 +129=24 +130=24 +131=16 +132=24 +133=24 +134=24 +135=24 +136=24 +137=24 +138=24 +139=24 +140=24 +141=24 +142=24 +143=24 +144=24 +145=24 +146=24 +147=24 +148=24 +149=24 +150=24 +151=24 +152=24 +153=24 +154=24 +155=24 +156=24 +157=24 +158=24 +159=24 +160=24 +161=24 +162=24 +163=24 +164=24 +165=24 +166=24 +167=24 +168=24 +169=24 +170=24 +171=24 +172=24 +173=24 +174=24 +175=24 +176=24 +177=24 +178=24 +179=24 +180=24 +181=24 +182=24 +183=24 +184=16 +185=24 +186=24 +187=24 +188=24 +189=24 +190=16 +191=24 +192=24 +193=24 +194=24 +195=24 +196=24 +197=24 +198=24 +199=24 +200=24 +201=24 +202=24 +203=24 +204=24 +205=24 +206=24 +207=24 +208=24 +209=24 +210=24 +211=24 +212=24 +213=16 +214=24 +215=24 +216=24 +217=24 +218=24 +219=24 +220=24 +221=24 +222=16 +223=16 +224=24 +225=16 +226=24 +227=24 +228=24 +229=16 +230=24 +231=24 +232=16 +233=24 +234=24 +235=24 +236=24 +237=24 +238=24 +239=24 +240=24 +241=16 +242=24 +243=24 +244=24 +245=24 +246=24 +247=24 +248=24 +249=24 +250=24 +251=24 +252=24 +253=24 +254=24 +255=24 +256=24 +257=24 +258=24 +259=24 +260=24 +261=24 +262=24 +263=24 +264=16 +265=24 +266=24 +267=24 +268=24 +269=24 +270=24 +271=24 +272=24 +273=24 +274=24 +275=16 +276=24 +277=24 +278=16 +279=24 +280=24 +281=24 +282=24 +283=24 +284=24 +285=24 +286=24 +287=24 +288=24 +289=24 +290=24 +291=24 +292=24 +293=24 +294=24 +295=24 +296=24 +297=24 +298=24 +299=24 +300=24 +301=24 +302=24 +303=24 +304=24 +305=24 +306=24 +307=24 +308=24 +309=24 +310=24 +311=24 +312=24 +313=24 +314=24 +315=24 +316=24 +317=24 +318=24 +319=24 +320=24 +321=24 +322=24 +323=24 +324=24 +325=24 +326=24 +327=24 +328=16 +329=24 +330=24 +331=24 +332=24 +333=24 +334=24 +335=24 +336=24 +337=24 +338=24 +339=24 +340=24 +341=16 +342=16 +343=24 +344=16 +345=24 +346=24 +347=24 +348=24 +349=24 +350=24 +351=24 +352=24 +353=24 +354=24 +355=24 +356=24 +357=16 +358=24 +359=24 + +[compatible] +Line 0= ̄―-~ +Line 1=¥¢£¬ + +0=24 +1=26 +2=24 +3=24 +4=13 +5=24 +6=24 +7=24 diff --git a/Themes/Rebirth/Fonts/_open sans 48px [alt] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_open sans 48px [alt] 10x10 (doubleres).png new file mode 100644 index 0000000000..1973b17b3a Binary files /dev/null and b/Themes/Rebirth/Fonts/_open sans 48px [alt] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_open sans 48px [main] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_open sans 48px [main] 15x15 (doubleres).png new file mode 100644 index 0000000000..9b96495980 Binary files /dev/null and b/Themes/Rebirth/Fonts/_open sans 48px [main] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_open sans 48px.ini b/Themes/Rebirth/Fonts/_open sans 48px.ini new file mode 100644 index 0000000000..32c50d2f8e --- /dev/null +++ b/Themes/Rebirth/Fonts/_open sans 48px.ini @@ -0,0 +1,352 @@ +[common] +Baseline=52 +Top=18 +LineSpacing=65 +DrawExtraPixelsLeft=4 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 + +[main] +Line 0= !"#$%&'()*+,-. +Line 1=/0123456789:;<= +Line 2=>?@ABCDEFGHIJKL +Line 3=MNOPQRSTUVWXYZ[ +Line 4=\]^_`abcdefghij +Line 5=klmnopqrstuvwxy +Line 6=z{|}~€‚ƒ„…†‡ˆ‰Š +Line 7=‹ŒŽ‘’“”•–—˜™š›œ +Line 8=žŸ ¡¢£¤¥¦§¨©ª«¬ +Line 9=­®¯°±²³´µ¶·¸¹º» +Line 10=¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊ +Line 11=ËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ +Line 12=ÚÛÜÝÞßàáâãäåæçè +Line 13=éêëìíîïðñòóôõö÷ +Line 14=øùúûüýþÿ + +0=12 +1=13 +2=19 +3=31 +4=27 +5=40 +6=35 +7=11 +8=14 +9=14 +10=26 +11=27 +12=12 +13=15 +14=13 +15=18 +16=27 +17=27 +18=27 +19=27 +20=27 +21=27 +22=27 +23=27 +24=27 +25=27 +26=13 +27=13 +28=27 +29=27 +30=27 +31=21 +32=43 +33=30 +34=31 +35=30 +36=35 +37=27 +38=25 +39=35 +40=35 +41=13 +42=13 +43=29 +44=25 +45=43 +46=36 +47=37 +48=29 +49=37 +50=30 +51=26 +52=27 +53=35 +54=29 +55=44 +56=28 +57=27 +58=27 +59=16 +60=18 +61=16 +62=26 +63=22 +64=28 +65=27 +66=29 +67=23 +68=29 +69=27 +70=16 +71=26 +72=29 +73=12 +74=12 +75=25 +76=12 +77=45 +78=29 +79=29 +80=29 +81=29 +82=20 +83=23 +84=17 +85=29 +86=24 +87=37 +88=25 +89=24 +90=22 +91=18 +92=26 +93=18 +94=27 +95=28 +96=12 +97=28 +98=19 +99=38 +100=24 +101=24 +102=28 +103=58 +104=26 +105=15 +106=44 +107=27 +108=8 +109=8 +110=17 +111=17 +112=18 +113=24 +114=48 +115=28 +116=37 +117=23 +118=15 +119=45 +120=22 +121=27 +122=12 +123=13 +124=27 +125=27 +126=27 +127=27 +128=26 +129=25 +130=28 +131=40 +132=17 +133=24 +134=27 +135=15 +136=40 +137=24 +138=21 +139=27 +140=17 +141=17 +142=28 +143=30 +144=31 +145=13 +146=11 +147=17 +148=18 +149=24 +150=37 +151=37 +152=37 +153=21 +154=30 +155=30 +156=30 +157=30 +158=30 +159=30 +160=42 +161=30 +162=27 +163=27 +164=27 +165=27 +166=13 +167=13 +168=13 +169=13 +170=35 +171=36 +172=37 +173=37 +174=37 +175=37 +176=37 +177=27 +178=37 +179=35 +180=35 +181=35 +182=35 +183=27 +184=29 +185=30 +186=27 +187=27 +188=27 +189=27 +190=27 +191=27 +192=41 +193=23 +194=27 +195=27 +196=27 +197=27 +198=12 +199=12 +200=12 +201=12 +202=29 +203=29 +204=29 +205=29 +206=29 +207=29 +208=29 +209=27 +210=29 +211=29 +212=29 +213=29 +214=29 +215=24 +216=29 +217=24 + +[alt] +Line 0= Ą˘Ł¤ĽŚ§¨Š +Line 1=ŞŤŹ­ŽŻ°ą˛ł +Line 2=´ľśˇ¸šşťź˝ +Line 3=žżŔÁÂĂÄĹĆÇ +Line 4=ČÉĘËĚÍÎĎĐŃ +Line 5=ŇÓÔŐÖ×ŘŮÚŰ +Line 6=ÜÝŢßŕáâăäĺ +Line 7=ćçčéęëěíîď +Line 8=đńňóôőö÷řů +Line 9=úűüýţ˙ + +0=12 +1=30 +2=28 +3=25 +4=27 +5=25 +6=26 +7=25 +8=28 +9=26 +10=26 +11=27 +12=27 +13=15 +14=27 +15=27 +16=21 +17=27 +18=9 +19=13 +20=28 +21=12 +22=23 +23=28 +24=11 +25=23 +26=23 +27=17 +28=22 +29=28 +30=22 +31=22 +32=30 +33=30 +34=30 +35=30 +36=30 +37=25 +38=30 +39=30 +40=30 +41=27 +42=27 +43=27 +44=27 +45=13 +46=13 +47=35 +48=35 +49=36 +50=36 +51=37 +52=37 +53=37 +54=37 +55=27 +56=30 +57=35 +58=35 +59=35 +60=35 +61=27 +62=27 +63=30 +64=20 +65=27 +66=27 +67=27 +68=27 +69=12 +70=23 +71=23 +72=23 +73=27 +74=27 +75=27 +76=27 +77=12 +78=12 +79=29 +80=29 +81=29 +82=29 +83=29 +84=29 +85=29 +86=29 +87=27 +88=20 +89=29 +90=29 +91=29 +92=29 +93=24 +94=17 +95=12 diff --git a/Themes/Rebirth/Fonts/_open sans Bold 48px [alt] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_open sans Bold 48px [alt] 10x10 (doubleres).png new file mode 100644 index 0000000000..f5fa7d832b Binary files /dev/null and b/Themes/Rebirth/Fonts/_open sans Bold 48px [alt] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_open sans Bold 48px [main] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_open sans Bold 48px [main] 15x15 (doubleres).png new file mode 100644 index 0000000000..82e2bf275b Binary files /dev/null and b/Themes/Rebirth/Fonts/_open sans Bold 48px [main] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_open sans Bold 48px.ini b/Themes/Rebirth/Fonts/_open sans Bold 48px.ini new file mode 100644 index 0000000000..92200075eb --- /dev/null +++ b/Themes/Rebirth/Fonts/_open sans Bold 48px.ini @@ -0,0 +1,352 @@ +[common] +Baseline=52 +Top=16 +LineSpacing=65 +DrawExtraPixelsLeft=4 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 + +[main] +Line 0= !"#$%&'()*+,-. +Line 1=/0123456789:;<= +Line 2=>?@ABCDEFGHIJKL +Line 3=MNOPQRSTUVWXYZ[ +Line 4=\]^_`abcdefghij +Line 5=klmnopqrstuvwxy +Line 6=z{|}~€‚ƒ„…†‡ˆ‰Š +Line 7=‹ŒŽ‘’“”•–—˜™š›œ +Line 8=žŸ ¡¢£¤¥¦§¨©ª«¬ +Line 9=­®¯°±²³´µ¶·¸¹º» +Line 10=¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊ +Line 11=ËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ +Line 12=ÚÛÜÝÞßàáâãäåæçè +Line 13=éêëìíîïðñòóôõö÷ +Line 14=øùúûüýþÿ + +0=12 +1=14 +2=23 +3=31 +4=27 +5=43 +6=36 +7=13 +8=16 +9=16 +10=26 +11=27 +12=14 +13=15 +14=14 +15=20 +16=27 +17=27 +18=27 +19=27 +20=27 +21=27 +22=27 +23=27 +24=27 +25=27 +26=14 +27=14 +28=27 +29=27 +30=27 +31=23 +32=43 +33=33 +34=32 +35=31 +36=36 +37=27 +38=26 +39=35 +40=37 +41=16 +42=16 +43=32 +44=27 +45=45 +46=39 +47=38 +48=30 +49=38 +50=32 +51=26 +52=28 +53=36 +54=31 +55=46 +56=32 +57=30 +58=28 +59=16 +60=20 +61=16 +62=26 +63=20 +64=29 +65=29 +66=30 +67=25 +68=30 +69=28 +70=19 +71=27 +72=32 +73=15 +74=15 +75=30 +76=15 +77=47 +78=32 +79=30 +80=30 +81=30 +82=22 +83=24 +84=21 +85=32 +86=27 +87=41 +88=28 +89=27 +90=23 +91=19 +92=26 +93=19 +94=27 +95=27 +96=14 +97=27 +98=25 +99=41 +100=25 +101=25 +102=29 +103=61 +104=26 +105=18 +106=47 +107=28 +108=10 +109=10 +110=21 +111=21 +112=18 +113=24 +114=48 +115=29 +116=36 +117=24 +118=18 +119=47 +120=23 +121=30 +122=12 +123=14 +124=27 +125=27 +126=27 +127=27 +128=26 +129=23 +130=29 +131=40 +132=18 +133=30 +134=27 +135=15 +136=40 +137=24 +138=21 +139=27 +140=18 +141=18 +142=29 +143=32 +144=31 +145=14 +146=10 +147=18 +148=19 +149=30 +150=42 +151=42 +152=42 +153=23 +154=33 +155=33 +156=33 +157=33 +158=33 +159=33 +160=46 +161=31 +162=27 +163=27 +164=27 +165=27 +166=16 +167=16 +168=16 +169=16 +170=36 +171=39 +172=38 +173=38 +174=38 +175=38 +176=38 +177=27 +178=38 +179=36 +180=36 +181=36 +182=36 +183=30 +184=30 +185=34 +186=29 +187=29 +188=29 +189=29 +190=29 +191=29 +192=44 +193=25 +194=28 +195=28 +196=28 +197=28 +198=15 +199=15 +200=15 +201=15 +202=30 +203=32 +204=30 +205=30 +206=30 +207=30 +208=30 +209=27 +210=30 +211=32 +212=32 +213=32 +214=32 +215=27 +216=30 +217=27 + +[alt] +Line 0= Ą˘Ł¤ĽŚ§¨Š +Line 1=ŞŤŹ­ŽŻ°ą˛ł +Line 2=´ľśˇ¸šşťź˝ +Line 3=žżŔÁÂĂÄĹĆÇ +Line 4=ČÉĘËĚÍÎĎĐŃ +Line 5=ŇÓÔŐÖ×ŘŮÚŰ +Line 6=ÜÝŢßŕáâăäĺ +Line 7=ćçčéęëěíîď +Line 8=đńňóôőö÷řů +Line 9=úűüýţ˙ + +0=12 +1=33 +2=29 +3=27 +4=27 +5=27 +6=26 +7=23 +8=29 +9=26 +10=26 +11=28 +12=28 +13=15 +14=28 +15=28 +16=21 +17=29 +18=10 +19=16 +20=29 +21=15 +22=24 +23=29 +24=10 +25=24 +26=24 +27=21 +28=23 +29=28 +30=23 +31=23 +32=32 +33=33 +34=33 +35=33 +36=33 +37=27 +38=31 +39=31 +40=31 +41=27 +42=27 +43=27 +44=27 +45=16 +46=16 +47=36 +48=36 +49=39 +50=39 +51=38 +52=38 +53=38 +54=38 +55=27 +56=32 +57=36 +58=36 +59=36 +60=36 +61=30 +62=28 +63=34 +64=22 +65=29 +66=29 +67=29 +68=29 +69=15 +70=25 +71=25 +72=25 +73=28 +74=28 +75=28 +76=28 +77=15 +78=15 +79=30 +80=31 +81=32 +82=32 +83=30 +84=30 +85=30 +86=30 +87=27 +88=22 +89=32 +90=32 +91=32 +92=32 +93=27 +94=21 +95=15 diff --git a/Themes/Rebirth/Fonts/_theFont 24px [alt-stroke] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [alt-stroke] 10x10 (doubleres).png new file mode 100644 index 0000000000..db97444f69 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [alt-stroke] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [alt] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [alt] 10x10 (doubleres).png new file mode 100644 index 0000000000..f9aeb07fac Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [alt] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [main-stroke] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [main-stroke] 15x15 (doubleres).png new file mode 100644 index 0000000000..955758db19 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [main-stroke] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [main] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [main] 15x15 (doubleres).png new file mode 100644 index 0000000000..58ece030c6 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [main] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [numbers-stroke] 4x4 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [numbers-stroke] 4x4 (doubleres).png new file mode 100644 index 0000000000..d1519b9e56 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [numbers-stroke] 4x4 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [numbers] 4x4 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [numbers] 4x4 (doubleres).png new file mode 100644 index 0000000000..f7d7ad5017 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [numbers] 4x4 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [symbol2-stroke] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [symbol2-stroke] 16x16 (doubleres).png new file mode 100644 index 0000000000..55653035d7 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [symbol2-stroke] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [symbol2] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 24px [symbol2] 16x16 (doubleres).png new file mode 100644 index 0000000000..3c96a252a7 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [symbol2] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 24px [symbol3-stroke] 16x6.png b/Themes/Rebirth/Fonts/_theFont 24px [symbol3-stroke] 16x6.png new file mode 100644 index 0000000000..2a0efdde2a Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 24px [symbol3-stroke] 16x6.png differ diff --git a/Themes/Til Death/Fonts/_bokutachinogothic2bold 24px [symbol3] 16x6.png b/Themes/Rebirth/Fonts/_theFont 24px [symbol3] 16x6.png similarity index 100% rename from Themes/Til Death/Fonts/_bokutachinogothic2bold 24px [symbol3] 16x6.png rename to Themes/Rebirth/Fonts/_theFont 24px [symbol3] 16x6.png diff --git a/Themes/Rebirth/Fonts/_theFont 24px.ini b/Themes/Rebirth/Fonts/_theFont 24px.ini new file mode 100644 index 0000000000..e445b42be6 --- /dev/null +++ b/Themes/Rebirth/Fonts/_theFont 24px.ini @@ -0,0 +1,749 @@ +[common] +Baseline=28 +Top=12 +LineSpacing=32 +DrawExtraPixelsLeft=1 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 + +[main] +Line 0= !"#$%&'()*+,-. +Line 1=/0123456789:;<= +Line 2=>?@ABCDEFGHIJKL +Line 3=MNOPQRSTUVWXYZ[ +Line 4=\]^_`abcdefghij +Line 5=klmnopqrstuvwxy +Line 6=z{|}~€‚ƒ„…†‡ˆ‰Š +Line 7=‹ŒŽ‘’“”•–—˜™š›œ +Line 8=žŸ ¡¢£¤¥¦§¨©ª«¬ +Line 9=­®¯°±²³´µ¶·¸¹º» +Line 10=¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊ +Line 11=ËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ +Line 12=ÚÛÜÝÞßàáâãäåæçè +Line 13=éêëìíîïðñòóôõö÷ +Line 14=øùúûüýþÿ + +0=5 +1=5 +2=8 +3=17 +4=13 +5=15 +6=16 +7=5 +8=8 +9=8 +10=9 +11=9 +12=5 +13=8 +14=5 +15=9 +16=13 +17=9 +18=12 +19=12 +20=13 +21=13 +22=13 +23=12 +24=13 +25=13 +26=6 +27=6 +28=10 +29=10 +30=10 +31=11 +32=20 +33=14 +34=13 +35=14 +36=15 +37=12 +38=11 +39=15 +40=14 +41=9 +42=11 +43=14 +44=11 +45=18 +46=15 +47=15 +48=12 +49=15 +50=13 +51=13 +52=11 +53=14 +54=13 +55=20 +56=14 +57=11 +58=14 +59=7 +60=9 +61=7 +62=11 +63=14 +64=14 +65=11 +66=12 +67=11 +68=12 +69=11 +70=7 +71=11 +72=12 +73=5 +74=6 +75=11 +76=5 +77=16 +78=11 +79=11 +80=12 +81=12 +82=8 +83=10 +84=7 +85=11 +86=11 +87=15 +88=11 +89=11 +90=11 +91=8 +92=5 +93=9 +94=9 +95=15 +96=6 +97=7 +98=9 +99=15 +100=10 +101=10 +102=13 +103=19 +104=13 +105=10 +106=20 +107=14 +108=6 +109=5 +110=9 +111=9 +112=6 +113=10 +114=14 +115=14 +116=11 +117=11 +118=10 +119=18 +120=11 +121=11 +122=5 +123=5 +124=11 +125=16 +126=14 +127=13 +128=5 +129=14 +130=14 +131=15 +132=8 +133=13 +134=8 +135=7 +136=10 +137=9 +138=7 +139=8 +140=6 +141=6 +142=14 +143=13 +144=13 +145=6 +146=7 +147=4 +148=8 +149=12 +150=17 +151=18 +152=17 +153=11 +154=14 +155=14 +156=14 +157=14 +158=14 +159=14 +160=21 +161=14 +162=12 +163=12 +164=12 +165=12 +166=9 +167=9 +168=9 +169=9 +170=14 +171=15 +172=15 +173=15 +174=15 +175=15 +176=15 +177=8 +178=14 +179=14 +180=14 +181=14 +182=14 +183=11 +184=12 +185=14 +186=11 +187=11 +188=11 +189=11 +190=12 +191=11 +192=18 +193=11 +194=11 +195=11 +196=11 +197=11 +198=5 +199=5 +200=6 +201=6 +202=12 +203=11 +204=11 +205=11 +206=11 +207=11 +208=11 +209=8 +210=11 +211=11 +212=11 +213=11 +214=11 +215=11 +216=12 +217=11 + +[alt] +Line 0= Ą˘Ł¤ĽŚ§¨Š +Line 1=ŞŤŹ­ŽŻ°ą˛ł +Line 2=´ľśˇ¸šşťź˝ +Line 3=žżŔÁÂĂÄĹĆÇ +Line 4=ČÉĘËĚÍÎĎĐŃ +Line 5=ŇÓÔŐÖ×ŘŮÚŰ +Line 6=ÜÝŢßŕáâăäĺ +Line 7=ćçčéęëěíîď +Line 8=đńňóôőö÷řů +Line 9=úűüýţ˙ + +0=5 +1=14 +2=14 +3=11 +4=14 +5=11 +6=13 +7=14 +8=14 +9=13 +10=13 +11=11 +12=14 +13=7 +14=14 +15=14 +16=7 +17=11 +18=10 +19=6 +20=14 +21=7 +22=10 +23=10 +24=7 +25=11 +26=10 +27=7 +28=11 +29=10 +30=11 +31=11 +32=13 +33=14 +34=14 +35=14 +36=14 +37=11 +38=14 +39=14 +40=14 +41=12 +42=12 +43=12 +44=12 +45=9 +46=9 +47=15 +48=14 +49=15 +50=14 +51=15 +52=15 +53=14 +54=15 +55=8 +56=14 +57=14 +58=14 +59=14 +60=14 +61=11 +62=11 +63=14 +64=8 +65=11 +66=11 +67=11 +68=12 +69=5 +70=11 +71=11 +72=11 +73=11 +74=11 +75=11 +76=11 +77=5 +78=6 +79=14 +80=12 +81=11 +82=12 +83=11 +84=11 +85=11 +86=11 +87=8 +88=9 +89=11 +90=11 +91=12 +92=11 +93=11 +94=7 +95=14 + +[numbers] +Baseline=23 +Top=1 +LineSpacing=25 +DrawExtraPixelsLeft=0 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 +Line 0=0123 +Line 1=4567 +Line 2=89% +Line 3= + +0=13 +1=13 +2=13 +3=13 +4=13 +5=13 +6=13 +7=13 +8=13 +9=13 +10=15 +11=7 +12=12 +13=7 +14=10 +15=7 + +[symbol2] +Baseline=21 +Top=12 +LineSpacing=32 +DrawExtraPixelsLeft=2 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 + +Line 0=,.:´`¨‾_仝—‐\‖|‘’ +Line 1=“[]−±×÷≠≦≧∞∴♂♀°′ +Line 2=″℃¢£§☆★○●◎◇◆□■△▲ +Line 3=▽▼※→←↑↓∈∋⊆⊇⊂⊃∪∩∧ +Line 4=∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√ +Line 5=∽∝∵∫∬ʼn♯♭♪†‡¶◯01 +Line 6=23456789ΑΒΓΔΕΖΗΘ +Line 7=ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ +Line 8=αβγδεζηθικλμνξοπ +Line 9=ρστυχψωАБВГДЕЁЖЗ +Line 10=ИЙКЛМНОПРСТУФХЦЧ +Line 11=ШЩЪЫЬЭЮЯабвгдеёж +Line 12=зийклмнопрстуфхц +Line 13=чшщъыьэюя─│┌┐┘└├ +Line 14=┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠ +Line 15=┯┨┷┿┝┰┥┸╂ + +0=14 +1=14 +2=14 +3=14 +4=14 +5=14 +6=14 +7=14 +8=14 +9=12 +10=14 +11=14 +12=14 +13=14 +14=14 +15=14 +16=14 +17=14 +18=14 +19=14 +20=14 +21=14 +22=14 +23=14 +24=14 +25=14 +26=14 +27=14 +28=14 +29=14 +30=14 +31=14 +32=14 +33=14 +34=12 +35=12 +36=14 +37=14 +38=14 +39=14 +40=14 +41=14 +42=14 +43=14 +44=14 +45=14 +46=14 +47=14 +48=14 +49=14 +50=14 +51=14 +52=14 +53=14 +54=14 +55=14 +56=14 +57=14 +58=14 +59=14 +60=14 +61=14 +62=14 +63=14 +64=14 +65=12 +66=14 +67=14 +68=14 +69=14 +70=14 +71=14 +72=14 +73=14 +74=14 +75=14 +76=14 +77=14 +78=14 +79=14 +80=14 +81=14 +82=14 +83=14 +84=14 +85=14 +86=14 +87=14 +88=14 +89=14 +90=14 +91=14 +92=14 +93=14 +94=14 +95=14 +96=14 +97=14 +98=14 +99=14 +100=14 +101=14 +102=14 +103=14 +104=14 +105=14 +106=14 +107=14 +108=14 +109=14 +110=14 +111=14 +112=14 +113=14 +114=14 +115=14 +116=14 +117=14 +118=14 +119=14 +120=14 +121=12 +122=14 +123=14 +124=14 +125=14 +126=14 +127=14 +128=14 +129=14 +130=14 +131=14 +132=14 +133=14 +134=14 +135=14 +136=14 +137=14 +138=14 +139=14 +140=14 +141=14 +142=14 +143=14 +144=14 +145=14 +146=14 +147=14 +148=14 +149=14 +150=14 +151=14 +152=14 +153=14 +154=14 +155=14 +156=14 +157=14 +158=14 +159=14 +160=14 +161=14 +162=14 +163=14 +164=14 +165=14 +166=14 +167=14 +168=14 +169=14 +170=14 +171=14 +172=14 +173=14 +174=14 +175=14 +176=14 +177=14 +178=14 +179=14 +180=14 +181=14 +182=14 +183=14 +184=14 +185=14 +186=14 +187=14 +188=14 +189=14 +190=14 +191=14 +192=14 +193=14 +194=14 +195=14 +196=14 +197=14 +198=14 +199=14 +200=14 +201=14 +202=14 +203=14 +204=14 +205=14 +206=14 +207=14 +208=14 +209=14 +210=14 +211=14 +212=14 +213=14 +214=14 +215=14 +216=14 +217=14 +218=14 +219=14 +220=14 +221=14 +222=14 +223=14 +224=14 +225=14 +226=14 +227=14 +228=14 +229=14 +230=14 +231=14 +232=14 +233=14 +234=14 +235=14 +236=14 +237=14 +238=14 +239=14 +240=14 +241=14 +242=14 +243=14 +244=14 +245=14 +246=14 +247=14 +248=14 + +[symbol3] +Line 0=①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯ +Line 1=⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔ +Line 2=㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝ +Line 3=㎞㎎㎏㏄㎡㍻№㏍℡㊤㊥㊦㊧㊨㈱㈲ +Line 4=㈹㍾㍽㍼∮∟⊿ⅰⅱⅲⅳⅴⅵⅶⅷⅸ +Line 5=ⅹ¦'" + +0=24 +1=24 +2=24 +3=24 +4=24 +5=24 +6=24 +7=24 +8=24 +9=24 +10=24 +11=24 +12=24 +13=24 +14=24 +15=24 +16=24 +17=24 +18=24 +19=24 +20=24 +21=24 +22=24 +23=24 +24=24 +25=24 +26=24 +27=24 +28=24 +29=24 +30=24 +31=24 +32=24 +33=24 +34=24 +35=24 +36=24 +37=24 +38=24 +39=24 +40=24 +41=24 +42=24 +43=24 +44=24 +45=24 +46=24 +47=24 +48=24 +49=24 +50=24 +51=24 +52=24 +53=24 +54=24 +55=24 +56=24 +57=24 +58=24 +59=24 +60=24 +61=24 +62=24 +63=24 +64=24 +65=24 +66=24 +67=24 +68=24 +69=24 +70=24 +71=24 +72=24 +73=24 +74=24 +75=24 +76=24 +77=24 +78=24 +79=24 +80=24 +81=24 +82=24 +83=24 diff --git a/Themes/Rebirth/Fonts/_theFont 48px [alt-stroke] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [alt-stroke] 10x10 (doubleres).png new file mode 100644 index 0000000000..335001a5dd Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [alt-stroke] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [alt] 10x10 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [alt] 10x10 (doubleres).png new file mode 100644 index 0000000000..d15bf746e9 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [alt] 10x10 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [main-stroke] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [main-stroke] 15x15 (doubleres).png new file mode 100644 index 0000000000..1a0feadd1f Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [main-stroke] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [main] 15x15 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [main] 15x15 (doubleres).png new file mode 100644 index 0000000000..849b4ce9d0 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [main] 15x15 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [numbers-stroke] 4x4 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [numbers-stroke] 4x4 (doubleres).png new file mode 100644 index 0000000000..9fcc21bf60 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [numbers-stroke] 4x4 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [numbers] 4x4 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [numbers] 4x4 (doubleres).png new file mode 100644 index 0000000000..2009a624ad Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [numbers] 4x4 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [symbol2-stroke] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [symbol2-stroke] 16x16 (doubleres).png new file mode 100644 index 0000000000..b36f0e605a Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [symbol2-stroke] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [symbol2] 16x16 (doubleres).png b/Themes/Rebirth/Fonts/_theFont 48px [symbol2] 16x16 (doubleres).png new file mode 100644 index 0000000000..2af3ff6bb6 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [symbol2] 16x16 (doubleres).png differ diff --git a/Themes/Rebirth/Fonts/_theFont 48px [symbol3-stroke] 16x6.png b/Themes/Rebirth/Fonts/_theFont 48px [symbol3-stroke] 16x6.png new file mode 100644 index 0000000000..7209d19a85 Binary files /dev/null and b/Themes/Rebirth/Fonts/_theFont 48px [symbol3-stroke] 16x6.png differ diff --git a/Themes/Til Death/Fonts/_bokutachinogothic2bold 48px [symbol3] 16x6.png b/Themes/Rebirth/Fonts/_theFont 48px [symbol3] 16x6.png similarity index 100% rename from Themes/Til Death/Fonts/_bokutachinogothic2bold 48px [symbol3] 16x6.png rename to Themes/Rebirth/Fonts/_theFont 48px [symbol3] 16x6.png diff --git a/Themes/Rebirth/Fonts/_theFont 48px.ini b/Themes/Rebirth/Fonts/_theFont 48px.ini new file mode 100644 index 0000000000..f1641591d5 --- /dev/null +++ b/Themes/Rebirth/Fonts/_theFont 48px.ini @@ -0,0 +1,748 @@ +[common] +Baseline=52 +Top=25 +LineSpacing=65 +DrawExtraPixelsLeft=3 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 + +[main] +Line 0= !"#$%&'()*+,-. +Line 1=/0123456789:;<= +Line 2=>?@ABCDEFGHIJKL +Line 3=MNOPQRSTUVWXYZ[ +Line 4=\]^_`abcdefghij +Line 5=klmnopqrstuvwxy +Line 6=z{|}~€‚ƒ„…†‡ˆ‰Š +Line 7=‹ŒŽ‘’“”•–—˜™š›œ +Line 8=žŸ ¡¢£¤¥¦§¨©ª«¬ +Line 9=­®¯°±²³´µ¶·¸¹º» +Line 10=¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊ +Line 11=ËÌÍÎÏÐÑÒÓÔÕÖ×ØÙ +Line 12=ÚÛÜÝÞßàáâãäåæçè +Line 13=éêëìíîïðñòóôõö÷ +Line 14=øùúûüýþÿ + +0=10 +1=11 +2=17 +3=33 +4=27 +5=29 +6=31 +7=10 +8=16 +9=15 +10=18 +11=17 +12=11 +13=15 +14=10 +15=19 +16=26 +17=18 +18=25 +19=25 +20=25 +21=25 +22=26 +23=25 +24=27 +25=26 +26=11 +27=11 +28=20 +29=20 +30=20 +31=22 +32=39 +33=27 +34=27 +35=29 +36=29 +37=24 +38=23 +39=30 +40=28 +41=18 +42=23 +43=28 +44=23 +45=35 +46=29 +47=29 +48=24 +49=30 +50=27 +51=27 +52=22 +53=29 +54=27 +55=39 +56=27 +57=21 +58=27 +59=14 +60=18 +61=14 +62=21 +63=27 +64=29 +65=22 +66=24 +67=23 +68=24 +69=22 +70=14 +71=23 +72=23 +73=10 +74=11 +75=22 +76=9 +77=32 +78=22 +79=23 +80=24 +81=24 +82=16 +83=20 +84=15 +85=22 +86=21 +87=31 +88=21 +89=21 +90=21 +91=17 +92=11 +93=17 +94=17 +95=31 +96=11 +97=7 +98=18 +99=30 +100=10 +101=10 +102=25 +103=39 +104=26 +105=20 +106=41 +107=27 +108=12 +109=11 +110=19 +111=19 +112=13 +113=20 +114=28 +115=29 +116=22 +117=22 +118=20 +119=37 +120=21 +121=21 +122=10 +123=10 +124=22 +125=32 +126=27 +127=25 +128=11 +129=29 +130=29 +131=30 +132=15 +133=27 +134=15 +135=13 +136=20 +137=17 +138=14 +139=15 +140=12 +141=12 +142=29 +143=26 +144=25 +145=12 +146=14 +147=9 +148=15 +149=24 +150=34 +151=36 +152=34 +153=21 +154=27 +155=27 +156=27 +157=27 +158=27 +159=27 +160=41 +161=29 +162=24 +163=24 +164=24 +165=24 +166=18 +167=18 +168=18 +169=18 +170=29 +171=29 +172=29 +173=29 +174=29 +175=29 +176=29 +177=15 +178=29 +179=29 +180=29 +181=29 +182=29 +183=21 +184=24 +185=28 +186=22 +187=22 +188=22 +189=22 +190=25 +191=22 +192=35 +193=23 +194=22 +195=22 +196=22 +197=22 +198=10 +199=10 +200=12 +201=12 +202=25 +203=22 +204=23 +205=23 +206=23 +207=23 +208=23 +209=15 +210=22 +211=22 +212=22 +213=22 +214=22 +215=21 +216=24 +217=21 + +[alt] +Line 0= Ą˘Ł¤ĽŚ§¨Š +Line 1=ŞŤŹ­ŽŻ°ą˛ł +Line 2=´ľśˇ¸šşťź˝ +Line 3=žżŔÁÂĂÄĹĆÇ +Line 4=ČÉĘËĚÍÎĎĐŃ +Line 5=ŇÓÔŐÖ×ŘŮÚŰ +Line 6=ÜÝŢßŕáâăäĺ +Line 7=ćçčéęëěíîď +Line 8=đńňóôőö÷řů +Line 9=úűüýţ˙ + +0=10 +1=28 +2=29 +3=23 +4=27 +5=22 +6=27 +7=29 +8=29 +9=26 +10=27 +11=22 +12=27 +13=13 +14=27 +15=27 +16=14 +17=22 +18=10 +19=12 +20=29 +21=13 +22=20 +23=21 +24=14 +25=22 +26=20 +27=15 +28=21 +29=10 +30=21 +31=21 +32=27 +33=27 +34=27 +35=27 +36=27 +37=23 +38=29 +39=29 +40=29 +41=24 +42=24 +43=24 +44=24 +45=18 +46=18 +47=29 +48=29 +49=29 +50=29 +51=29 +52=29 +53=29 +54=29 +55=15 +56=27 +57=29 +58=29 +59=28 +60=29 +61=21 +62=22 +63=28 +64=16 +65=22 +66=22 +67=22 +68=25 +69=9 +70=23 +71=23 +72=23 +73=22 +74=22 +75=22 +76=22 +77=10 +78=12 +79=29 +80=24 +81=22 +82=23 +83=23 +84=23 +85=23 +86=23 +87=15 +88=18 +89=22 +90=22 +91=23 +92=22 +93=21 +94=15 +95=29 + +[numbers] +Baseline=43 +Top=1 +LineSpacing=49 +DrawExtraPixelsLeft=0 +DrawExtraPixelsRight=0 +AdvanceExtraPixels=0 +Line 0=0123 +Line 1=4567 +Line 2=89% +Line 3= + +0=26 +1=26 +2=26 +3=26 +4=26 +5=26 +6=26 +7=26 +8=26 +9=26 +10=29 +11=12 +12=22 +13=12 +14=19 +15=12 + +[symbol2] +AddToAllWidths=0 +DrawExtraPixelsLeft=0 +Baseline=46 +Top=25 +LineSpacing=65 + +Line 0=,.:´`¨‾_仝—‐\‖|‘’ +Line 1=“[]−±×÷≠≦≧∞∴♂♀°′ +Line 2=″℃¢£§☆★○●◎◇◆□■△▲ +Line 3=▽▼※→←↑↓∈∋⊆⊇⊂⊃∪∩∧ +Line 4=∨¬⇒⇔∀∃∠⊥⌒∂∇≡≒≪≫√ +Line 5=∽∝∵∫∬ʼn♯♭♪†‡¶◯01 +Line 6=23456789ΑΒΓΔΕΖΗΘ +Line 7=ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ +Line 8=αβγδεζηθικλμνξοπ +Line 9=ρστυχψωАБВГДЕЁЖЗ +Line 10=ИЙКЛМНОПРСТУФХЦЧ +Line 11=ШЩЪЫЬЭЮЯабвгдеёж +Line 12=зийклмнопрстуфхц +Line 13=чшщъыьэюя─│┌┐┘└├ +Line 14=┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠ +Line 15=┯┨┷┿┝┰┥┸╂ + +0=30 +1=30 +2=30 +3=30 +4=30 +5=30 +6=30 +7=30 +8=30 +9=24 +10=30 +11=30 +12=30 +13=30 +14=30 +15=30 +16=30 +17=30 +18=30 +19=30 +20=30 +21=30 +22=30 +23=30 +24=30 +25=30 +26=30 +27=30 +28=30 +29=30 +30=30 +31=30 +32=30 +33=30 +34=24 +35=24 +36=30 +37=30 +38=30 +39=30 +40=30 +41=30 +42=30 +43=30 +44=30 +45=30 +46=30 +47=30 +48=30 +49=30 +50=30 +51=30 +52=30 +53=30 +54=30 +55=30 +56=30 +57=30 +58=30 +59=30 +60=30 +61=30 +62=30 +63=30 +64=30 +65=24 +66=30 +67=30 +68=30 +69=30 +70=30 +71=30 +72=30 +73=30 +74=30 +75=30 +76=30 +77=30 +78=30 +79=30 +80=30 +81=30 +82=30 +83=30 +84=30 +85=30 +86=30 +87=30 +88=30 +89=30 +90=30 +91=30 +92=30 +93=30 +94=30 +95=30 +96=30 +97=30 +98=30 +99=30 +100=30 +101=30 +102=30 +103=30 +104=30 +105=30 +106=30 +107=30 +108=30 +109=30 +110=30 +111=30 +112=30 +113=30 +114=30 +115=30 +116=30 +117=30 +118=30 +119=30 +120=30 +121=24 +122=30 +123=30 +124=30 +125=30 +126=30 +127=30 +128=30 +129=30 +130=30 +131=30 +132=30 +133=30 +134=30 +135=30 +136=30 +137=30 +138=30 +139=30 +140=30 +141=30 +142=30 +143=30 +144=30 +145=30 +146=30 +147=30 +148=30 +149=30 +150=30 +151=30 +152=30 +153=30 +154=30 +155=30 +156=30 +157=30 +158=30 +159=30 +160=30 +161=30 +162=30 +163=30 +164=30 +165=30 +166=30 +167=30 +168=30 +169=30 +170=30 +171=30 +172=30 +173=30 +174=30 +175=30 +176=30 +177=30 +178=30 +179=30 +180=30 +181=30 +182=30 +183=30 +184=30 +185=30 +186=30 +187=30 +188=30 +189=30 +190=30 +191=30 +192=30 +193=30 +194=30 +195=30 +196=30 +197=30 +198=30 +199=30 +200=30 +201=30 +202=30 +203=30 +204=30 +205=30 +206=30 +207=30 +208=30 +209=30 +210=30 +211=30 +212=30 +213=30 +214=30 +215=30 +216=30 +217=30 +218=30 +219=30 +220=30 +221=30 +222=30 +223=30 +224=30 +225=30 +226=30 +227=30 +228=30 +229=30 +230=30 +231=30 +232=30 +233=30 +234=30 +235=30 +236=30 +237=30 +238=30 +239=30 +240=30 +241=30 +242=30 +243=30 +244=30 +245=30 +246=30 +247=30 +248=30 + +[symbol3] +Line 0=①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯ +Line 1=⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔ +Line 2=㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝ +Line 3=㎞㎎㎏㏄㎡㍻№㏍℡㊤㊥㊦㊧㊨㈱㈲ +Line 4=㈹㍾㍽㍼∮∟⊿ⅰⅱⅲⅳⅴⅵⅶⅷⅸ +Line 5=ⅹ¦'" + +0=48 +1=48 +2=48 +3=48 +4=48 +5=48 +6=48 +7=48 +8=48 +9=48 +10=48 +11=48 +12=48 +13=48 +14=48 +15=48 +16=48 +17=48 +18=48 +19=48 +20=48 +21=48 +22=48 +23=48 +24=48 +25=48 +26=48 +27=48 +28=48 +29=48 +30=48 +31=48 +32=48 +33=48 +34=48 +35=48 +36=48 +37=48 +38=48 +39=48 +40=48 +41=48 +42=48 +43=48 +44=48 +45=48 +46=48 +47=48 +48=48 +49=48 +50=48 +51=48 +52=48 +53=48 +54=48 +55=48 +56=48 +57=48 +58=48 +59=48 +60=48 +61=48 +62=48 +63=48 +64=48 +65=48 +66=48 +67=48 +68=48 +69=48 +70=48 +71=48 +72=48 +73=48 +74=48 +75=48 +76=48 +77=48 +78=48 +79=48 +80=48 +81=48 +82=48 +83=48 diff --git a/Themes/Rebirth/Graphics/ComboGraph Backing.lua b/Themes/Rebirth/Graphics/ComboGraph Backing.lua new file mode 100644 index 0000000000..1e544ade0f --- /dev/null +++ b/Themes/Rebirth/Graphics/ComboGraph Backing.lua @@ -0,0 +1,9 @@ +-- represents the background for the entire combo graph + +return Def.Quad { + Name = "Backing", + InitCommand = function(self) + self:diffusealpha(0.9) + registerActorToColorConfigElement(self, "evaluation", "ComboGraphBackground") + end +} diff --git a/Themes/Rebirth/Graphics/ComboGraph ComboNumber.lua b/Themes/Rebirth/Graphics/ComboGraph ComboNumber.lua new file mode 100644 index 0000000000..173998ed39 --- /dev/null +++ b/Themes/Rebirth/Graphics/ComboGraph ComboNumber.lua @@ -0,0 +1,12 @@ +-- represents the numbers on the combo graph if they are present +-- usually is placed on the largest combo + +return LoadFont("Common normal") .. { + Name = "Numbers", + InitCommand = function(self) + self:zoom(0.55) + self:strokecolor(color("#00000077")) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "main", "PrimaryText") + end +} diff --git a/Themes/Rebirth/Graphics/ComboGraph MaxCombo.lua b/Themes/Rebirth/Graphics/ComboGraph MaxCombo.lua new file mode 100644 index 0000000000..8f77966b5c --- /dev/null +++ b/Themes/Rebirth/Graphics/ComboGraph MaxCombo.lua @@ -0,0 +1,9 @@ +-- represents the quad drawn for the largest combo on the combo graph + +return Def.Quad { + Name = "MaxCombo", + InitCommand = function(self) + self:diffusealpha(0.8) + registerActorToColorConfigElement(self, "evaluation", "MaxComboText") + end +} diff --git a/Themes/Rebirth/Graphics/ComboGraph NormalCombo.lua b/Themes/Rebirth/Graphics/ComboGraph NormalCombo.lua new file mode 100644 index 0000000000..170e46903a --- /dev/null +++ b/Themes/Rebirth/Graphics/ComboGraph NormalCombo.lua @@ -0,0 +1,8 @@ +-- represents the quad drawn for all combos that are not the max combo on the combo graph + +return Def.Quad { + InitCommand = function(self) + self:diffusealpha(0.7) + registerActorToColorConfigElement(self, "evaluation", "NormalComboText") + end +} diff --git a/Themes/Rebirth/Graphics/Common fallback background.png b/Themes/Rebirth/Graphics/Common fallback background.png new file mode 100644 index 0000000000..9d923f045c Binary files /dev/null and b/Themes/Rebirth/Graphics/Common fallback background.png differ diff --git a/Themes/Rebirth/Graphics/Common fallback banner.png b/Themes/Rebirth/Graphics/Common fallback banner.png new file mode 100644 index 0000000000..ac98414e2f Binary files /dev/null and b/Themes/Rebirth/Graphics/Common fallback banner.png differ diff --git a/Themes/Rebirth/Graphics/Discord.png b/Themes/Rebirth/Graphics/Discord.png new file mode 100644 index 0000000000..a48b023821 Binary files /dev/null and b/Themes/Rebirth/Graphics/Discord.png differ diff --git a/Themes/Rebirth/Graphics/EO.png b/Themes/Rebirth/Graphics/EO.png new file mode 100644 index 0000000000..7b59d53df5 Binary files /dev/null and b/Themes/Rebirth/Graphics/EO.png differ diff --git a/Themes/Rebirth/Graphics/Github.png b/Themes/Rebirth/Graphics/Github.png new file mode 100644 index 0000000000..ec4ca8342c Binary files /dev/null and b/Themes/Rebirth/Graphics/Github.png differ diff --git a/Themes/Rebirth/Graphics/GraphDisplay Barely.redir b/Themes/Rebirth/Graphics/GraphDisplay Barely.redir new file mode 100644 index 0000000000..af69262596 --- /dev/null +++ b/Themes/Rebirth/Graphics/GraphDisplay Barely.redir @@ -0,0 +1 @@ +_nothing \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/GraphDisplay SongBoundary.redir b/Themes/Rebirth/Graphics/GraphDisplay SongBoundary.redir new file mode 100644 index 0000000000..af69262596 --- /dev/null +++ b/Themes/Rebirth/Graphics/GraphDisplay SongBoundary.redir @@ -0,0 +1 @@ +_nothing \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/GraphDisplay backing.lua b/Themes/Rebirth/Graphics/GraphDisplay backing.lua new file mode 100644 index 0000000000..1d6fdb0f34 --- /dev/null +++ b/Themes/Rebirth/Graphics/GraphDisplay backing.lua @@ -0,0 +1,9 @@ +-- represents the background for the entire life graph + +return Def.Quad { + Name = "Backing", + InitCommand = function(self) + self:diffusealpha(1) + registerActorToColorConfigElement(self, "evaluation", "LifeGraphBackground") + end +} diff --git a/Themes/Rebirth/Graphics/GraphDisplay body.png b/Themes/Rebirth/Graphics/GraphDisplay body.png new file mode 100644 index 0000000000..c3f9de7037 Binary files /dev/null and b/Themes/Rebirth/Graphics/GraphDisplay body.png differ diff --git a/Themes/Rebirth/Graphics/License.txt b/Themes/Rebirth/Graphics/License.txt new file mode 100644 index 0000000000..240fc0f54a --- /dev/null +++ b/Themes/Rebirth/Graphics/License.txt @@ -0,0 +1 @@ +Some icons are sourced from: https://fontawesome.com/license/free \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/Logo.png b/Themes/Rebirth/Graphics/Logo.png new file mode 100644 index 0000000000..44de69bf12 Binary files /dev/null and b/Themes/Rebirth/Graphics/Logo.png differ diff --git a/Themes/Rebirth/Graphics/Marker.png b/Themes/Rebirth/Graphics/Marker.png new file mode 100644 index 0000000000..1dc321718e Binary files /dev/null and b/Themes/Rebirth/Graphics/Marker.png differ diff --git a/Themes/Rebirth/Graphics/MusicWheel highlight.lua b/Themes/Rebirth/Graphics/MusicWheel highlight.lua new file mode 100644 index 0000000000..082f70d316 --- /dev/null +++ b/Themes/Rebirth/Graphics/MusicWheel highlight.lua @@ -0,0 +1 @@ +return Def.Actor{} \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/NoteField cover.lua b/Themes/Rebirth/Graphics/NoteField cover.lua new file mode 100644 index 0000000000..27d3bc12c8 --- /dev/null +++ b/Themes/Rebirth/Graphics/NoteField cover.lua @@ -0,0 +1,246 @@ +-- the cover for the notefield +-- responsible for basically being the bms/iidx lane cover +-- and whatever else you want + +-- dont load outside of gameplay +if Var("LoadingScreen") ~= nil and Var("LoadingScreen"):find("Gameplay") == nil then + return Def.Actor {} +end + +local laneColor = COLORS:getGameplayColor("LaneCover") +local bpmColor = COLORS:getGameplayColor("LaneCoverBPM") +local heightColor = COLORS:getGameplayColor("LaneCoverHeight") + +local cols = GAMESTATE:GetCurrentStyle():ColumnsPerPlayer() +local evencols = cols - cols%2 + +-- load from prefs later +local nfspace = MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0 +local width = 64 * cols * MovableValues.NoteFieldWidth + nfspace * (evencols) +local padding = 20 + +local prefsP1 = playerConfig:get_data().LaneCover +local isReverseP1 = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() +if prefsP1 == 2 then -- it's a Hidden LaneCover + isReverseP1 = not isReverseP1 +end + +local heightP1 = MovableValues.CoverHeight + +if prefsP1 == 0 then + return Def.Actor {Name = "Cover"} +end + + +local function getPlayerBPM() + local songPosition = GAMESTATE:GetPlayerState():GetSongPosition() + local ts = SCREENMAN:GetTopScreen() + local bpm = 0 + if ts:GetScreenType() == "ScreenType_Gameplay" then + bpm = ts:GetTrueBPS() * 60 + end + return bpm +end + +local function getMaxDisplayBPM() + local steps = GAMESTATE:GetCurrentSteps() + if steps:GetDisplayBPMType() ~= "DisplayBPM_Random" then + return steps:GetDisplayBpms()[2] + else + return steps:GetTimingData():GetActualBPM()[2] + end +end + +local function getSpeed() + local po = GAMESTATE:GetPlayerState():GetPlayerOptions("ModsLevel_Preferred") + if po:XMod() ~= nil then + return po:XMod() * getPlayerBPM() + elseif po:CMod() ~= nil then + return po:CMod() + elseif po:MMod() ~= nil then + return po:MMod() * (getPlayerBPM() / getMaxDisplayBPM()) + else + return getPlayerBPM() + end +end + +local yoffsetreverse = THEME:GetMetric("Player", "ReceptorArrowsYReverse") +local yoffsetstandard = THEME:GetMetric("Player", "ReceptorArrowsYStandard") + +local function getNoteFieldHeight() + local usingreverse = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() + if usingreverse then + return (SCREEN_CENTER_Y + yoffsetreverse / getNoteFieldScale()) + else + return (SCREEN_CENTER_Y - yoffsetstandard / getNoteFieldScale()) + end +end + +local function getScrollSpeed(LaneCoverHeight) + local questionableNumber = 22 -- 22 is the "edge of screen" position for some reason??? + local height = getNoteFieldHeight() + local speed = getSpeed() + LaneCoverHeight = LaneCoverHeight - questionableNumber + + if LaneCoverHeight < height then + return speed * (height / (height - LaneCoverHeight)) + else + return 0 + end +end + +local t = Def.ActorFrame { + Name = "Cover", + InitCommand = function(self) + registerActorToCustomizeGameplayUI({ + actor = self, + zoomInc = {5,1}, + }, 5) + self:playcommand("SetUpMovableValues") + end, + SetUpMovableValuesMessageCommand = function(self) + local wb4 = width + local hb4 = heightP1 + width = 64 * cols * MovableValues.NoteFieldWidth + MovableValues.NoteFieldSpacing * (evencols) + heightP1 = MovableValues.CoverHeight + + if width ~= wb4 or heightP1 ~= hb4 then + local whitetext = self:GetChild("CoverTextP1White") + local greentext = self:GetChild("CoverTextP1Green") + whitetext:settext(math.floor(heightP1)) + if prefsP1 == 1 then -- don't update greennumber for hidden lanecovers + greentext:settext(math.floor(getScrollSpeed(heightP1))) + end + + if isReverseP1 then + whitetext:y(heightP1 - 5 - getNoteFieldHeight()/2) + greentext:y(heightP1 - 5 - getNoteFieldHeight()/2) + else + whitetext:y(getNoteFieldHeight()/2 - heightP1 + 5) + greentext:y(getNoteFieldHeight()/2 - heightP1 + 5) + end + + whitetext:finishtweening() + whitetext:diffusealpha(1) + whitetext:sleep(0.25) + whitetext:smooth(0.75) + whitetext:diffusealpha(0) + + greentext:finishtweening() + greentext:diffusealpha(1) + greentext:sleep(0.25) + greentext:smooth(0.75) + greentext:diffusealpha(0) + + whitetext:x(-(width / 8)) + greentext:x((width / 8)) + end + + self:playcommand("SetMovableWidths") + end, +} + +t[#t + 1] = Def.Quad { + Name = "CoverP1", + InitCommand = function(self) + if isReverseP1 then + self:y(-getNoteFieldHeight()/2) + self:valign(0) + else + self:y(getNoteFieldHeight()/2) + self:valign(1) + end + self:playcommand("SetMovableWidths") + self:diffuse(laneColor) + self:diffusealpha(1) + end, + SetMovableWidthsCommand = function(self) + self:x(cols % 2 == 0 and -(MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0) / 2 or 0) + self:zoomto((width + padding), heightP1) + end, +} + +-- harming your fps in the name of making things look not bad if you have a bad setup +t[#t+1] = Def.Quad { + Name = "CoverYouNeverSeeUnlessYouMoveTheNotefield", + InitCommand = function(self) + if isReverseP1 then + self:y(-getNoteFieldHeight()/2) + self:valign(1) + else + self:y(getNoteFieldHeight()/2) + self:valign(0) + end + self:playcommand("SetMovableWidths") + self:diffuse(laneColor) + self:diffusealpha(1) + end, + SetMovableWidthsCommand = function(self) + self:x(cols % 2 == 0 and -(MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0) / 2 or 0) + self:zoomto((width + padding), SCREEN_HEIGHT) + if allowedCustomization then + self:zoomy(0) + end + end, +} + +t[#t + 1] = LoadFont("Common Normal") .. { + Name = "CoverTextP1White", + InitCommand = function(self) + self:valign(1) + self:playcommand("SetMovableWidths") + self:zoom(0.5) + self:diffuse(heightColor) + self:diffusealpha(1) + self:settext(0) + end, + BeginCommand = function(self) + self:settext(0) + if isReverseP1 then + self:y(heightP1 - 5 - getNoteFieldHeight()/2) + self:valign(1) + else + self:y(getNoteFieldHeight()/2 - heightP1 + 5) + self:valign(0) + end + self:finishtweening() + self:diffusealpha(1) + self:sleep(0.25) + self:smooth(0.75) + self:diffusealpha(0) + end, + SetMovableWidthsCommand = function(self) + self:x(-(width / 8)) + end, +} +t[#t + 1] = LoadFont("Common Normal") .. { + Name = "CoverTextP1Green", + InitCommand = function(self) + self:valign(1) + self:playcommand("SetMovableWidths") + self:zoom(0.5) + self:diffuse(bpmColor) + self:diffusealpha(1) + self:settext(0) + end, + SetMovableWidthsCommand = function(self) + self:x((width / 8)) + end, + BeginCommand = function(self) + self:settext(math.floor(getSpeed(PLAYER_1))) + if isReverseP1 then + self:y(heightP1 - 5) + self:valign(1) + else + self:y(SCREEN_BOTTOM - heightP1 + 5) + self:valign(0) + end + self:finishtweening() + self:diffusealpha(1) + self:sleep(0.25) + self:smooth(0.75) + self:diffusealpha(0) + end +} + +return t diff --git a/Themes/Rebirth/Graphics/Notefield board.lua b/Themes/Rebirth/Graphics/Notefield board.lua new file mode 100644 index 0000000000..3bd6457d38 --- /dev/null +++ b/Themes/Rebirth/Graphics/Notefield board.lua @@ -0,0 +1,125 @@ +-- board for the notefield +-- this is an underlay to the notefield +-- usually used for measure counters and stuff for edit mode +-- but this lua defines a black background and the cbhighlight stuff + +local padding = 20 -- 10px on each side +local arrowWidth = 64 -- until noteskin metrics are implemented... + +--moving notefield shenanigans +local noteFieldWidth = MovableValues.NoteFieldWidth +local oldWidth = noteFieldWidth +local oldspacing = MovableValues.NoteFieldSpacing +local filter +local cbContainer + +-- dont load outside of gameplay +if Var("LoadingScreen") ~= nil and Var("LoadingScreen"):find("Gameplay") == nil then + return Def.Actor {} +end + +local t = Def.ActorFrame {Name = "NoteFieldBoardFile"} + +local style = GAMESTATE:GetCurrentStyle() +local cols = style:ColumnsPerPlayer() +local hCols = math.floor(cols / 2) +local evenCols = cols - cols%2 +local filterWidth = (arrowWidth * cols) + padding + +local function laneHighlight() + local enabled = playerConfig:get_data().CBHighlight + local judgeThreshold = Enum.Reverse(TapNoteScore)[ComboContinue()] + local alpha = 0.4 + local r = Def.ActorFrame { + InitCommand = function(self) + cbContainer = self + end + } + local width = style:GetWidth() + local colWidth = width/cols + local border = 4 + + if not enabled then + return r + end + + for i = 1, cols do + r[#r+1] = Def.Quad { + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + + self:fadebottom(0.6):fadetop(0.6) + self:visible(false) + end, + SetUpMovableValuesMessageCommand = function(self) + noteFieldWidth = MovableValues.NoteFieldWidth + self:zoomto((arrowWidth - 4) * noteFieldWidth, SCREEN_HEIGHT * 2) + + local reverse = GAMESTATE:GetPlayerState():GetCurrentPlayerOptions():UsingReverse() + local receptor = reverse and THEME:GetMetric("Player", "ReceptorArrowsYStandard") or THEME:GetMetric("Player", "ReceptorArrowsYReverse") + + self:diffusealpha(alpha) + local thewidth + if noteFieldWidth >= 1 then + thewidth = math.abs(1-noteFieldWidth) + else + thewidth = noteFieldWidth - 1 + end + -- x position is relative to the center of the notefield + -- account for the column width and also the number of columns + -- consider that we are also setting positions without aligns so the coordinate is the center of the column + self:xy((-(arrowWidth * (cols / 2)) + ((i - 1) * arrowWidth) + (arrowWidth / 2)) + (i-(cols/2)-(1/2))*colWidth*(thewidth),-receptor) + -- mimic the behavior of the moving function for spacing to set the last bit of x position + -- this moves all columns except "the middle" by however much the spacing requires + self:addx((i - hCols - 1) * (MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0)) + end, + JudgmentMessageCommand=function(self,params) + local notes = params.Notes + local firstTrack = params.FirstTrack+1 + if params.HoldNoteScore then return end + if params.TapNoteScore then + local enum = Enum.Reverse(TapNoteScore)[params.TapNoteScore] + if enum < judgeThreshold and enum > 3 and i == firstTrack then + self:stoptweening() + self:visible(true) + self:diffuse(colorByJudgment(params.TapNoteScore)) + self:diffusealpha(alpha) + self:tween(0.25, "TweenType_Bezier",{0,0,0.5,0,1,1,1,1}) + self:diffusealpha(0) + end + end + end, + } + end + + return r +end + + +local filterColor = COLORS:getGameplayColor("NoteFieldBG") +local filterAlphas = playerConfig:get_data().ScreenFilter +if filterAlphas == nil then + filterAlphas = 0 +else + filterAlphas = tonumber(filterAlphas) +end + +t[#t+1] = Def.Quad { + Name = "SinglePlayerFilter", + InitCommand = function(self) + self:playcommand("SetUpMovableValues") + self:diffuse(filterColor) + self:diffusealpha(filterAlphas) + filter = self + end, + SetUpMovableValuesMessageCommand = function(self) + self:x(0) + self:zoomto(filterWidth * noteFieldWidth + (MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0) * evenCols, SCREEN_HEIGHT * 2) + -- offset the filter by this much for even column counts + self:addx(cols % 2 == 0 and -(MovableValues.NoteFieldSpacing and MovableValues.NoteFieldSpacing or 0) / 2 or 0) + end, +} + +t[#t+1] = laneHighlight() + +return t diff --git a/Themes/Rebirth/Graphics/Patreon.png b/Themes/Rebirth/Graphics/Patreon.png new file mode 100644 index 0000000000..a94cf4caf4 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patreon.png differ diff --git a/Themes/Rebirth/Graphics/Patterns/1234 roll.jpg b/Themes/Rebirth/Graphics/Patterns/1234 roll.jpg new file mode 100644 index 0000000000..56c74ae667 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/1234 roll.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/1243 roll.jpg b/Themes/Rebirth/Graphics/Patterns/1243 roll.jpg new file mode 100644 index 0000000000..0fe4cf4d86 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/1243 roll.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/13 split jt.jpg b/Themes/Rebirth/Graphics/Patterns/13 split jt.jpg new file mode 100644 index 0000000000..665d3303ca Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/13 split jt.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/14 split jt.jpg b/Themes/Rebirth/Graphics/Patterns/14 split jt.jpg new file mode 100644 index 0000000000..1bd1ebc7ee Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/14 split jt.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/1423 roll.jpg b/Themes/Rebirth/Graphics/Patterns/1423 roll.jpg new file mode 100644 index 0000000000..84b13aef77 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/1423 roll.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/anchor.jpg b/Themes/Rebirth/Graphics/Patterns/anchor.jpg new file mode 100644 index 0000000000..c241b447b2 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/anchor.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/burst.jpg b/Themes/Rebirth/Graphics/Patterns/burst.jpg new file mode 100644 index 0000000000..f21101ecc4 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/burst.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/chordjacks.jpg b/Themes/Rebirth/Graphics/Patterns/chordjacks.jpg new file mode 100644 index 0000000000..e713dfda4c Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/chordjacks.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/dense chordjack.jpg b/Themes/Rebirth/Graphics/Patterns/dense chordjack.jpg new file mode 100644 index 0000000000..cd6d487c9c Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/dense chordjack.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/gluts.jpg b/Themes/Rebirth/Graphics/Patterns/gluts.jpg new file mode 100644 index 0000000000..e8800db997 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/gluts.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/graces.jpg b/Themes/Rebirth/Graphics/Patterns/graces.jpg new file mode 100644 index 0000000000..dc3444284e Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/graces.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/handstream.jpg b/Themes/Rebirth/Graphics/Patterns/handstream.jpg new file mode 100644 index 0000000000..c6a2c9444d Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/handstream.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/hold.jpg b/Themes/Rebirth/Graphics/Patterns/hold.jpg new file mode 100644 index 0000000000..05bbacc3ff Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/hold.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/jumpstream.jpg b/Themes/Rebirth/Graphics/Patterns/jumpstream.jpg new file mode 100644 index 0000000000..e0c6f3d7be Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/jumpstream.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/jumptrill.jpg b/Themes/Rebirth/Graphics/Patterns/jumptrill.jpg new file mode 100644 index 0000000000..6d7a46bff8 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/jumptrill.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/longjack.jpg b/Themes/Rebirth/Graphics/Patterns/longjack.jpg new file mode 100644 index 0000000000..d974887a93 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/longjack.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/minedodge.jpg b/Themes/Rebirth/Graphics/Patterns/minedodge.jpg new file mode 100644 index 0000000000..53fc382c8b Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/minedodge.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/minijacks.jpg b/Themes/Rebirth/Graphics/Patterns/minijacks.jpg new file mode 100644 index 0000000000..72eff27405 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/minijacks.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/polyrhythms.jpg b/Themes/Rebirth/Graphics/Patterns/polyrhythms.jpg new file mode 100644 index 0000000000..8165932664 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/polyrhythms.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/quadstream.jpg b/Themes/Rebirth/Graphics/Patterns/quadstream.jpg new file mode 100644 index 0000000000..2435439d1a Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/quadstream.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/rolld.jpg b/Themes/Rebirth/Graphics/Patterns/rolld.jpg new file mode 100644 index 0000000000..41a8a10dc0 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/rolld.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/runningman.jpg b/Themes/Rebirth/Graphics/Patterns/runningman.jpg new file mode 100644 index 0000000000..d8ca88f4ac Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/runningman.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/streams.jpg b/Themes/Rebirth/Graphics/Patterns/streams.jpg new file mode 100644 index 0000000000..fa9a2ced16 Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/streams.jpg differ diff --git a/Themes/Rebirth/Graphics/Patterns/trill.jpg b/Themes/Rebirth/Graphics/Patterns/trill.jpg new file mode 100644 index 0000000000..9045fcc3ba Binary files /dev/null and b/Themes/Rebirth/Graphics/Patterns/trill.jpg differ diff --git a/Themes/Rebirth/Graphics/Player combo/default.lua b/Themes/Rebirth/Graphics/Player combo/default.lua new file mode 100644 index 0000000000..d5709ac958 --- /dev/null +++ b/Themes/Rebirth/Graphics/Player combo/default.lua @@ -0,0 +1,129 @@ +local c +local enabledCombo = playerConfig:get_data().ComboText +local enabledLabel = playerConfig:get_data().ComboLabel +local enableGlow = playerConfig:get_data().ComboGlow + +local function arbitraryComboZoom(value) + c.Label:zoom(value) + c.Number:zoom(value - 0.1) +end + +local ShowComboAt = THEME:GetMetric("Combo", "ShowComboAt") +local labelColor = getComboColor("ComboLabel") +local mfcNumbers = getComboColor("MarvFullCombo") +local pfcNumbers = getComboColor("PerfFullCombo") +local fcNumbers = getComboColor("FullCombo") +local regNumbers = getComboColor("RegularCombo") + +local translated_combo = "Combo"--THEME:GetString("ScreenGameplay", "ComboText") + +local t = Def.ActorFrame { + Name = "Combo", + InitCommand = function(self) + c = self:GetChildren() + -- queued to execute slightly late + self:queuecommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + OnCommand = function(self) + if (allowedCustomization) then + c.Number:visible(true) + c.Number:settext(1000) + c.Label:visible(enabledLabel) + c.Label:settext(translated_combo) + end + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.ComboX, MovableValues.ComboY) + arbitraryComboZoom(MovableValues.ComboZoom * 1.25) + end, + ComboCommand = function(self, param) + local iCombo = param.Combo + if not iCombo or iCombo < ShowComboAt then + c.Number:visible(false) + c.Label:visible(false) + return + end + + c.Number:visible(true) + c.Number:settext(iCombo) + c.Label:visible(enabledLabel) + c.Label:settext(translated_combo) + + c.BG:x(-c.Number:GetZoomedWidth() - (enabledLabel and 24 or 4)) + c.BG:zoomto(c.Number:GetZoomedWidth() + c.Label:GetZoomedWidth() + (enabledLabel and 24 or 4), c.Label:GetZoomedHeight()) + + -- FullCombo Rewards + if param.FullComboW1 then + c.Number:diffuse(mfcNumbers) + if enableGlow then + c.Number:glowshift() + end + elseif param.FullComboW2 then + c.Number:diffuse(pfcNumbers) + if enableGlow then + c.Number:glowshift() + end + elseif param.FullComboW3 then + c.Number:diffuse(fcNumbers) + if enableGlow then + c.Number:stopeffect() + end + elseif param.Combo then + c.Number:diffuse(regNumbers) + if enableGlow then + c.Number:stopeffect() + end + c.Label:diffuse(labelColor) + c.Label:diffusebottomedge(color("0.75,0.75,0.75,1")) + else + -- probably for if you want to fade out the combo after a miss + c.Number:diffuse(color("#ff0000")) + c.Number:stopeffect() + c.Label:diffuse(Color("Red")) + c.Label:diffusebottomedge(color("0.5,0,0,1")) + end + end, + + Def.Quad { -- not normally visible but acts as a way for customize gameplay to hook into the combo size + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(1) + self:visible(false) + end, + }, + LoadFont("Combo", "numbers") .. { + Name = "Number", + InitCommand = function(self) + if enabledLabel then + self:halign(1):valign(1) + self:xy(-4, -0.5) + self:skewx(-0.125) + self:visible(false) + else + self:halign(0.5):valign(1) + self:xy(-24, -0.5) + self:skewx(-0.125) + self:visible(false) + end + end + }, + LoadFont("Common Normal") .. { + Name = "Label", + InitCommand = function(self) + self:halign(0):valign(1) + self:diffusebottomedge(color("0.75,0.75,0.75,1")) + self:visible(false) + end + }, +} + +if enabledCombo then + return t +end + +return Def.ActorFrame {} diff --git a/Themes/Rebirth/Graphics/Player judgment/default.lua b/Themes/Rebirth/Graphics/Player judgment/default.lua new file mode 100644 index 0000000000..f0f255c811 --- /dev/null +++ b/Themes/Rebirth/Graphics/Player judgment/default.lua @@ -0,0 +1,136 @@ +local c +local enabledJudgment = playerConfig:get_data().JudgmentText +local enabledAnimations = playerConfig:get_data().JudgmentTweens + +--[[ +Removed from metrics and translated to lua here: +JudgmentW1Command=shadowlength,0;diffusealpha,1;zoom,1.3;linear,0.05;zoom,1;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0;glowblink;effectperiod,0.05;effectcolor1,color("1,1,1,0");effectcolor2,color("1,1,1,0.25") +JudgmentW2Command=shadowlength,0;diffusealpha,1;zoom,1.3;linear,0.05;zoom,1;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0 +JudgmentW3Command=shadowlength,0;diffusealpha,1;zoom,1.2;linear,0.05;zoom,1;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0; +JudgmentW4Command=shadowlength,0;diffusealpha,1;zoom,1.1;linear,0.05;zoom,1;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0; +JudgmentW5Command=shadowlength,0;diffusealpha,1;zoom,1.0;vibrate;effectmagnitude,4,8,8;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0 +JudgmentMissCommand=shadowlength,0;diffusealpha,1;zoom,1;linear,0.8;sleep,0.8;linear,0.1;zoomy,0.5;zoomx,2;diffusealpha,0 +]] +local JudgeCmds = { + TapNoteScore_W1 = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.3 * MovableValues.JudgmentZoom) + self:linear(0.05):zoom(1 * MovableValues.JudgmentZoom) + self:sleep(0.8):linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + self:glowblink():effectperiod(0.05):effectcolor1(color("1,1,1,0")):effectcolor2(color("1,1,1,0.25")) + end, + TapNoteScore_W2 = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.3 * MovableValues.JudgmentZoom) + self:linear(0.05):zoom(1 * MovableValues.JudgmentZoom) + self:sleep(0.8):linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + end, + TapNoteScore_W3 = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.2 * MovableValues.JudgmentZoom) + self:linear(0.05):zoom(1 * MovableValues.JudgmentZoom) + self:sleep(0.8):linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + end, + TapNoteScore_W4 = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.1 * MovableValues.JudgmentZoom) + self:linear(0.05):zoom(1 * MovableValues.JudgmentZoom) + self:sleep(0.8):linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + end, + TapNoteScore_W5 = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.0 * MovableValues.JudgmentZoom) + self:vibrate() + self:effectmagnitude(0.01,0.02,0.02) + self:sleep(0.8) + self:linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + end, + TapNoteScore_Miss = function(self) + self:shadowlength(0):diffusealpha(1):zoom(1.0 * MovableValues.JudgmentZoom) + self:linear(0.8) + self:sleep(0.8):linear(0.1) + self:zoomx(0.5 * MovableValues.JudgmentZoom) + self:zoomy(2 * MovableValues.JudgmentZoom) + self:diffusealpha(0) + end +} + +local TNSFrames = { + TapNoteScore_W1 = 0, + TapNoteScore_W2 = 1, + TapNoteScore_W3 = 2, + TapNoteScore_W4 = 3, + TapNoteScore_W5 = 4, + TapNoteScore_Miss = 5 +} + +local t = Def.ActorFrame { + Name = "Judgment", -- c++ renames this to "Judgment" + BeginCommand = function(self) + c = self:GetChildren() + -- queued to run slightly late + self:queuecommand("SetUpMovableValues") + registerActorToCustomizeGameplayUI({ + actor = self, + coordInc = {5,1}, + zoomInc = {0.1,0.05}, + }) + end, + SetUpMovableValuesMessageCommand = function(self) + self:xy(MovableValues.JudgmentX, MovableValues.JudgmentY) + self:zoom(MovableValues.JudgmentZoom) + end, + Def.Sprite { + Texture = "../../../../" .. getAssetPath("judgment"), + Name = "Judgment", + InitCommand = function(self) + self:pause() + self:visible(false) + end, + ResetCommand = function(self) + self:finishtweening() + self:stopeffect() + self:visible(false) + end, + }, + + JudgmentMessageCommand = function(self, param) + if param.HoldNoteScore or param.FromReplay then + return + end + local iNumStates = c.Judgment:GetNumStates() + local iFrame = TNSFrames[param.TapNoteScore] + if not iFrame then + return + end + if iNumStates == 12 then + iFrame = iFrame * 2 + if not param.Early then + iFrame = iFrame + 1 + end + end + + self:playcommand("Reset") + c.Judgment:visible(true) + c.Judgment:setstate(iFrame) + if enabledAnimations then + JudgeCmds[param.TapNoteScore](c.Judgment) + end + end, +} + +if enabledJudgment then + return t +end + +return Def.ActorFrame {} diff --git a/Themes/Rebirth/Graphics/ScreenTitleMenu scroller.lua b/Themes/Rebirth/Graphics/ScreenTitleMenu scroller.lua new file mode 100644 index 0000000000..c76d3674b9 --- /dev/null +++ b/Themes/Rebirth/Graphics/ScreenTitleMenu scroller.lua @@ -0,0 +1,94 @@ +local gc = Var("GameCommand") +local choiceTable = strsplit(THEME:GetMetric("ScreenTitleMenu", "ChoiceNames"), ",") +local transitionTable = strsplit(THEME:GetMetric("ScreenTitleMenu", "ChoicesExitScreen"), ",") +local screen + +local choiceTextZoom = 0.6 +local buttonVerticalFudge = 5 + +-- initial width of the selector +-- it may be bigger or smaller in reality due to item resizing done in ScreenTitleMenu underlay +local selectorWidth = 574 / 1920 * SCREEN_WIDTH + +-- look through the choices defined in metrics.ini [ScreenTitleMenu] ChoiceNames +-- the Scrollers (choices) are named "ScrollChoice" so we just have to look for it that way +-- we offset it by 1 to index it from 0: the index being sent back to c++ needs to be 0 indexed +-- keep that in mind if reusing it somewhere else in a table +-- index of 1 returned by this function is the 2nd item in the choiceTable +local function findChoiceIndex(name) + local ind = 0 + for i, nam in ipairs(choiceTable) do + if "ScrollChoice"..nam == name then + ind = i - 1 + end + end + return ind +end + +-- we keep track of which screen choices will cause a transition to the next screen or something else +local function choiceWillTransitionOut(name) + for i, nam in ipairs(transitionTable) do + if nam == name then + return true + end + end + return false +end + +return Def.ActorFrame { + BeginCommand = function(self) + screen = SCREENMAN:GetTopScreen() + end, + -- the name of this frame is determined by C++ + -- it will be the name of the choice + -- ie: ScrollerChoice + + LoadFont("Common Large") .. { + Name = "ScrollerText", + BeginCommand = function(self) + self:halign(0) + self:zoom(choiceTextZoom) + self:settext(THEME:GetString(screen:GetName(), gc:GetText())) + self:diffuse(COLORS:getTitleColor("PrimaryText")) + self:diffusealpha(1) + end + }, + UIElements.QuadButton(1, 1) .. { + Name = "Button", + BeginCommand = function(self) + self:halign(0) + self:diffusealpha(0) + local txt = self:GetParent():GetChild("ScrollerText") + self:zoomto(selectorWidth, txt:GetZoomedHeight() + buttonVerticalFudge) + end, + MouseOverCommand = function(self) + -- if not focused on the scroller, don't allow controlling it + if not TITLE:GetFocus() then return end + local ind = findChoiceIndex(self:GetParent():GetName()) + screen:SetSelectionIndex(ind) + end, + MouseDownCommand = function(self) + -- if not focused on the scroller, don't allow controlling it + if not TITLE:GetFocus() then return end + + -- this should make clicking work the same as pressing enter + screen:PlaySelectSound() + screen:playcommand("MadeChoiceP1") + screen:playcommand("Choose") + MESSAGEMAN:Broadcast("MenuStartP1") + + -- add 1 to this index to convert c++ index to lua table index + local ind = findChoiceIndex(self:GetParent():GetName()) + 1 + local choice = choiceTable[ind] + + -- have to join players if playing the game or whatever + -- this might be moved to force join on game start for profile selection in main menu + GAMESTATE:JoinPlayer(PLAYER_1) + if choiceWillTransitionOut(choice) then + screen:PostScreenMessage("SM_BeginFadingOut", 0) + else + GAMESTATE:ApplyGameCommand(THEME:GetMetric("ScreenTitleMenu", "Choice"..choice)) + end + end + } +} diff --git a/Themes/Rebirth/Graphics/StreamDisplay hot.png b/Themes/Rebirth/Graphics/StreamDisplay hot.png new file mode 100644 index 0000000000..b27e3441d1 Binary files /dev/null and b/Themes/Rebirth/Graphics/StreamDisplay hot.png differ diff --git a/Themes/Rebirth/Graphics/StreamDisplay normal.png b/Themes/Rebirth/Graphics/StreamDisplay normal.png new file mode 100644 index 0000000000..c87e5648b3 Binary files /dev/null and b/Themes/Rebirth/Graphics/StreamDisplay normal.png differ diff --git a/Themes/Rebirth/Graphics/Title BG.lua b/Themes/Rebirth/Graphics/Title BG.lua new file mode 100644 index 0000000000..c57e888003 --- /dev/null +++ b/Themes/Rebirth/Graphics/Title BG.lua @@ -0,0 +1,54 @@ +-- the grid image we are using is 200x200 +-- determine how many images we need to load to cover the screen +local adjustedsize = 200 / 1080 * SCREEN_HEIGHT + +-- adding 1 to these counts for the moving background stuff, give us more room to work with +local verticalcount = math.ceil(SCREEN_HEIGHT / adjustedsize) + 1 +local horizontalcount = math.ceil(SCREEN_WIDTH / adjustedsize) + 1 +local checkerboardAnimationSeconds = 7 + +-- generate the bg checkerboard as a frame +local function bgCheckerBoard() + local d = Def.ActorFrame {Name = "BGCheckerboardFrame"} + + for i = 1,verticalcount do + for j = 1, horizontalcount do + d[#d+1] = Def.Sprite { + Name = "BGCheckerboard_"..i.."_"..j, + Texture = THEME:GetPathG("", "bg-pattern"), + InitCommand = function(self) + self:halign(0):valign(0) + self:xy((j-1) * adjustedsize, (i-1) * adjustedsize) + self:zoomto(adjustedsize, adjustedsize) + end + } + end + end + + return d +end + +return Def.ActorFrame { + Name = "BGContainer", + Def.Quad { + Name = "BG", + InitCommand = function(self) + self:halign(0):valign(0) + self:zoomto(SCREEN_WIDTH, SCREEN_HEIGHT) + self:diffuse(COLORS:getTitleColor("UnderlayBackground")) + self:diffusealpha(1) + end + }, + bgCheckerBoard() .. { + -- These extra commands will move the checkerboard diagonally up left infinitely + BeginCommand = function(self) + self:queuecommand("Animate") + end, + AnimateCommand = function(self) + self:xy(0,0) + self:linear(checkerboardAnimationSeconds) + self:xy(-adjustedsize,-adjustedsize) + self:queuecommand("Animate") + end + } +} \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/Triangle.png b/Themes/Rebirth/Graphics/Triangle.png new file mode 100644 index 0000000000..2b80a3740b Binary files /dev/null and b/Themes/Rebirth/Graphics/Triangle.png differ diff --git a/Themes/Rebirth/Graphics/Warning.png b/Themes/Rebirth/Graphics/Warning.png new file mode 100644 index 0000000000..3c962178c4 Binary files /dev/null and b/Themes/Rebirth/Graphics/Warning.png differ diff --git a/Themes/Rebirth/Graphics/_nothing.lua b/Themes/Rebirth/Graphics/_nothing.lua new file mode 100644 index 0000000000..0b4dfd99e4 --- /dev/null +++ b/Themes/Rebirth/Graphics/_nothing.lua @@ -0,0 +1 @@ +return Def.ActorFrame {} \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/_thick circle (doubleres).png b/Themes/Rebirth/Graphics/_thick circle (doubleres).png new file mode 100644 index 0000000000..d55a3a719a Binary files /dev/null and b/Themes/Rebirth/Graphics/_thick circle (doubleres).png differ diff --git a/Themes/Rebirth/Graphics/_triangle (doubleres).png b/Themes/Rebirth/Graphics/_triangle (doubleres).png new file mode 100644 index 0000000000..0dbe791860 Binary files /dev/null and b/Themes/Rebirth/Graphics/_triangle (doubleres).png differ diff --git a/Themes/Rebirth/Graphics/bg-pattern.png b/Themes/Rebirth/Graphics/bg-pattern.png new file mode 100644 index 0000000000..3efd593cfb Binary files /dev/null and b/Themes/Rebirth/Graphics/bg-pattern.png differ diff --git a/Themes/Rebirth/Graphics/checkmark (doubleres).png b/Themes/Rebirth/Graphics/checkmark (doubleres).png new file mode 100644 index 0000000000..7e896d22ca Binary files /dev/null and b/Themes/Rebirth/Graphics/checkmark (doubleres).png differ diff --git a/Themes/Rebirth/Graphics/color_hsv.png b/Themes/Rebirth/Graphics/color_hsv.png new file mode 100644 index 0000000000..8bd743d3d7 Binary files /dev/null and b/Themes/Rebirth/Graphics/color_hsv.png differ diff --git a/Themes/Rebirth/Graphics/color_sat_gradient.png b/Themes/Rebirth/Graphics/color_sat_gradient.png new file mode 100644 index 0000000000..7d03e82659 Binary files /dev/null and b/Themes/Rebirth/Graphics/color_sat_gradient.png differ diff --git a/Themes/Rebirth/Graphics/color_sat_overlay.png b/Themes/Rebirth/Graphics/color_sat_overlay.png new file mode 100644 index 0000000000..6b30e2ad7a Binary files /dev/null and b/Themes/Rebirth/Graphics/color_sat_overlay.png differ diff --git a/Themes/Rebirth/Graphics/deleteGoal.png b/Themes/Rebirth/Graphics/deleteGoal.png new file mode 100644 index 0000000000..7cc027eef4 Binary files /dev/null and b/Themes/Rebirth/Graphics/deleteGoal.png differ diff --git a/Themes/Rebirth/Graphics/dialogExit.png b/Themes/Rebirth/Graphics/dialogExit.png new file mode 100644 index 0000000000..bf0a9c7dbf Binary files /dev/null and b/Themes/Rebirth/Graphics/dialogExit.png differ diff --git a/Themes/Rebirth/Graphics/dialogTop.png b/Themes/Rebirth/Graphics/dialogTop.png new file mode 100644 index 0000000000..82c5eee87d Binary files /dev/null and b/Themes/Rebirth/Graphics/dialogTop.png differ diff --git a/Themes/Rebirth/Graphics/elementborder.lua b/Themes/Rebirth/Graphics/elementborder.lua new file mode 100644 index 0000000000..5e53b966ef --- /dev/null +++ b/Themes/Rebirth/Graphics/elementborder.lua @@ -0,0 +1,122 @@ +-- border used for customize gameplay primarily but feel free to attempt to use it for something else and have it not work + +local borderAlpha = 0.2 +local buttonHoverAlpha = 0.6 + +return Def.ActorFrame { + Name = "BorderContainer", -- not really necessary to have this in an actorframe unless the border is more complex + UIElements.QuadButton(1) .. { + Name = "Border", + OnCommand = function(self) + self:queuecommand("SetUp") + end, + SetUpCommand = function(self) + local pf = self:GetParent():GetParent() + + -- avoid shadowing self in the below nested functions, so store self in some variable + local shelf = self + self:GetParent():SetUpdateFunction(function(self) + -- find the largest actor child of the assigned parent we are making a border for + -- assign this border to match its size basically + local bigw = 0 + local bigh = 0 + local eleh = nil + pf:RunCommandsRecursively( + function(self) + if self:GetName() ~= shelf:GetName() then + local w = self:GetZoomedWidth() + local h = self:GetZoomedHeight() + if w > bigw then bigw = w eleh = self end + if h > bigh then bigh = h eleh = self end + end + end + ) + shelf:halign(eleh:GetHAlign()) + shelf:valign(eleh:GetVAlign()) + shelf:x(eleh:GetX()) + shelf:y(eleh:GetY()) + shelf:zoomto(bigw, bigh) + end) + self:diffusealpha(borderAlpha) + + -- allow this to function as a button + -- even with the comment this makes no sense. basically it has to do with layering stuff + -- buttons on buttons and buttons in front or behind certain elements breaks stuff + -- a higher z value is more "in front" + self:z(10) + + -- place the quad behind the whole actorframe we are bordering + self:draworder(-99) + pf:SortByDrawOrder() + + self.alphaDeterminingFunction = function(self) + if isOver(self) then + pf:diffusealpha(buttonHoverAlpha) + self:diffusealpha(borderAlpha * buttonHoverAlpha) + else + pf:diffusealpha(1) + self:diffusealpha(borderAlpha) + end + end + self:queuecommand("SetUpFinished") + end, + MouseOverCommand = function(self) + self:alphaDeterminingFunction() + end, + MouseOutCommand = function(self) + self:alphaDeterminingFunction() + end, + MouseDragCommand = function(self, params) + if params.event == "DeviceButton_right mouse button" or not self.canDrag then return end + + -- due to unknown math reasons, rotated elements enter an unstable state which causes them + -- to fly off the screen when their angle of rotation exceeds 60 degrees in either direction + -- so im disabling dragging any rotated elements + -- if you know how to fix this go ahead + if self:GetTrueRotationZ() ~= 0 then return end + + + local screenscale = MovableValues.ScreenZoom + local pp = self:GetParent():GetParent() + local ppp = pp:GetParent() + local trueX = pp:GetTrueX() / screenscale + local trueY = pp:GetTrueY() / screenscale + local zoomfactor = 1 + + -- this is almost always true but + -- the primary reason this exists is to offset the Player related things properly + -- normally it should be 0, but instead it is 640 for example + -- hindsight comment: gonna be real with u chief this is a huge hack but it works + -- if it stops working RESTRUCTURE YOUR ELEMENTS + -- this only works because 'pp' is the ActorFrame that represents the gameplay element + -- if ppp has been zoomed (breaking everything), then pp is a child of something that isn't a screen, + -- such as the NoteField being pp and the Player being ppp + if ppp ~= nil then + trueX = trueX - (ppp:GetTrueX() / screenscale) + trueY = trueY - (ppp:GetTrueY() / screenscale) + zoomfactor = ppp:GetZoom() + end + + local newx = params.MouseX + trueX - ((self.initialClickX or 0) / screenscale) + local newy = params.MouseY + trueY - ((self.initialClickY or 0) / screenscale) + newx = newx / zoomfactor + newy = newy / zoomfactor + local differenceX = newx - pp:GetX() + local differenceY = newy - pp:GetY() + + pp:x(newx):y(newy) + setSelectedCustomizeGameplayElementActorPosition(differenceX, differenceY) + end, + MouseDownCommand = function(self, params) + if params.event == "DeviceButton_right mouse button" then return end + local pp = self:GetParent():GetParent() + local screenscale = MovableValues.ScreenZoom + + self.initialClickX = params.MouseX * screenscale + self.initialClickY = params.MouseY * screenscale + + local name = pp:GetName() + self.canDrag = setSelectedCustomizeGameplayElementActorByName(name) + end, + } +} \ No newline at end of file diff --git a/Themes/Rebirth/Graphics/exit.png b/Themes/Rebirth/Graphics/exit.png new file mode 100644 index 0000000000..9de745a2fc Binary files /dev/null and b/Themes/Rebirth/Graphics/exit.png differ diff --git a/Themes/Rebirth/Graphics/gameinfoandhelp.png b/Themes/Rebirth/Graphics/gameinfoandhelp.png new file mode 100644 index 0000000000..8b9e34c4d4 Binary files /dev/null and b/Themes/Rebirth/Graphics/gameinfoandhelp.png differ diff --git a/Themes/Rebirth/Graphics/loggedin.png b/Themes/Rebirth/Graphics/loggedin.png new file mode 100644 index 0000000000..f710893f70 Binary files /dev/null and b/Themes/Rebirth/Graphics/loggedin.png differ diff --git a/Themes/Rebirth/Graphics/loggedout.png b/Themes/Rebirth/Graphics/loggedout.png new file mode 100644 index 0000000000..75e08878ef Binary files /dev/null and b/Themes/Rebirth/Graphics/loggedout.png differ diff --git a/Themes/Rebirth/Graphics/mirror.png b/Themes/Rebirth/Graphics/mirror.png new file mode 100644 index 0000000000..f2dd6938aa Binary files /dev/null and b/Themes/Rebirth/Graphics/mirror.png differ diff --git a/Themes/Rebirth/Graphics/newProfile.png b/Themes/Rebirth/Graphics/newProfile.png new file mode 100644 index 0000000000..b473a1d1bf Binary files /dev/null and b/Themes/Rebirth/Graphics/newProfile.png differ diff --git a/Themes/Rebirth/Graphics/packdlIcon.png b/Themes/Rebirth/Graphics/packdlIcon.png new file mode 100644 index 0000000000..8e723c2300 Binary files /dev/null and b/Themes/Rebirth/Graphics/packdlIcon.png differ diff --git a/Themes/Rebirth/Graphics/packdownloads.png b/Themes/Rebirth/Graphics/packdownloads.png new file mode 100644 index 0000000000..2e636ea35d Binary files /dev/null and b/Themes/Rebirth/Graphics/packdownloads.png differ diff --git a/Themes/Rebirth/Graphics/profileselectorGlow.png b/Themes/Rebirth/Graphics/profileselectorGlow.png new file mode 100644 index 0000000000..810f86c26e Binary files /dev/null and b/Themes/Rebirth/Graphics/profileselectorGlow.png differ diff --git a/Themes/Rebirth/Graphics/random.png b/Themes/Rebirth/Graphics/random.png new file mode 100644 index 0000000000..0a289716c9 Binary files /dev/null and b/Themes/Rebirth/Graphics/random.png differ diff --git a/Themes/Rebirth/Graphics/round_star.png b/Themes/Rebirth/Graphics/round_star.png new file mode 100644 index 0000000000..e028749557 Binary files /dev/null and b/Themes/Rebirth/Graphics/round_star.png differ diff --git a/Themes/Rebirth/Graphics/roundedCapsBar.png b/Themes/Rebirth/Graphics/roundedCapsBar.png new file mode 100644 index 0000000000..0c498c187a Binary files /dev/null and b/Themes/Rebirth/Graphics/roundedCapsBar.png differ diff --git a/Themes/Rebirth/Graphics/scoreboardGlow.png b/Themes/Rebirth/Graphics/scoreboardGlow.png new file mode 100644 index 0000000000..8cb0bc9e83 Binary files /dev/null and b/Themes/Rebirth/Graphics/scoreboardGlow.png differ diff --git a/Themes/Rebirth/Graphics/searchBar.png b/Themes/Rebirth/Graphics/searchBar.png new file mode 100644 index 0000000000..377f81b935 Binary files /dev/null and b/Themes/Rebirth/Graphics/searchBar.png differ diff --git a/Themes/Rebirth/Graphics/searchIcon.png b/Themes/Rebirth/Graphics/searchIcon.png new file mode 100644 index 0000000000..e3b2dc21ba Binary files /dev/null and b/Themes/Rebirth/Graphics/searchIcon.png differ diff --git a/Themes/Rebirth/Graphics/selectorFade.png b/Themes/Rebirth/Graphics/selectorFade.png new file mode 100644 index 0000000000..54852d4f67 Binary files /dev/null and b/Themes/Rebirth/Graphics/selectorFade.png differ diff --git a/Themes/Rebirth/Graphics/settings.png b/Themes/Rebirth/Graphics/settings.png new file mode 100644 index 0000000000..8d7f815789 Binary files /dev/null and b/Themes/Rebirth/Graphics/settings.png differ diff --git a/Themes/Rebirth/Graphics/showEval.png b/Themes/Rebirth/Graphics/showEval.png new file mode 100644 index 0000000000..2c199c8b31 Binary files /dev/null and b/Themes/Rebirth/Graphics/showEval.png differ diff --git a/Themes/Rebirth/Graphics/showReplay.png b/Themes/Rebirth/Graphics/showReplay.png new file mode 100644 index 0000000000..ed56c260ac Binary files /dev/null and b/Themes/Rebirth/Graphics/showReplay.png differ diff --git a/Themes/Rebirth/Graphics/sliderBar.png b/Themes/Rebirth/Graphics/sliderBar.png new file mode 100644 index 0000000000..fa10218f43 Binary files /dev/null and b/Themes/Rebirth/Graphics/sliderBar.png differ diff --git a/Themes/Rebirth/Graphics/stepsdisplayGlow.png b/Themes/Rebirth/Graphics/stepsdisplayGlow.png new file mode 100644 index 0000000000..010b462f7b Binary files /dev/null and b/Themes/Rebirth/Graphics/stepsdisplayGlow.png differ diff --git a/Themes/Rebirth/Graphics/title-gradient.png b/Themes/Rebirth/Graphics/title-gradient.png new file mode 100644 index 0000000000..7bd25999f8 Binary files /dev/null and b/Themes/Rebirth/Graphics/title-gradient.png differ diff --git a/Themes/Rebirth/Graphics/updatedownload.png b/Themes/Rebirth/Graphics/updatedownload.png new file mode 100644 index 0000000000..be70117d43 Binary files /dev/null and b/Themes/Rebirth/Graphics/updatedownload.png differ diff --git a/Themes/Rebirth/Graphics/upload.png b/Themes/Rebirth/Graphics/upload.png new file mode 100644 index 0000000000..0665bf47bc Binary files /dev/null and b/Themes/Rebirth/Graphics/upload.png differ diff --git a/Themes/Rebirth/LICENSE.md b/Themes/Rebirth/LICENSE.md new file mode 100644 index 0000000000..41fbc9465b --- /dev/null +++ b/Themes/Rebirth/LICENSE.md @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2020 Barinade + +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. diff --git a/Themes/Rebirth/Languages/en.ini b/Themes/Rebirth/Languages/en.ini new file mode 100644 index 0000000000..799448872e --- /dev/null +++ b/Themes/Rebirth/Languages/en.ini @@ -0,0 +1,110 @@ +[Common] +WindowTitle=Etterna: Rebirth + +[ClearTypes] +MFC=MFC +WF=WF +SDP=SDP +PFC=PFC +BF=BF +SDG=SDG +FC=FC +MF=MF +SDCB=SDCB +Clear=Clear +Failed=Failed +Invalid=Invalid +No Play=No Play + +[OptionNames] +SetPercent=Set Percent +PersonalBest=Personal Best +EWMA=EWMA +NPSDisplay=NPS Display +NPSGraph=NPS Graph + + +[OptionTitles] +ReceptorSize = Receptor Size +CustomizeGameplay= Customize Gameplay +LaneCover=Lane Cover +StaticBG = Background Changes +CBHighlight=CB Highlight +JudgmentText=Judgment Text +JudgmentAnimations=Judgment Animations +ComboText=Combo Text +ComboLabel=Combo Label +DisplayPercent = Current Percent +TargetTracker = Goal Tracker +TargetGoal = Tracker Goal +TargetTrackerMode = Tracker mode +JudgeCounter = Judge Counter +ErrorBar=Error Bar +ErrorBarCount=Error Bar Count +PlayerInfo = Player Info +FullProgressBar = Full Progressbar +MiniProgressBar = Mini Progressbar +Leaderboard = Leaderboard +NPSDisplay=NPS Display + +[OptionExplanations] +ReceptorSize = Scale the size of the receptors and notes. This will also indirectly scale your scrolling speed. +CustomizeGameplay= While active, allows you to reposition and resize elements on the gameplay screen to your liking. Any changes will persist through version updates. +LaneCover=Enable lane cover which will cover a portion of the notefield. The height can be adjusted by holding down