Compare commits

...

331 Commits
v0.1 ... stable

Author SHA1 Message Date
Freddie W
34b173ba49
Merge pull request #78 from dannylin0711/stable
fix(sdvx): Use dynamic require instead of ESM style import
2026-02-26 04:12:05 +08:00
LatoWolf
e87a36d158 Use dynamic require instead of ESM style import (no type check in this case) 2026-02-26 03:12:53 +08:00
Freddie W
910c134f12
Merge pull request #77 from dannylin0711/stable
SDVX EG Final
2026-01-22 16:27:11 +08:00
LatoWolf
4e05f9107f sdvx: Update Readme 2026-01-22 14:17:07 +08:00
LatoWolf
45c7f0dcb1 sdvx: Update Readme 2026-01-22 14:15:44 +08:00
LatoWolf
5d484d8378 No more BigInt() to protect user from not able to start the server 2026-01-15 10:16:43 +08:00
LatoWolf
2cac29b513 Added judge save and modify detail page to load ap_card from game path 2026-01-15 10:16:39 +08:00
LatoWolf
b04e723fea Update SDVX webui plugin to use directly pointed asset paths, preview is still missing on some bgms 2026-01-14 15:33:38 +08:00
LatoWolf
7067c00c66
Fix multiroute 2026-01-06 02:49:33 +08:00
LatoWolf
ec0f23177b
Delete _shared/lib directory 2026-01-06 02:48:06 +08:00
dannylin0711
3dcc69bbba Merge branch 'stable' of github.com:dannylin0711/plugins into stable 2026-01-04 15:44:55 +08:00
dannylin0711
73b27b4e0d EG Final 2026-01-04 14:37:08 +08:00
LatoWolf
0cbb84d46f
Merge branch 'asphyxia-core:stable' into stable 2026-01-04 13:42:59 +08:00
Freddie W
997d141b3b
Merge pull request #76 from JamesLewisLiu/gitadora-feature-add
GITADORA Plugin Feature Add
2025-12-05 04:40:28 +08:00
James Liu
163642e0c1
Merge pull request #3 from JamesLewisLiu/codex/implement-shared-song-scores-feature
Add shared song score aggregation support
2025-12-05 01:21:10 +08:00
James Liu
1bbebfb033 Add shared song score aggregation support 2025-12-05 01:20:47 +08:00
James Liu
4aeb1249f6 Added initial support for GALAXY WAVE.
Added initial support for GALAXY WAVE.
2025-12-05 00:44:08 +08:00
James Liu
0ad96458b6 Add support up for Tri-Boost Re:EVOLVE, HIGH-VOLTAGE, FUZZ-UP
Add support up for Tri-Boost Re:EVOLVE, HIGH-VOLTAGE, FUZZ-UP
2025-12-04 22:56:42 +08:00
James Liu
502886ea93
Merge pull request #1 from JamesLewisLiu/codex/update-xml-reading-logic-for-mdb-files
Adjust MDB XML filename resolution
2025-12-03 23:05:02 +08:00
James Liu
98fe38982f Add support for prefixed MDB XML filenames 2025-12-03 23:04:36 +08:00
James Liu
d863a522b1 Fix GITADORA Plugin errors when launch asphyxia with devmode. 2025-12-03 09:19:10 +00:00
Freddie W
aaa4a93f57
Merge pull request #74 from duel0213/iidx
Add IIDX support
2025-10-04 19:47:59 +08:00
duel0213
ebf56f009d Merge branch 'stable' of https://github.com/asphyxia-core/plugins into iidx 2025-10-04 17:38:45 +09:00
duel0213
4427834df4 IIDX: Initial support added for Pinky Crush 2025-09-28 23:45:40 +09:00
Freddie W
dc36427ffb
Merge pull request #73 from cracrayol/stable
[PnM] Add Unilab support
2025-09-25 00:13:01 +08:00
cracrayol
3a215a1314 Unilab : Fix first play value 2025-09-24 16:45:58 +02:00
cracrayol
ee0ae068f6 Add Unilab support
Remove non-core Asphyxia data import
Send correct number of Goods
2025-09-24 16:13:34 +02:00
cracrayol
8d1b13c730
Merge branch 'asphyxia-core:stable' into stable 2025-09-22 22:44:46 +02:00
duel0213
3bad80d504 Merge pull request #2 from anzuwork/patch-5
IIDX: Fixed treating head equip as hand equip
2025-08-23 11:06:58 +09:00
Danny Lin
625552aa5c Add dependencies 2025-08-05 09:05:10 +08:00
Freddie W
eb8ac47803
Merge pull request #70 from dannylin0711/2024ver
SDVX 2024ver
2025-07-08 08:54:23 +08:00
duel0213
d66f9e1085 IIDX: Implemented EXTRA FAVORITE 2025-03-26 21:57:20 +09:00
duel0213
8bbe8390eb IIDX: Added error message for incompatible score database
IIDX: Fixed where unable to login after step up (rootage)
IIDX: Reverted `v0.1.15` dev mode hackjob codes
IIDX: Line Encoding changes, misc
2025-03-21 21:11:45 +09:00
duel0213
11cbbb9e7a IIDX: Fixed where unable to login after event play (copula) 2025-02-08 23:39:58 +09:00
duel0213
5821ea8b95 IIDX: Fixed where music.crate response may not compatible with certain versions 2025-02-08 19:15:59 +09:00
duel0213
7aff587928 IIDX: Moved invalid miss count migration code position 2025-01-26 16:04:40 +09:00
duel0213
1b277d768c IIDX: misc 2025-01-25 17:37:04 +09:00
duel0213
bc4b54dfd8 IIDX: Changed music.arenaCPU to make compatible with newer version 2025-01-25 12:30:07 +09:00
duel0213
1ee57ffa6e IIDX: misc 2 2025-01-25 11:25:09 +09:00
duel0213
1b9afb6486 IIDX: misc 2025-01-24 23:47:59 +09:00
duel0213
6783f0d13f IIDX: Fixed rival webui 2025-01-24 23:14:38 +09:00
duel0213
1a027d86e5 IIDX: Added migration for existing IIDX29 pcdata 2025-01-24 21:12:26 +09:00
duel0213
14d3452fc8 IIDX: misc 2025-01-23 22:58:14 +09:00
duel0213
6b5a90b05e IIDX: Fixed grade achieve and miss count 2025-01-22 01:14:06 +09:00
LatoWolf
0a0d463b63
Update common.ts 2025-01-21 09:49:48 +08:00
duel0213
a0f9167bfc IIDX: misc 2024-11-30 17:40:29 +09:00
duel0213
27de09dae2 IIDX: Added missing default elements on IIDX29 pcdata 2024-11-30 13:07:20 +09:00
duel0213
09407661ef IIDX: misc 2024-11-26 20:59:52 +09:00
duel0213
f231932289 IIDX: Changed pc.lanegacha response to not use lane gacha ticket by default 2024-11-26 19:27:45 +09:00
duel0213
2159989026 IIDX: Fixed score import (data version 2) 2024-11-26 19:21:52 +09:00
duel0213
863fa7a410 IIDX: Fixed lightning model settings save 2024-11-09 10:05:15 +09:00
duel0213
c3b2fab90c IIDX: Added migration code for invalid miss count records 2024-10-23 22:44:25 +09:00
dannylin0711
fd9866d483 Create exg_data.json 2024-10-21 13:06:34 +08:00
duel0213
ad36c48bc6 IIDX: misc 2024-10-20 03:33:13 +09:00
duel0213
44458a1a75 IIDX: Fixed MUSIC FILTER 2024-10-20 03:02:38 +09:00
duel0213
8d06dcf25b IIDX: misc 2024-10-20 01:46:33 +09:00
duel0213
3a73379fb9 IIDX: Fixed miss count issue 2024-10-19 23:08:29 +09:00
duel0213
993595ff99 IIDX: Fixed score import (DP/WebUI typo) 2024-10-17 19:47:22 +09:00
duel0213
1f05cef58b IIDX: Fixed where MISS COUNT has 0 as default 2024-10-14 22:14:42 +09:00
duel0213
844dd6c4d1 IIDX: Initial support added for EPOLIS
IIDX: Bug fixes and Enhancements
2024-10-09 11:23:39 +09:00
dannylin0711
06e7a067de SDVX EG update 2024-10-04 20:41:15 +08:00
duel0213
5750f8f464 IIDX: Added score import/export 2024-08-01 18:56:35 +09:00
duel0213
425b8962ee IIDX: Added grade badge save 2024-08-01 18:48:18 +09:00
duel0213
064838bca9 IIDX: Added experimental badge save/load support 2024-07-31 06:40:27 +09:00
duel0213
432cd116c1 IIDX: Added missing qpro saving code (WebUI) 2024-07-29 12:13:33 +09:00
duel0213
6853773a41 IIDX: Added dummy license element on pc.common response 2024-07-26 12:41:06 +09:00
duel0213
4330b65ade IIDX: Fixed where s_sub_type reference d_sub_type on pc.get response
IIDX: Update README.md
2024-07-18 06:52:17 +09:00
duel0213
a2e387d636 IIDX: Fixed where mydata not being sent on certain versions 2024-07-17 01:41:11 +09:00
duel0213
d8d2e59818 IIDX: misc 2 2024-07-10 04:51:38 +09:00
duel0213
156d76dda3 IIDX: misc 2024-06-11 15:31:38 +09:00
duel0213
1bb944e274 IIDX: Added Disable Beginner Option, misc 2024-05-21 16:13:32 +09:00
duel0213
d490c53425 IIDX: Update README.md 2024-05-03 20:53:15 +09:00
duel0213
4a0e97106a IIDX: Changed music.appoint response to comply with old games (~ DJ TROOPERS) 2024-04-06 20:48:42 +09:00
duel0213
851618de7f IIDX: Update README.md 2024-04-06 13:53:56 +09:00
duel0213
9c1bc88f96 IIDX: Initial support added for GOLD
IIDX: Fixed where plugin cannot be compiled (Buffer type) - 2
2024-04-06 00:59:27 +09:00
duel0213
20c2d7b83e IIDX: Fixed where plugin cannot be compiled (Buffer type) 2024-04-02 13:22:06 +09:00
duel0213
9a48fadde0 IIDX: Fixed timing display option isn't saving on certain version 2024-03-20 04:10:00 +09:00
duel0213
e04588e942 IIDX: Added events saving on CANNON BALLERS and misc 2024-03-19 15:27:07 +09:00
duel0213
6b24d34d89 Merge branch 'stable' of github.com:duel0213/asphyxia-plugins 2024-03-19 13:15:38 +09:00
duel0213
4a0e22ff98
Merge branch 'asphyxia-core:stable' into stable 2024-03-19 13:15:08 +09:00
duel0213
17e67042d6 IIDX: Added events saving on SINOBUZ and misc 2024-03-19 03:47:51 +09:00
Freddie Wang
9fa01f4104
Merge pull request #64 from raytsang/patch-1
[GITADORA] Fix isSharedFavoriteMusicEnabled is not a function
2024-03-18 20:31:34 +08:00
Raymond Tsang
7a272bd201
Fix isSharedFavoriteMusicEnabled is not a function 2024-03-18 17:05:16 +08:00
duel0213
e1155c7a66 IIDX: Added events saving on Rootage and misc 2024-03-17 00:04:26 +09:00
duel0213
e55e659b3b IIDX: Added OMEGA-Attack saving support 2024-02-29 18:24:46 +09:00
duel0213
bb3333152f IIDX: Fixed Base64toBuffer 2024-02-29 18:24:13 +09:00
duel0213
8a9683589b IIDX: Initial support added for EMPRESS 2024-02-28 09:19:13 +09:00
duel0213
28cf5af5da Merge branch 'stable' of github.com:duel0213/asphyxia-plugins 2024-02-27 12:29:41 +09:00
duel0213
b7df5d1c64 IIDX: Removed shop.savename as not working as intented 2024-02-27 12:27:46 +09:00
duel0213
ad074e5380 IIDX: Added BEGINNER/EXPERT play record support for DJ TROOPERS 2024-02-27 11:03:36 +09:00
duel0213
6244f37687 IIDX: Added music.getralive for DJ TROOPERS 2024-02-26 12:45:21 +09:00
duel0213
1d1e4575fe IIDX: Changed music.crate response to comply DJ TROOPERS 2024-02-26 10:11:47 +09:00
duel0213
df66022b4e Merge branch 'stable' of github.com:duel0213/asphyxia-plugins 2024-02-26 00:14:38 +09:00
duel0213
7f6836d1d8 IIDX: Changed music.crate response to comply with Resort Anthem 2024-02-26 00:14:11 +09:00
duel0213
f4d2a81f07 IIDX: Initial support added for DJ TROOPERS 2024-02-25 19:27:26 +09:00
duel0213
50f54ee2bb IIDX: Added ranking responses
IIDX: Added EXPERT/TUTORIAL records support on SIRIUS
2024-02-25 19:18:42 +09:00
duel0213
927030100a IIDX: Initial support added for SIRIUS 2024-02-23 14:36:22 +09:00
duel0213
c2412571c6 IIDX: Reflect shop name, step up achieve on responses 2024-02-21 00:59:21 +09:00
duel0213
4168a54bd3 IIDX: refactor music.appoint response to look somewhat cleaner 2024-02-20 09:22:14 +09:00
duel0213
c180ee208f IIDX: Exposed more pc.common/gameSystem.systemInfo attributes to plugin settings - 2 2024-02-19 11:28:30 +09:00
duel0213
28538c17dd IIDX: Exposed more pc.common attributes to plugin settings
IIDX: Fixed couple of events saving
2024-02-18 20:25:03 +09:00
duel0213
8fa3349b88 IIDX: Added clear/full combo rate, BEGINNER mode clear lamp support 2024-02-17 15:20:59 +09:00
duel0213
6902182a8d IIDX: Added Experimental WebUI / Plugin Settings
IIDX: Removed pugFile that no longer being used
2024-02-16 10:31:50 +09:00
duel0213
05a500f6c2 IIDX: Added Shop Ranking support 2024-02-15 02:30:06 +09:00
duel0213
998589c21b IIDX: Initial support added for SINOBUZ ~ Rootage
- Converted from asphyxia_route_public
2024-02-14 11:14:38 +09:00
duel0213
6e8c8017e5 IIDX: Initial support added for copula 2024-02-14 08:37:31 +09:00
duel0213
1f4f881545 IIDX: Initial support added for PENDUAL 2024-02-14 05:14:20 +09:00
duel0213
0b44be3640 IIDX: Initial support added for SPADA 2024-02-13 02:54:42 +09:00
duel0213
386c4ccb7b IIDX: Initial support added for tricoro
IIDX: Added movie_upload url setting on plugin settings
2024-01-31 13:00:05 +09:00
duel0213
b9bedd6d1f IIDX: Initial support added for Resort Anthem 2024-01-23 00:35:12 +09:00
duel0213
26b6140d2c Added Initial support for beatmaniaIIDX 2024-01-21 16:55:45 +09:00
LatoWolf
8891f2b222
Update exg.ts 2024-01-20 14:16:40 +08:00
dannylin0711
b2a5c63163 [1205] Enable HEXA OVERDRIVE 2023-12-12 18:55:41 +08:00
dannylin0711
485d751683 Fix licensed characters 2023-11-05 07:50:10 +08:00
Freddie Wang
ccd98fc76c
Merge pull request #61 from dannylin0711/stable
SDVX EG S3 support
2023-10-07 12:53:49 +08:00
dannylin0711
5daaba9b12 Fix data.json 2023-10-07 03:15:42 +08:00
dannylin0711
a5e2b5db0d Update data.json 2023-10-07 03:11:47 +08:00
dannylin0711
8503d5d295 Fix main bg preview making setting page incorrect color 2023-10-07 03:11:18 +08:00
dannylin0711
fea596a158 Add s3p extraction with pure nodejs 2023-10-07 03:06:43 +08:00
dannylin0711
9b7e4a405e Fix fs copy path 2023-10-07 03:05:25 +08:00
dannylin0711
1084ea71ec Fix mainbg not saving after apply in webui 2023-09-15 00:46:23 +08:00
dannylin0711
bc0d5bb0a4 Fix kencode parse error 2023-09-14 20:12:46 +08:00
dannylin0711
0ce49ea16b Main BG preview 2023-09-14 20:05:40 +08:00
dannylin0711
51c06fbb1f Update minimum Core version in readme 2023-09-14 19:10:41 +08:00
dannylin0711
046f442da2 Merge branch 'stable' of github.com:dannylin0711/plugins into stable 2023-09-14 18:44:11 +08:00
dannylin0711
573549d393 6.1.2 2023-09-14 18:43:45 +08:00
dannylin0711
e94794f777 6.1.2 2023-09-14 18:43:31 +08:00
dannylin0711
bbd424fd4c Fix skill type 2023-09-14 18:42:36 +08:00
dannylin0711
925f81f98c 6.1.1 Readme 2023-09-14 13:49:06 +08:00
dannylin0711
3cf798987e 6.1.1 2023-09-14 13:45:23 +08:00
dannylin0711
ff0c85c121 Fix Hiscore 2023-09-03 16:34:05 +08:00
dannylin0711
420961d82e Merge branch 'stable' of github.com:dannylin0711/plugins into stable 2023-08-25 04:51:50 +08:00
dannylin0711
0e289169b5 Further remove unused old version files 2023-08-25 04:51:34 +08:00
LatoWolf
5bdd2f2c7a
Merge branch 'asphyxia-core:stable' into stable 2023-08-24 15:49:56 -05:00
dannylin0711
27116545a4 6.1.0 2023-08-25 04:48:00 +08:00
Freddie Wang
013fafaed1
Merge pull request #58 from aoki-marika/highvoltage
Add GITADORA HIGH-VOLTAGE support
2023-05-03 15:32:12 +08:00
marika
a628fc437a Add GITADORA HIGH-VOLTAGE support with bugfixes. 2023-05-02 03:57:10 -03:00
Freddie Wang
87f6ba2abb
Merge pull request #57 from Kirito3481/jubeat-fix
[jubeat] Fix compile error
2023-02-15 23:48:47 +08:00
Kirito
09301ebada Fix compile error 2023-02-12 22:52:24 +09:00
cracrayol
1c408d84c4 Lapistoria+ : Add force unlock option 2022-09-28 18:36:10 +02:00
Freddie Wang
81630a86a2
Merge pull request #55 from cracrayol/stable
Kaimei Riddles : Support added
2022-09-18 12:29:29 +08:00
cracrayol
b4588e6c47 Kaimei Riddles : Support added
Usaneko : Add Daily Missions support
Usaneko+ : Remove game id check for Omnimix
2022-09-18 02:55:18 +02:00
Freddie Wang
78d8be9092
Merge pull request #54 from yuanqiuye/stable
Support Jubeat Festo
2022-08-14 21:25:17 +08:00
Sean Chen
a4fef0a05b Festo Support finished 2022-08-14 18:14:01 +08:00
Freddie Wang
89deb428e1
Merge pull request #52 from dannylin0711/patch-1
Fix #51
2022-07-30 14:57:37 +08:00
LatoWolf
e7c38f84dd
Fix #51 2022-07-30 14:54:39 +08:00
Freddie Wang
5ad309c1af
Merge pull request #48 from Kirito3481/stable
Support jubeat ripples
2022-05-26 14:22:04 +08:00
Kim Daesoo
5ae06b259a Initial Release 2022-05-26 15:02:13 +09:00
Freddie Wang
33f5720460
Merge pull request #47 from thomeval/stable
Implement Shared Favorite Songs option and server leaderboard
2022-05-24 15:44:00 +08:00
Thome Valentin
249d80d6da Removed one unused script reference 2022-05-24 08:29:31 +02:00
Thome Valentin
7dd7c199da Removed one duplicate .gitignore entry 2022-05-24 08:24:59 +02:00
thomeval
66381eff1f
Merge pull request #4 from thomeval/feature/00_sharedFavorites
Feature/00 shared favorites
2022-05-23 08:58:43 +02:00
Thome Valentin
b84df19e7c * Added experimental 'Shared Favorite Songs' option. If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.
* Added a leaderboards page to the WebUI. This page displays the rank of all players per game and version, ordered by Skill rating.
 * More code cleanups to Profiles.ts
2022-05-23 07:43:50 +02:00
Freddie Wang
b9a9001682
Merge pull request #45 from thomeval/stable
[Gitadora] Fix default scroll speed, code cleanup
2022-05-18 17:02:36 +08:00
Thome Valentin
c4b52fd148 Merge branch 'stable' into feature/00_sharedFavorites
# Conflicts:
#	gitadora@asphyxia/README.md
#	gitadora@asphyxia/handlers/profiles.ts
2022-05-17 09:21:32 +02:00
thomeval
1649ab9bd9
Merge branch 'asphyxia-core:stable' into stable 2022-05-16 16:12:18 +02:00
thomeval
ea00a2330e
Merge pull request #3 from thomeval/bug/00_defaultNoteSpeed
Fixed note scroll speed defaulting to 0.5x
2022-05-16 13:51:58 +02:00
Thome Valentin
1b6ab0085f Fixed note scroll speed defaulting to 0.5x for newly registered profiles.
Introduced several new interfaces, and refactored code to cleanup profiles.ts
2022-05-16 13:50:18 +02:00
Thome Valentin
4a8e0707f0 Added experimental 'Shared Favorite Songs' option. 2022-05-16 06:47:59 +02:00
Freddie Wang
0bf3cc30fc
Merge pull request #44 from thomeval/stable
Fix loading MDB files in XML format
2022-05-13 18:46:31 +08:00
thomeval
90651282c0
Merge branch 'asphyxia-core:stable' into stable 2022-05-11 17:07:24 +02:00
Thome Valentin
f501bab164 Fixed bug preventing MDB files in XML format from loading correctly. 2022-05-11 17:06:03 +02:00
Freddie Wang
10ead0bdf4
Merge pull request #43 from thomeval/stable
Fixed plugin crash due to a missing import
2022-05-04 14:53:29 +08:00
Thome Valentin
3368953fbc Merge branch 'feature/00_betterMDB' into stable 2022-05-04 08:40:52 +02:00
Thome Valentin
ec4b59330d Fix missing import 2022-05-04 08:33:55 +02:00
Freddie Wang
1a2e955dd4
Merge pull request #42 from thomeval/feature/00_betterMDB
Feature/00 better mdb
2022-05-04 13:03:45 +08:00
Thome Valentin
a26b8db8df Updated readme 2022-05-04 07:00:51 +02:00
Thome Valentin
257c9962f5 Added several player profile stats to the web UI.
Refactored and cleaned up several functions.
MDB loader now logs the number of loaded songs available to GF and DM.
MDB: Fixed "is_secret" field being ignored (always set to false)
2022-05-04 06:58:17 +02:00
Freddie Wang
d0c9a7f918
Merge pull request #41 from thomeval/stable
Persist secret music and rewards
2022-05-01 22:43:40 +08:00
Thome Valentin
2fe270dbfa Merge branch 'stable' into feature/00_betterMDB 2022-05-01 12:55:54 +02:00
thomeval
67599e37da
Merge branch 'asphyxia-core:stable' into stable 2022-05-01 12:51:39 +02:00
thomeval
ad8a6b20a3
Merge pull request #2 from thomeval/feature/34_secretMusic
Feature/34 secret music
2022-05-01 12:51:00 +02:00
Thome Valentin
6c8585a258 Update readme file. 2022-05-01 12:50:14 +02:00
Thome Valentin
89be828ef8 Rewrite MDB loading code to support loading .xml, .json or .b64 files. This applies to the default MDB (determined by the version of the game requesting it), or custom MDB if that setting is enabled. To use a custom MDB, enable it in Web UI, then place a custom.json, custom.xml or custom.b64 file in the data/mdb subfolder. 2022-05-01 12:24:17 +02:00
Freddie Wang
d18edbcfa5
Merge pull request #40 from thomeval/stable
Fix server errors for two Guitar Freaks players, implement ranking and "Recommended to friends" songlist
2022-04-30 22:48:22 +08:00
Thome Valentin
5c612929e2 Secret music (unlocked songs) are now saved and loaded correctly. Partially fixes issue #34.
Rewards are now saved and loaded correctly. Partially fixes issue #34.
2022-04-30 07:00:28 +02:00
thomeval
b7377a3d0d
Merge pull request #1 from thomeval/bug/39_twoPlayerSave
Bug/39 two player save
2022-04-28 13:58:05 +02:00
Thome Valentin
1771619a3c Removed information.info field (seems to be unused by the game) 2022-04-28 13:55:36 +02:00
Thome Valentin
b104e40fac Fixed all_skill ranking always being set to 0.
Moved "samples" folder to "apisamples"
2022-04-28 11:14:43 +02:00
Thome Valentin
ce10dca416 Revert global gitignore 2022-04-28 10:23:52 +02:00
Thome Valentin
2929045847 - Fixed server error when saving profiles for two Guitar Freaks players at the end of a session. Fixes issue #39.
- Fixed another server error when two players are present, but only one player is using a profile.
- Added support for the "ranking" field. Gitadora will now correctly display your server ranking (based on Skill) on the post-game screen.
- Added logging for profile loading/saving when Asphyxia is running in dev mode.
- Added more logging to mdb (song database) loading.
- "Recommended to friends" songs are now saved and loaded correctly. Since you don't have any friends, this won't be terribly useful, but it does at least provide an extra five slots for saving your favourite songs.
- Fixed "Recommended to friends" song list being incorrectly initialized to "I think about you".
- Removed some unneeded duplicate code.
- Latest getPlayer() and savePlayers() API requests and responses are now saved to file when Asphyxia is in dev mode. Useful for debugging.

NOTE: The above has only been tested on Gitadora Exchain.
2022-04-28 10:18:16 +02:00
Freddie Wang
0773375713
Merge pull request #38 from dannylin0711/stable
Fix something
2022-04-05 20:47:15 +08:00
dannylin0711
369297131f fix 4 2022-04-04 17:25:32 +08:00
dannylin0711
9448915ab7 fix 3 2022-04-04 17:12:05 +08:00
LatoWolf
5730dfa544
fix 2 2022-04-04 16:54:22 +08:00
LatoWolf
09b8e5f106
idiot fix 2022-04-04 16:52:40 +08:00
Freddie Wang
6d572cf737
Merge pull request #37 from dannylin0711/stable
Exceed Gear y-1 support
2022-04-03 14:19:25 +08:00
dannylin0711
de6e89e3f0 Exceed Gear y-1 2022-04-03 13:13:16 +08:00
Freddie Wang
ea15d4628d
Merge pull request #32 from cracrayol/stable
Add MGA plugin + Fix for PnM plugin
2021-06-19 22:03:10 +08:00
cracrayol
4184cfbd25 Update PnM README. 2021-06-19 16:01:57 +02:00
cracrayol
a1b884dcd6 Merge branch 'mga' into stable 2021-06-19 14:02:52 +02:00
cracrayol
e4a247a715 MGA: Update README 2021-06-19 13:53:56 +02:00
cracrayol
3e52594f34 Merge branch 'stable' of https://github.com/cracrayol/plugins into stable 2021-06-19 12:17:48 +02:00
cracrayol
3bf7448e2c All: Send 0 if clear_type is not existing. 2021-06-19 12:17:24 +02:00
cracrayol
64c02a0dd3 MGA : Add comments 2021-06-19 10:33:39 +02:00
cracrayol
eff4de9134 Initial support for MGA 2021-06-16 00:41:02 +02:00
Freddie Wang
5e497b7f15
Merge pull request #30 from Kirito3481/ddr
Release Dance Dance Revolution Plugin
2021-06-07 16:32:10 +08:00
Kirito
34f506c3ce Support Dance Dance Revolution A, A20 2021-06-07 17:29:08 +09:00
Freddie Wang
732d5ee6dc
Merge pull request #29 from DitFranXX/fix-version-check
fix: 🐛 Fix if major version bump up to 2.0
2021-05-31 14:10:01 +08:00
DitFranXX
05f6c2e13c fix: 🐛 Fix if major version bump up to 2.0 2021-05-31 15:07:18 +09:00
Freddie Wang
7f8e3989ea
Merge pull request #28 from DitFranXX/gitadora-somethingnew
Gitadora v1.1.1
2021-05-28 10:54:10 +08:00
DitFranXX
3a8863b452 feat: 🔊 Update logger on musiclist for more readable. 2021-05-28 01:00:45 +09:00
DitFranXX
04c84d55f7 feat: 🔊 Support logger for more level 2021-05-28 00:58:01 +09:00
DitFranXX
f49179a416 chore: 🔖 Release GITADORA Plugin v1.1.1 2021-05-28 00:51:12 +09:00
DitFranXX
edc6b09a33 fix: 🐛 Trying to fix ex's bg event bug after nt support 2021-05-26 19:52:27 +09:00
DitFranXX
860e2c8c7b feat: 🔊 Logger update 2021-05-26 13:07:33 +09:00
Freddie Wang
a8759b3a4e
Merge pull request #21 from DitFranXX/gitadora-nextage
Gitadora NEX+AGE support
2021-05-25 20:23:17 +08:00
DitFranXX
bfec542a6d Update README.md
version tag add/correction
remove db migration notice as fixed on core side.
2021-05-25 21:20:23 +09:00
DitFranXX
8c79ec3739 Known issues. 2021-05-25 21:12:22 +09:00
DitFranXX
d290608305 Release the release version. :) Please squash. 2021-05-25 21:09:14 +09:00
Freddie Wang
058351255c
Merge pull request #27 from Kirito3481/jubeat
Support saucer fulfill
2021-05-25 12:17:44 +08:00
Kirito
105c9796bd saucer fulfill support 2021-05-25 13:07:44 +09:00
Freddie Wang
06d9cb58fb
Merge pull request #26 from Kirito3481/jubeat
Matching Support (Experimental)
2021-05-25 12:05:20 +08:00
Kirito
c18baaedd6 saucer support 2021-05-22 13:57:07 +09:00
Kirito
397144a2a9 Matching Support (Experimental) 2021-05-20 10:51:49 +09:00
Freddie Wang
d9b3051839
Merge pull request #25 from cracrayol/stable
Pop'n Plugin v2.2.1
2021-05-18 00:20:24 +08:00
cracrayol
6b158087b5 Usaneko/Peace : Add Omnimix support 2021-05-16 22:14:21 +02:00
Kirito
d457186980 Merge branch 'asphyxia-core:stable' into stable 2021-05-16 04:57:27 +09:00
cracrayol
5b0cfb25b2 Update README 2021-05-13 20:14:27 +02:00
cracrayol
f25590a1ee Fix score conversion code 2021-05-13 20:00:13 +02:00
DitFranXX
1a9f32916c Encore version config. 2021-05-11 22:54:37 +09:00
DitFranXX
b82ff40c7f Dummy support trbitem (testing) 2021-05-10 12:20:38 +09:00
DitFranXX
65ffaa5417 Resturcture bit 2021-05-10 12:09:37 +09:00
cracrayol
764f72e5d0 Set g_pm_id with friendId
Add generation of friendId for all versions
2021-05-06 22:44:12 +02:00
cracrayol
72aef41d0e Tune Street: save player customization 2021-05-05 18:33:26 +02:00
cracrayol
b7c7be2ef3 Merge remote-tracking branch 'upstream/stable' into stable 2021-05-04 12:52:58 +02:00
cracrayol
db8ace3dbe Pop'n Music plugin v2.2.0
Tune Street : Add Town Mode + enable Net Taisen
2021-05-04 12:49:55 +02:00
Freddie Wang
18b82eb86e
Update README.md 2021-05-02 19:54:02 +08:00
DitFranXX
530c83d7f5 matixx premium encore support 2021-05-02 14:36:03 +09:00
DitFranXX
fd5161199e NT special premium encore support test 2021-04-30 19:12:11 +09:00
DitFranXX
f3835a53b9 fix bug and encore(extra) stage info temp impl. 2021-04-30 01:57:20 +09:00
Freddie Wang
58743f618c
Merge pull request #23 from DitFranXX/patch-1
Fix TS2737 on SDVX plugin
2021-04-29 18:08:07 +08:00
DitFranXX
55008b575c
Fix TS2737 on SDVX plugin
TS2737: BigInt literals are not available when targeting lower than ES2020.
2021-04-28 13:27:58 +09:00
Kirito
256fe48701 copious support 2021-04-27 22:10:24 +09:00
DitFranXX
819561337e fix mattix and use less dupe codes 2021-04-27 13:00:11 +09:00
DitFranXX
91f772d309 initailize migration helper 2021-04-26 20:03:35 +09:00
DitFranXX
75b6cd9464 dummy highvoltage support(not work) 2021-04-26 19:51:44 +09:00
cracrayol
d94b4ea1bb Fantasia : fix rival scores 2021-04-26 10:42:52 +02:00
DitFranXX
79311d686c Simplfiy 2021-04-26 17:20:19 +09:00
Kirito
8d0f882822 Fix jubeat profile 2021-04-26 17:06:56 +09:00
DitFranXX
fbebd9eac8 Nextage Beta Release 2021-04-26 07:09:18 +09:00
DitFranXX
18f05da297 Fix bootup on login due to missing value
and fix some version correction on save load.
still left to work on save system
2021-04-26 06:33:46 +09:00
DitFranXX
3c941c3b3e Use b64 as a data 2021-04-26 03:21:13 +09:00
DitFranXX
770ac0c66b Very basic nextage support (for now). 2021-04-26 02:51:22 +09:00
Freddie Wang
5797c2ca97
Merge pull request #19 from Kirito3481/stable
Add jubeat plugin
2021-04-25 16:50:27 +08:00
Freddie Wang
80a29aed17
Merge pull request #20 from RoxCian/bst-support
Fix a webui bug about name saving for BeatStream Animtribe
2021-04-25 16:49:57 +08:00
RoxCian
ee92f52b59 Now the player name can be saved via webui (BeatStream) 2021-04-25 16:32:41 +08:00
Rox Cian
c3ad4e8c62
Merge pull request #3 from asphyxia-core/stable
Update fork: bst-support
2021-04-25 16:21:55 +08:00
Kirito
f96aecf53d jubeat knit support 2021-04-24 18:10:06 +09:00
Freddie Wang
a78917c06a
Merge pull request #17 from Kirito3481/stable
Booth Support
2021-04-21 01:03:25 +08:00
Freddie Wang
b58b69886b
Merge pull request #18 from cracrayol/stable
Pop'n Music rivals support
2021-04-21 01:03:10 +08:00
Kirito
d1eb39e944 Booth support 2021-04-20 22:46:02 +09:00
Kirito
0c925ca0e0 Implement eventlog.write 2021-04-20 13:12:38 +09:00
cracrayol
086e055455 Merge remote-tracking branch 'upstream/stable' into stable 2021-04-19 23:17:10 +02:00
cracrayol
b9257ce08a Fix loading of rivals scores 2021-04-19 23:09:55 +02:00
cracrayol
84113ca2e2 Add rivals support for fantasia and sunny park
Add comments
2021-04-17 00:13:02 +02:00
cracrayol
af7128e327 Fix addRival refid check 2021-04-15 23:47:00 +02:00
cracrayol
c3119b6e4b Add rivals support for Lapistoria-peace
Fix stamp not properly initialized on usaneko
2021-04-15 23:45:06 +02:00
Freddie Wang
b90427387d
Merge pull request #16 from cracrayol/stable
Updated Pop'n Music plugin
2021-04-15 17:50:44 +08:00
cracrayol
efb28ad9a0 Fantasia: Fix player card best_music 2021-04-12 10:32:50 +02:00
cracrayol
abd4525129 Add most played songs 2021-04-11 15:35:56 +02:00
cracrayol
bc93c8603e Tune street: Fix profile name not displayed 2021-04-10 13:47:18 +02:00
cracrayol
ad677b4c2b Pop'n Music plugin v2.0.0
* Big rewrite/reorganization of the code
* Add support for Tune Street, fantasia, Sunny Park, Lapistoria
* Add automatic convertion from plugin v1.x data to v2.x
* Enable/disable score sharing between versions
* Various fixes
2021-04-09 23:08:37 +02:00
cracrayol
718ded19cf Merge remote-tracking branch 'upstream/stable' into stable 2021-04-09 23:06:06 +02:00
Freddie Wang
716473277a
Merge pull request #15 from RoxCian/bst-support
BeatStream support
2021-04-06 11:00:26 +08:00
Freddie Wang
6521aef4d8
Merge pull request #14 from dannylin0711/stable
Update for Blaster Pass
2021-04-06 11:00:14 +08:00
RoxCian
e64542f233 Fix saving score incorrectly while the first play for BeatStream. 2021-04-02 02:15:48 +08:00
RoxCian
c2e321e298 Add webui for BeatStream. 2021-04-01 12:54:54 +08:00
RoxCian
bfdeca09fd Initial BeatStream support. 2021-03-30 18:55:18 +08:00
dannylin0711
be159cef77 Fix Blaster Pass time, Fix VW appeal cards not appearing 2021-03-12 20:22:42 +08:00
dannylin0711
8144150de9 Update for Blaster Pass
Time is broken.
2021-03-06 00:02:37 +08:00
Freddie Wang
d967adfb4a
Merge pull request #13 from dannylin0711/stable
Update Skill Analyzer to MAYHEM Course(Newest)
2021-03-05 22:52:38 +08:00
dannylin0711
6c5d1a055e Update Skill Analyzer to MAYHEM Course(Newest) 2021-03-05 22:50:46 +08:00
Freddie Wang
3e60ef7030
Merge pull request #12 from dannylin0711/stable
Update for hexa diver,and skill analyzer to 666 course
2021-03-01 17:13:24 +08:00
dannylin0711
1b9dcce6bf Update for hexa diver,and skill analyzer to 666 course 2021-03-01 17:10:47 +08:00
dannylin0711
6f9604a198 Preparation for vvd final 2021-02-21 01:12:23 +08:00
LatoWolf
d95b93077c
Merge pull request #1 from asphyxia-core/stable
Follow up
2021-02-21 01:06:15 +08:00
Freddie Wang
8f4282e711
Merge pull request #10 from DitFranXX/nostalgia
Nostalgia(First version) support
2020-12-26 15:13:38 +08:00
DitFranXX
546175f1af WebUI for fix login issue about last index 2020-12-26 16:11:18 +09:00
DitFranXX
b7bb38f626 Update Readme, Support First fully with mdb. 2020-12-26 15:49:18 +09:00
DitFranXX
f63ee8aae8 Make sure that do not pass invaild value to client 2020-12-26 15:16:23 +09:00
DitFranXX
7ab416c917 Initial First version of Nostalgia support. 2020-12-26 14:33:16 +09:00
Freddie Wang
7a97396340
Update common.ts 2020-12-22 17:34:25 +08:00
Freddie Wang
0312d7dcf2
Merge pull request #6 from DitFranXX/nostalgia
Nostalgia Forte Support
2020-12-22 17:14:40 +08:00
DitFranXX
9834625a64 update readme 2020-12-22 17:56:20 +09:00
DitFranXX
3bd785b0ea Move to base64 encoded data 2020-12-22 17:55:12 +09:00
DitFranXX
df715e7ad2 Simplifiy the forte codes. 2020-12-22 16:27:04 +09:00
cracrayol
a31def74ad Update readme 2020-12-19 02:22:56 +01:00
cracrayol
5a4db13a88 Add change name, enable/disable popn 25 event
Disable NET Taisen
2020-12-19 02:22:17 +01:00
cracrayol
7c776accf4 Merge branch 'stable' of github.com:cracrayol/plugins into stable 2020-12-19 02:20:59 +01:00
Freddie Wang
805f5d0e18
Merge pull request #8 from cracrayol/stable
Updated pnm plugin. Set 23/24/25 to latest phase.
2020-12-16 12:29:34 +08:00
cracrayol
7007c6781d Update Readme 2020-12-15 22:31:14 +01:00
cracrayol
f72f723b17 Set 23/24/25 to latest phase 2020-12-15 22:31:14 +01:00
cracrayol
5a0592af6d Update Readme 2020-12-15 22:24:55 +01:00
cracrayol
b3e9c1163e Set 23/24/25 to latest phase 2020-12-15 20:03:50 +01:00
Freddie Wang
599452d146
Merge pull request #7 from cracrayol/stable
Add pop'n music 23/24 plugin
2020-12-13 19:41:25 +08:00
cracrayol
d36975339b Update README 2020-12-13 12:39:52 +01:00
cracrayol
0ba2df1a43 Update README 2020-12-13 12:34:50 +01:00
cracrayol
7fcd3024c1 Add simple import of data from non-core asphyxia 2020-12-12 23:44:01 +01:00
cracrayol
10207c1044 Add pnm 23/24 plugin 2020-12-12 15:09:57 +01:00
DitFranXX
f1878c09a6 Fix forteNumericHandler that not follow isForte. 2020-12-12 15:15:59 +09:00
DitFranXX
124ad7087b fix typo 2020-12-12 03:48:40 +09:00
DitFranXX
707b6cb3e3 Formatting, And Update ReadMe 2020-12-12 03:45:38 +09:00
DitFranXX
ad54bc70da Fix Forte Detection, Set ParseInt radix to 10. 2020-12-12 03:29:32 +09:00
DitFranXX
c17ee88eff Support Forte in Player Routes
Add support for Cat Progress(Stair)
Add/Fix some fields that Forte needs.
Add Missing RouteHandler that causing massive reqs.
And Fix saving issue too.

This is not tested on Op.2.
Should test it between games.
2020-12-12 03:04:57 +09:00
DitFranXX
82439eb1fb Initial MDB Parser for Forte support. 2020-12-11 22:37:21 +09:00
DitFranXX
cf15744a8b Forte Support Draft 2020-12-11 18:09:53 +09:00
Freddie Wang
01016aef8f
Update README.md 2020-12-11 16:59:23 +08:00
Freddie Wang
2b2d5e16e2
Merge pull request #2 from DitFranXX/gitadora
Initial Release for Gitadora Extension
2020-12-11 16:57:51 +08:00
DitFranXX
8db9d98b2b Use newer API for Custom MDB, Fix profile bug. 2020-12-11 17:48:27 +09:00
DitFranXX
5d52767f0a Fix duplicated and simplify scores & related codes
Actually not tested for now. will be soon
2020-12-11 16:48:03 +09:00
DitFranXX
12bcc0f6de Fix mistake. 2020-12-11 16:48:03 +09:00
DitFranXX
77072fb0b5 Avoid more $.content 2020-12-11 16:48:03 +09:00
DitFranXX
3e43c2196c Avoid to use $.content, Changes on WebUI
* Fix mistake that i was put webui handler on models folder
* Cahnge WebUI name.
2020-12-11 16:48:03 +09:00
DitFranXX
e98db72efa Use JSON.parse instead of resolveJsonModule.
And fix typo.
2020-12-11 16:48:03 +09:00
DitFranXX
43e0716b85 Initial Release for Gitadora Extension
* Converted from Asphyxia-Public-Route
* Enable `resolveJsonModule` in `tsconfig.json`
* WebUI for change name and title.
2020-12-11 16:48:02 +09:00
Freddie Wang
ece1f7b7b8
Merge pull request #5 from DitFranXX/museca
Museca Support
2020-12-10 22:26:51 +08:00
DitFranXX
2237356d4a Use new method to determine required version.
v1.19 is buggy that i can't run it btw...
2020-12-10 20:17:13 +09:00
DitFranXX
a8f5b3f082 Use newer API for custom MDB. Fix Grafica issue. 2020-12-10 19:35:46 +09:00
DitFranXX
3d3247a98b More information about Community-System 2020-12-10 19:34:48 +09:00
DitFranXX
178dfa89f8 Fix bug with event list. 2020-12-10 19:34:48 +09:00
DitFranXX
64e3d1871e types for better debug. 2020-12-10 19:34:47 +09:00
DitFranXX
9cf950c759 Museca 1+1/2 and Museca Plus 2020-12-10 19:34:47 +09:00
Freddie Wang
aefac94c05
Merge pull request #3 from DitFranXX/nostalgia
Initial Nostalgia Support.
2020-12-10 16:26:13 +08:00
Freddie Wang
37b4284fe4
Merge pull request #4 from DitFranXX/hello-popn
Add support for `Hello! Pop'n Music`.
2020-12-10 16:24:55 +08:00
Freddie Wang
f5eb982aed
Update README.md 2020-12-10 16:23:00 +08:00
Freddie Wang
1d82fafdcd remove files shipped with core 2020-12-10 16:20:39 +08:00
DitFranXX
13aaaca1b2 Add support for Hello! Pop'n Music. 2020-11-27 04:12:29 +09:00
DitFranXX
2f59174f18 Initial Nostalgia Support. 2020-11-26 02:38:07 +09:00
Freddie Wang
7e52d72c26
Merge pull request #1 from dannylin0711/stable
Update Automation Paradise song list
2020-06-13 13:05:30 +01:00
dannylin0711
8db526fd90
Update Automation Paradise song list
Update Automation Paradise song list for 20200115
2020-06-13 20:04:14 +08:00
518 changed files with 120833 additions and 8109 deletions

11
.gitignore vendored
View File

@ -1,4 +1,13 @@
# Editor configs
.vscode
.idea
# External modules
node_modules
package-lock.json
# Project files
asphyxia-core.d.ts
package.json
package-lock.json
typedoc.json
tsconfig.json

View File

@ -16,8 +16,11 @@ I don't actually follow any coding rules for this jank so neither should you. Th
I'll do my best to merge PR, but please make sure you are submitting code targeted for "public" releases. (Unless it is some ancient rare stuff and you feel generous enough to provide support for it)
- For new plugins: please use `@asphyxia` identifier for your plugin since you are submitting code as the community.
- This way we prevent third-party plugins (e.g. `popn` or `popn@someoneelse`) from conflicting with our database.
- For existing plugins: please inlude a changelog in your PR so it is easier for me to tell what it is for.
## How do I make plugins?
Checkout our [Documentation](https://asphyxia-core.github.io/typedoc/) and maybe consider join our [Discord](https://discord.gg/3TW3BDm) server. Make sure to familiar yourself with at least XML and Typescript/Javascript.
Note that you should run `npm install` to install typing for node and lodash, and launch CORE using `--dev` arguments to enable console log and typechecking when using typescript.

1115
asphyxia-core.d.ts vendored

File diff suppressed because it is too large Load Diff

9
bst@asphyxia/README.md Normal file
View File

@ -0,0 +1,9 @@
# BeatStream
Plugin Version: **v1.0.2**
Supported Versions:
- BeatStream アニムトライヴ
- Back end ✔
- Web UI ✔

View File

@ -0,0 +1,247 @@
import { Bst2EventParamsMap, getKEventControl } from "../../models/bst2/event_params"
import { Bst2AccountMap, Bst2BiscoMap, Bst2CourseMap, Bst2MusicRecordMap, Bst2PlayerMap, Bst2SurveyMap, Bst2TipsMap, Bst2UnlockingInfoMap, IBst2Account, IBst2Base, IBst2Bisco, IBst2Course, IBst2CrysisLog, IBst2Customization, IBst2Hacker, IBst2MusicRecord, IBst2Player, IBst2Survey, IBst2Tips, IBst2UnlockingInfo } from "../../models/bst2/profile"
import { Bst2CourseLogMap, Bst2StageLogMap, IBst2StageLog } from "../../models/bst2/stagelog"
import { bacK, BigIntProxy, boolme, fromMap, mapK, s16me, s32me, s8me, strme, toBigInt } from "../../utility/mapping"
import { isToday } from "../../utility/utility_functions"
import { DBM } from "../utility/db_manager"
import { readPlayerPostProcess, writePlayerPreProcess } from "./processing"
export namespace Bst2HandlersCommon {
export const Common: EPR = async (_0, _1, send) => await send.object({ event_ctrl: { data: getKEventControl() } })
export const BootPcb: EPR = async (_0, _1, send) => await send.object({ sinfo: { nm: K.ITEM("str", "Asphyxia"), cl_enbl: K.ITEM("bool", 1), cl_h: K.ITEM("u8", 0), cl_m: K.ITEM("u8", 0) } })
export const StartPlayer: EPR = async (_, data, send) => {
let params = fromMap(Bst2EventParamsMap)
let rid = $(data).str("rid")
let account = DB.FindOne<IBst2Account>(rid, { collection: "bst.bst2.player.account" })
if (account == null) params.playerId = -1
params.startTime = BigInt(Date.now())
send.object(mapK(params, Bst2EventParamsMap))
}
export const PlayerSucceeded: EPR = async (_, data, send) => {
let rid = $(data).str("rid")
let account: IBst2Account = await DB.FindOne<IBst2Account>(rid, { collection: "bst.bst2.player.account" })
let result
if (account == null) {
result = {
play: false,
data: { name: "" },
record: {},
hacker: {},
phantom: {}
}
} else {
let base: IBst2Base = await DB.FindOne<IBst2Base>(rid, { collection: "bst.bst2.player.base" })
let records: IBst2MusicRecord[] = await DB.Find<IBst2MusicRecord>({ collection: "bst.bst2.playData.musicRecord#userId", userId: account.userId })
result = {
play: true,
data: { name: base.name },
record: {},
hacker: {},
phantom: {}
}
}
send.object(mapK(result, {
play: boolme(),
data: { name: strme() },
record: {},
hacker: {},
phantom: {}
}))
}
export const ReadPlayer: EPR = async (_, data, send) => {
let refid = $(data).str("rid")
let account = await DB.FindOne<IBst2Account>(refid, { collection: "bst.bst2.player.account" })
if (account == null) return await send.deny()
let base = await DB.FindOne<IBst2Base>(refid, { collection: "bst.bst2.player.base" })
let survey = await DB.FindOne<IBst2Survey>(refid, { collection: "bst.bst2.player.survey" }) || fromMap(Bst2SurveyMap)
let unlocking = await DB.Find<IBst2UnlockingInfo>(refid, { collection: "bst.bst2.player.unlockingInfo" })
let customize = await DB.FindOne<IBst2Customization>(refid, { collection: "bst.bst2.player.customization" })
let tips = await DB.FindOne<IBst2Tips>(refid, { collection: "bst.bst2.player.tips" }) || fromMap(Bst2TipsMap)
let hacker = await DB.Find<IBst2Hacker>(refid, { collection: "bst.bst2.player.hacker" })
let crysis = await DB.Find<IBst2CrysisLog>(refid, { collection: "bst.bst2.player.event.crysis" })
let bisco = await DB.FindOne<IBst2Bisco>(refid, { collection: "bst.bst2.player.bisco" }) || fromMap(Bst2BiscoMap)
let records = await DB.Find<IBst2MusicRecord>({ collection: "bst.bst2.playData.musicRecord#userId", userId: account.userId })
let courses = await DB.Find<IBst2Course>({ collection: "bst.bst2.playData.course#userId", userId: account.userId })
account.previousStartTime = account.standardTime
account.standardTime = BigInt(Date.now())
account.ea = true
account.intrvld = 0
account.playCount++
account.playCountToday++
let eventPlayLog: { crysis?: IBst2CrysisLog[] } = {}
if (crysis.length != 0) eventPlayLog.crysis = crysis
let player: IBst2Player = {
pdata: {
account: account,
base: base,
survey: survey,
opened: {},
item: (unlocking.length == 0) ? {} : { info: unlocking },
customize: customize,
tips: tips,
hacker: (hacker.length == 0) ? {} : { info: hacker },
playLog: eventPlayLog,
bisco: { pinfo: bisco },
record: (records.length == 0) ? {} : { rec: records },
course: (courses.length == 0) ? {} : { record: courses }
}
}
send.object(readPlayerPostProcess(mapK(player, Bst2PlayerMap)))
}
export const WritePlayer: EPR = async (_, data, send) => {
let player = bacK(writePlayerPreProcess(data), Bst2PlayerMap).data
let refid = player.pdata.account.refid
let userId = player.pdata.account.userId
let now = BigIntProxy(BigInt(Date.now()))
let opm = new DBM.DBOperationManager()
let oldAccount = await DB.FindOne<IBst2Account>(refid, { collection: "bst.bst2.player.account" })
if (!oldAccount) {
do {
userId = Math.round(Math.random() * 99999999)
} while ((await DB.Find<IBst2Account>(null, { collection: "bst.bst2.player.account", userId: userId })).length > 0)
oldAccount = fromMap(Bst2AccountMap)
oldAccount.userId = userId
} else {
oldAccount.playCount++
if (!isToday(toBigInt(oldAccount.standardTime))) {
oldAccount.dayCount++
oldAccount.playCountToday = 1
} else oldAccount.playCountToday++
}
oldAccount.standardTime = BigIntProxy(BigInt(Date.now()))
opm.upsert<IBst2Account>(refid, { collection: "bst.bst2.player.account" }, oldAccount)
if (player.pdata.base) opm.upsert<IBst2Base>(refid, { collection: "bst.bst2.player.base" }, player.pdata.base)
if (player.pdata.item?.info?.length > 0) for (let u of player.pdata.item.info) opm.upsert<IBst2UnlockingInfo>(refid, { collection: "bst.bst2.player.unlockingInfo", type: u.type, id: u.id }, u)
if (player.pdata.customize) opm.upsert<IBst2Customization>(refid, { collection: "bst.bst2.player.customization" }, player.pdata.customize)
if (player.pdata.tips) opm.upsert<IBst2Base>(refid, { collection: "bst.bst2.player.base" }, player.pdata.base)
if (player.pdata.hacker?.info?.length > 0) for (let h of player.pdata.hacker.info) {
h.updateTime = now
opm.upsert<IBst2Hacker>(refid, { collection: "bst.bst2.player.hacker", id: h.id }, h)
}
if (player.pdata.playLog?.crysis?.length > 0) for (let c of player.pdata.playLog.crysis) opm.upsert<IBst2CrysisLog>(refid, { collection: "bst.bst2.player.event.crysis", id: c.id, stageId: c.stageId }, c)
await DBM.operate(opm)
send.object({ uid: K.ITEM("s32", oldAccount.userId) })
}
export const WriteStageLog: EPR = async (_, data, send) => {
await updateRecordFromStageLog(bacK(data, Bst2StageLogMap).data, false)
send.success()
}
export const WriteCourseStageLog: EPR = async (_, data, send) => {
await updateRecordFromStageLog(bacK(data, Bst2StageLogMap).data, true)
send.success()
}
async function updateRecordFromStageLog(stageLog: IBst2StageLog, isCourseStage: boolean) {
let query: Query<IBst2MusicRecord> = { collection: "bst.bst2.playData.musicRecord#userId", userId: stageLog.userId, musicId: stageLog.musicId, chart: stageLog.chart }
let oldRecord = await DB.FindOne<IBst2MusicRecord>(query)
let time = Date.now()
stageLog.time = time
stageLog.isCourseStage = isCourseStage
if (oldRecord == null) {
oldRecord = fromMap(Bst2MusicRecordMap)
oldRecord.musicId = stageLog.musicId
oldRecord.chart = stageLog.chart
oldRecord.clearCount = (stageLog.medal >= 3) ? 1 : 0
oldRecord.score = stageLog.score
oldRecord.grade = stageLog.grade
oldRecord.gaugeTimes10 = stageLog.gaugeTimes10
oldRecord.playCount = 1
oldRecord.medal = stageLog.medal
oldRecord.combo = stageLog.combo
oldRecord.lastPlayTime = time
oldRecord.updateTime = time
oldRecord.userId = stageLog.userId
} else {
if (stageLog.medal >= 3) oldRecord.clearCount++
if (oldRecord.score < stageLog.score) {
oldRecord.updateTime = time
oldRecord.score = stageLog.score
}
if (oldRecord.grade < stageLog.grade) {
oldRecord.updateTime = time
oldRecord.grade = stageLog.grade
}
if (oldRecord.gaugeTimes10 < stageLog.gaugeTimes10) {
oldRecord.updateTime = time
oldRecord.gaugeTimes10 = stageLog.gaugeTimes10
}
if (oldRecord.medal < stageLog.medal) {
oldRecord.updateTime = time
oldRecord.medal = stageLog.medal
}
if (oldRecord.combo < stageLog.combo) {
oldRecord.updateTime = time
oldRecord.combo = stageLog.combo
}
oldRecord.lastPlayTime = time
oldRecord.playCount++
}
DBM.upsert(null, query, oldRecord)
DBM.insert(null, stageLog)
}
export const WriteCourseLog: EPR = async (_, data, send) => {
let courseLog = bacK(data, Bst2CourseLogMap).data
let query: Query<IBst2Course> = { collection: "bst.bst2.playData.course#userId", userId: courseLog.userId, courseId: courseLog.courseId }
let oldRecord = await DB.FindOne<IBst2Course>(query)
let time = Date.now()
courseLog.time = time
if (oldRecord == null) {
oldRecord = fromMap(Bst2CourseMap)
oldRecord.courseId = courseLog.courseId
oldRecord.score = courseLog.score
oldRecord.grade = courseLog.grade
oldRecord.gauge = courseLog.gauge
oldRecord.playCount = 1
oldRecord.medal = courseLog.medal
oldRecord.combo = courseLog.combo
oldRecord.lastPlayTime = time
oldRecord.updateTime = time
oldRecord.userId = courseLog.userId
} else {
if (oldRecord.score < courseLog.score) {
oldRecord.updateTime = time
oldRecord.score = courseLog.score
}
if (oldRecord.grade < courseLog.grade) {
oldRecord.updateTime = time
oldRecord.grade = courseLog.grade
}
if (oldRecord.gauge < courseLog.gauge) {
oldRecord.updateTime = time
oldRecord.gauge = courseLog.gauge
}
if (oldRecord.medal < courseLog.medal) {
oldRecord.updateTime = time
oldRecord.medal = courseLog.medal
}
if (oldRecord.combo < courseLog.combo) {
oldRecord.updateTime = time
oldRecord.combo = courseLog.combo
}
oldRecord.lastPlayTime = time
oldRecord.playCount++
}
DBM.upsert(null, query, oldRecord)
DBM.insert(null, courseLog)
send.success()
}
}

View File

@ -0,0 +1,12 @@
import { IBst2Player } from "../../models/bst2/profile"
import { KITEM2 } from "../../utility/mapping"
import { toFullWidth, toHalfWidth } from "../../utility/utility_functions"
export function readPlayerPostProcess(player: KITEM2<IBst2Player>): KITEM2<IBst2Player> {
if (player.pdata.base?.name != null) player.pdata.base.name["@content"] = toFullWidth(player.pdata.base.name["@content"])
return player
}
export function writePlayerPreProcess(player: KITEM2<IBst2Player>): KITEM2<IBst2Player> {
if (player.pdata.base?.name != null) player.pdata.base.name["@content"] = toHalfWidth(player.pdata.base.name["@content"])
return player
}

View File

@ -0,0 +1,42 @@
import { IBst2Base, IBst2Customization } from "../../models/bst2/profile"
import { WebUIMessageType } from "../../models/utility/webui_message"
import { DBM } from "../utility/db_manager"
import { UtilityHandlersWebUI } from "../utility/webui"
export namespace Bst2HandlersWebUI {
export const UpdateSettings = async (data: {
refid: string
name: string
rippleNote: number
sfxNormalNote: number
sfxRippleNote: number
sfxSlashNote: number
sfxStreamNote: number
backgroundBrightness: number
judgeText: number
rippleNoteGuide: number
streamNoteGuide: number
sfxFine: number
sfxStreamNoteTail: number
}) => {
try {
let base = await DB.FindOne<IBst2Base>(data.refid, { collection: "bst.bst2.player.base" })
let customization = await DB.FindOne<IBst2Customization>(data.refid, { collection: "bst.bst2.player.customization" })
if (!customization || !base) throw new Error("No profile for refid=" + data.refid)
base.name = data.name
customization.custom[0] = data.rippleNote
customization.custom[2] = data.sfxNormalNote
customization.custom[3] = data.sfxRippleNote
customization.custom[4] = data.sfxSlashNote
customization.custom[5] = data.sfxStreamNote
customization.custom[6] = data.backgroundBrightness
customization.custom[7] = (data.judgeText << 0) | (data.rippleNoteGuide << 1) | (data.streamNoteGuide << 2) | (data.sfxStreamNoteTail << 3) | (data.sfxFine << 4)
customization.custom[9] = data.judgeText
DBM.update<IBst2Base>(data.refid, { collection: "bst.bst2.player.base" }, base)
DBM.update<IBst2Customization>(data.refid, { collection: "bst.bst2.player.customization" }, customization)
UtilityHandlersWebUI.pushMessage("Save BeatStream Animtribe settings succeeded!", 2, WebUIMessageType.success, data.refid)
} catch (e) {
UtilityHandlersWebUI.pushMessage("Error while save BeatStream Animtribe settings: " + e.message, 2, WebUIMessageType.error, data.refid)
}
}
}

View File

@ -0,0 +1,21 @@
import { IBatchResult } from "../../models/utility/batch"
import { IPluginVersion } from "../../models/utility/plugin_version"
import { isHigherVersion } from "../../utility/utility_functions"
import { DBM } from "./db_manager"
export namespace Batch {
let registeredBatch = <{ id: string, version: string, batch: () => Promise<any> }[]>[]
export async function execute(version: string): Promise<void> {
for (let b of registeredBatch) {
if ((await DB.Find<IBatchResult>({ collection: "bst.batchResult", batchId: b.id })).length == 0) if (!isHigherVersion(version, b.version)) {
await b.batch()
await DBM.insert<IBatchResult>(null, { collection: "bst.batchResult", batchId: b.id })
}
}
}
export function register(id: string, version: string, batch: () => Promise<any>) {
registeredBatch.push({ id: id, version: version, batch: batch })
}
}

View File

@ -0,0 +1,7 @@
import { Batch } from "./batch"
import { DBM } from "./db_manager"
import { bufferToBase64, log } from "../../utility/utility_functions"
export function initializeBatch() {
/* Register batch here **/
}

View File

@ -0,0 +1,17 @@
export namespace UtilityHandlersCommon {
export const WriteShopInfo: EPR = async (__, ___, send) => {
let result = {
sinfo: {
lid: K.ITEM("str", "ea"),
nm: K.ITEM("str", "Asphyxia shop"),
cntry: K.ITEM("str", "Japan"),
rgn: K.ITEM("str", "1"),
prf: K.ITEM("s16", 13),
cl_enbl: K.ITEM("bool", 0),
cl_h: K.ITEM("u8", 8),
cl_m: K.ITEM("u8", 0)
}
}
send.object(result)
}
}

View File

@ -0,0 +1,212 @@
import { ICollection } from "../../models/utility/definitions"
import { log } from "../../utility/utility_functions"
export namespace DBM {
export interface IDBCollectionName extends ICollection<"dbManager.collectionName"> {
name: string
}
export interface IDBOperation<T = any, TOperation extends "insert" | "update" | "upsert" | "remove" | "skip" = "insert" | "update" | "upsert" | "remove" | "skip"> {
refid?: string
query: TOperation extends "insert" ? null : Query<T>
operation: TOperation
doc: TOperation extends "remove" ? null : T | Doc<T>
isPublicDoc?: boolean
}
export class DBOperationManager {
public operations: IDBOperation[] = []
public push(...op: IDBOperation[]): void {
this.operations.push(...op)
}
public update<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true): void {
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
this.operations.push({ refid: refid, query: query, operation: "update", doc: data, isPublicDoc: isPublicDoc })
}
public upsert<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true): void {
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
this.operations.push({ refid: refid, query: query, operation: "upsert", doc: data, isPublicDoc: isPublicDoc })
}
public insert<T extends ICollection<any>>(refid: string | null, data: Doc<T>, isPublicDoc: boolean = true): void {
this.operations.push({ refid: refid, operation: "insert", query: null, doc: data, isPublicDoc: isPublicDoc })
}
public remove<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): void {
for (let o of this.operations) if (o.doc && DBOperationManager.isMatch(o.doc, query)) o.operation = "skip"
this.operations.push({ refid: refid, query: query, operation: "remove", doc: null, isPublicDoc: isPublicDoc })
}
public async findOne<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): Promise<T | Doc<T>> {
for (let i = this.operations.length - 1; i >= 0; i--) {
let o = this.operations[i]
if (o.doc == null) continue
if (DBOperationManager.isMatch(o.doc, query) && ((o.refid && refid) ? (o.refid == refid) : true)) return o.doc
}
return ((refid == null) && isPublicDoc) ? await DB.FindOne<T>(query) : await DB.FindOne<T>(refid, query)
}
public async find<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true): Promise<(T | Doc<T>)[]> {
let result: (T | Doc<T>)[] = []
for (let o of this.operations) {
if (o.doc == null) continue
if (DBOperationManager.isMatch(o.doc, query) && ((o.refid && refid) ? (o.refid == refid) : true)) result.push(o.doc)
}
return result.concat(await (((refid == null) && isPublicDoc) ? DB.Find<T>(query) : DB.Find<T>(refid, query)))
}
private static isMatch<T>(entry: T | Doc<T>, query: Query<T>): boolean {
if (entry == null) return query == null
if (query.$where && !query.$where.apply(entry)) return false
let $orResult = null
let skipKeys = ["$where", "_id"]
for (let qk in query) {
if (skipKeys.includes(qk)) continue
switch (qk) {
case "$or": {
if ($orResult == null) $orResult = false
for (let or of query.$or) if (this.isMatch(entry, or)) $orResult = true
break
}
case "$and": {
for (let and of query.$and) if (!this.isMatch(entry, and)) return false
break
}
case "$not": {
if (this.isMatch(entry, query.$not)) return false
break
}
default: {
let value = entry[qk]
let q = query[qk]
if (value == q) continue
if ((typeof q != "object") && (typeof q != "function")) return false
if ((q.$exists != null)) if ((q.$exists && (value == null)) || (!q.$exists && (value != null))) return false
if (Array.isArray(value)) {
if (q.$elemMatch && !this.isMatch(value, q.$elemMatch)) return false
if (q.$size && (value.length != q.$size)) return false
continue
} else if ((typeof value == "number") || (typeof value == "string")) {
if (q.$lt) if (value >= q.$lt) return false
if (q.$lte) if (value > q.$lte) return false
if (q.$gt) if (value <= q.$gt) return false
if (q.$gte) if (value < q.$gte) return false
if (q.$in) if (!value.toString().includes(q.$in)) return false
if (q.$nin) if (value.toString().includes(q.$nin)) return false
if (q.$ne) if (value == q.$ne) return false
if (q.$regex) if (value.toString().match(q.$regex).length == 0) return false
continue
} else if (typeof value == "object") {
if (!this.isMatch(value, q)) return false
continue
} else if (q != null) return false
}
}
}
return ($orResult == null) || $orResult
}
}
export async function getCollectionNames(filter?: string): Promise<IDBCollectionName[]> {
let result = await DB.Find<IDBCollectionName>({ collection: "dbManager.collectionName" })
if (filter != null) {
let filters = filter.split(",")
for (let i = 0; i < filter.length; i++) filters[i] = filters[i].trim()
let i = 0
while (i < result.length) {
let removeFlag = false
for (let f of filters) if (f.startsWith("!") ? !result[i].name.includes(f) : result[i].name.includes(f)) {
result.splice(i, 1)
removeFlag = true
break
}
if (!removeFlag) i++
}
}
return result
}
async function checkData<T extends ICollection<any>>(data: T): Promise<void> {
for (let k in data) if (k.startsWith("__")) delete data[k]
if (await DB.FindOne<IDBCollectionName>({ collection: "dbManager.collectionName", name: data.collection }) == null) {
await DB.Insert<IDBCollectionName>({ collection: "dbManager.collectionName", name: data.collection })
}
}
export async function update<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true) {
checkData(data)
if (refid == null) return isPublicDoc ? await DB.Update(query, data) : await DB.Update(null, query, data)
else return await DB.Update(refid, query, data)
}
export async function upsert<T extends ICollection<any>>(refid: string | null, query: Query<T>, data: Doc<T>, isPublicDoc: boolean = true) {
checkData(data)
if (refid == null) return isPublicDoc ? await DB.Upsert(query, data) : await DB.Upsert(null, query, data)
else return await DB.Upsert(refid, query, data)
}
export async function insert<T extends ICollection<any>>(refid: string | null, data: Doc<T>, isPublicDoc: boolean = true) {
checkData(data)
if (refid == null) return isPublicDoc ? await DB.Insert(data) : await DB.Insert(null, data)
else return await DB.Insert(refid, data)
}
export async function remove<T extends ICollection<any>>(refid: string | null, query: Query<T>, isPublicDoc: boolean = true) {
if (refid == null) return isPublicDoc ? await DB.Remove(query) : await DB.Remove(null, query)
else return await DB.Remove(refid, query)
}
export async function operate(operations: DBOperationManager) {
let result = []
for (let o of operations.operations) {
if (o.operation == "skip") continue
if (o.doc) delete o.doc._id
try {
switch (o.operation) {
case "insert":
result.push(await insert(o.refid, o.doc, o.isPublicDoc))
break
case "update":
result.push(await update(o.refid, o.query, o.doc, o.isPublicDoc))
break
case "upsert":
result.push(await upsert(o.refid, o.query, o.doc, o.isPublicDoc))
break
case "remove":
result.push(await remove(o.refid, o.query, o.isPublicDoc))
break
}
} catch (e) {
await log(new Date().toLocaleString() + " Error: " + (e as Error).message)
}
}
return result
}
export async function removeAllData(refid?: string, filter?: string) {
for (let c of await getCollectionNames(filter)) remove(refid, { collection: c.name })
if ((refid == null) && (filter == null)) remove(null, { collection: "dbManager.collectionName" })
}
export async function overall(refid: string, userId: number, filter: string, operation: "delete" | "export" | "override", data?: any) {
if (refid == null) return
try {
let collections = await DBM.getCollectionNames(filter)
let traverse = async (f: (rid: string | null, query: Query<ICollection<any>>) => Promise<any>) => {
let result = []
for (let c of collections) {
if (c.name.includes("#userId") && (userId != null)) result.concat(...await f(null, { collection: c.name, userId: userId }))
else result.concat(...await f(refid, { collection: c.name }))
}
return result
}
switch (operation) {
case "delete":
await traverse((rid, query) => DBM.remove(rid, query))
break
case "export":
let result = await traverse((rid, query) => DB.Find(rid, query))
return JSON.stringify(result)
case "override":
if (!Array.isArray(data)) return "The data may not be an Asphyxia CORE savedata."
await traverse((rid, query) => DBM.remove(rid, query))
for (let d of data) if ((typeof (d?.collection) == "string") && (!d.collection.includes(filter))) DB.Insert(d)
break
}
} catch (e) {
return e.message
}
return null
}
}

View File

@ -0,0 +1,15 @@
import { initializeBatch } from "./batch_initialize"
import { IPluginVersion } from "../../models/utility/plugin_version"
import { isHigherVersion } from "../../utility/utility_functions"
import { Batch } from "./batch"
import { DBM } from "./db_manager"
import { version } from "../../utility/about"
export async function initialize() {
let oldVersion = await DB.FindOne<IPluginVersion>({ collection: "bst.pluginVersion" })
if ((oldVersion == null) || isHigherVersion(oldVersion.version, version)) {
initializeBatch()
await Batch.execute(version)
await DBM.upsert<IPluginVersion>(null, { collection: "bst.pluginVersion" }, { collection: "bst.pluginVersion", version: version })
}
}

View File

@ -0,0 +1,12 @@
import { IWebUIMessage, WebUIMessageType } from "../../models/utility/webui_message"
import { DBM } from "./db_manager"
export namespace UtilityHandlersWebUI {
export function pushMessage(message: string, version: number, type: WebUIMessageType, rid?: string) {
DBM.upsert<IWebUIMessage>(null, { collection: "utility.webuiMessage" }, { collection: "utility.webuiMessage", message: message, type: type, refid: rid, version: version })
}
export const removeWebUIMessage = async () => {
await DBM.remove<IWebUIMessage>(null, { collection: "utility.webuiMessage" })
}
}

32
bst@asphyxia/index.ts Normal file
View File

@ -0,0 +1,32 @@
import { UtilityHandlersCommon } from "./handlers/utility/common"
import { UtilityHandlersWebUI } from "./handlers/utility/webui"
import { initialize } from "./handlers/utility/initialize"
import { Bst2HandlersCommon } from "./handlers/bst2/common"
import { Bst2HandlersWebUI } from "./handlers/bst2/webui"
export function register() {
R.GameCode("NBT")
routeBst2()
R.WebUIEvent("removeWebUIMessage", UtilityHandlersWebUI.removeWebUIMessage)
R.Unhandled()
initialize()
}
function routeBst2() {
R.Route("info2.common", Bst2HandlersCommon.Common)
R.Route("pcb2.boot", Bst2HandlersCommon.BootPcb)
R.Route("player2.start", Bst2HandlersCommon.StartPlayer)
R.Route("player2.continue", Bst2HandlersCommon.StartPlayer)
R.Route("player2.succeed", Bst2HandlersCommon.PlayerSucceeded)
R.Route("player2.read", Bst2HandlersCommon.ReadPlayer)
R.Route("player2.write", Bst2HandlersCommon.WritePlayer)
R.Route("player2.stagedata_write", Bst2HandlersCommon.WriteStageLog)
R.Route("player2.course_stage_data_write", Bst2HandlersCommon.WriteCourseStageLog)
R.Route("player2.course_data_write", Bst2HandlersCommon.WriteCourseLog)
R.WebUIEvent("bst2UpdateSettings", Bst2HandlersWebUI.UpdateSettings)
}

View File

@ -0,0 +1,47 @@
import { BigIntProxy, boolme, KITEM2, KM, s32me, u64me } from "../../utility/mapping"
export interface IFloorInfectionEventParams {
id: number
musicList: number
isCompleted: boolean
}
export const FloorInfectionEventParamsMap: KM<IFloorInfectionEventParams> = {
id: s32me("infection_id", 20),
musicList: s32me("music_list", 7),
isCompleted: boolme("is_complete", true)
}
export interface IBst2EventParams {
playerId: number
startTime: bigint | BigIntProxy
hasRbCollaboration: boolean
hasPopnCollaboration: boolean
floorInfection: { event: IFloorInfectionEventParams }
museca: { isPlayedMuseca: boolean }
}
export const Bst2EventParamsMap: KM<IBst2EventParams> = {
playerId: s32me("plyid"),
startTime: u64me("start_time"),
hasRbCollaboration: boolme("reflec_collabo", true),
hasPopnCollaboration: boolme("pop_collabo", true),
floorInfection: { event: FloorInfectionEventParamsMap, $targetKey: "floor_infection" },
museca: { isPlayedMuseca: boolme("is_play_museca", true) },
}
export interface IBst2EventControl {
type: number
phase: number
}
export const Bst2EventControlMap: KM<IBst2EventControl> = {
type: s32me(),
phase: s32me()
}
let kEventControl: KITEM2<IBst2EventControl>[]
export function getKEventControl(): KITEM2<IBst2EventControl>[] {
if (kEventControl == null) {
kEventControl = []
for (let i = 0; i <= 40; i++) for (let j = 0; j <= 25; j++) kEventControl.push(<any>{ type: K.ITEM("s32", i), phase: K.ITEM("s32", j) })
}
return kEventControl
}

View File

@ -0,0 +1,263 @@
import { BigIntProxy, boolme, colme, ignoreme, KM, s16me, s32me, s8me, strme, u16me, u64me, u8me } from "../../utility/mapping"
import { FixedSizeArray } from "../../utility/type"
import { ICollection } from "../utility/definitions"
export interface IBst2Account extends ICollection<"bst.bst2.player.account"> {
userId: number
isTakeOver: number
playerId: number
continueCount: number
playCount: number
playCountToday: number
crd: number
brd: number
dayCount: number
refid: string
lobbyId: string
mode: number
version: number
pp: boolean
ps: boolean
pay: number
payedPlayCount: number
standardTime: bigint | BigIntProxy
intrvld?: number
previousStartTime?: bigint | BigIntProxy
ea?: boolean
}
export const Bst2AccountMap: KM<IBst2Account> = {
collection: colme<IBst2Account>("bst.bst2.player.account"),
userId: s32me("usrid"),//
isTakeOver: s32me("is_takeover"),//
playerId: s32me("plyid"),
continueCount: s32me("continue_cnt"),
playCount: s32me("tpc"),//
playCountToday: s32me("dpc"),//
crd: s32me(),//
brd: s32me(),//
dayCount: s32me("tdc"),//
refid: strme("rid"),
lobbyId: strme("lid", "Asphyxia"),
mode: u8me(null, 2),
version: s16me("ver"),//
pp: boolme(),
ps: boolme(),
pay: s16me(),
payedPlayCount: s16me("pay_pc"),
standardTime: u64me("st", BigInt(Date.now())),//
intrvld: s32me(),//
previousStartTime: u64me("pst"),//
ea: boolme()//
}
export interface IBst2Base extends ICollection<"bst.bst2.player.base"> {
name: string
brnk: number
bcnum: number
lcnum: number
volt: number
gold: number
lastMusicId: number
lastChart: number
lastSort: number
lastTab: number
splv: number
preference: number
lcid: number
hat: number
}
export const Bst2BaseMap: KM<IBst2Base> = {
collection: colme<IBst2Base>("bst.bst2.player.base"),
name: strme(),
brnk: s8me(),
bcnum: s8me(),
lcnum: s8me(),
volt: s32me(),
gold: s32me(),
lastMusicId: s32me("lmid"),
lastChart: s8me("lgrd"),
lastSort: s8me("lsrt"),
lastTab: s8me("ltab"),
splv: s8me(),
preference: s8me("pref"),
lcid: s32me(),
hat: s32me()
}
export interface IBst2Survey extends ICollection<"bst.bst2.player.survey"> {
motivate: number
}
export const Bst2SurveyMap: KM<IBst2Survey> = {
collection: colme<IBst2Survey>("bst.bst2.player.survey"),
motivate: s8me()
}
export interface IBst2UnlockingInfo extends ICollection<"bst.bst2.player.unlockingInfo"> {
type: number
id: number
param: number
count: number
}
export const Bst2UnlockingInfoMap: KM<IBst2UnlockingInfo> = {
collection: colme<IBst2UnlockingInfo>("bst.bst2.player.unlockingInfo"),
type: s32me(),
id: s32me(),
param: s32me(),
count: s32me()
}
export interface IBst2Customization extends ICollection<"bst.bst2.player.customization"> {
// [rippleNote, rippleNoteColor, sfxNormalNote, sfxRippleNote, sfxSlashNote, sfxStreamNote, backgroundBrightnessTimes2, (000{sfxFine}{sfxStreamTail}{streamNoteGuide}{rippleNoteGuide}{judgeText}, ?, ?, ?, ?, ?, ?, ?, ?)]
custom: FixedSizeArray<number, 16>
}
export const Bst2CustomizationMap: KM<IBst2Customization> = {
collection: colme<IBst2Customization>("bst.bst2.player.customization"),
custom: u16me(null, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
}
export interface IBst2Tips extends ICollection<"bst.bst2.player.tips"> {
lastTips: number
}
export const Bst2TipsMap: KM<IBst2Tips> = {
collection: colme<IBst2Tips>("bst.bst2.player.tips"),
lastTips: s32me("last_tips")
}
export interface IBst2Hacker extends ICollection<"bst.bst2.player.hacker"> {
id: number
state0: number
state1: number
state2: number
state3: number
state4: number
updateTime: bigint | BigIntProxy
}
export const Bst2HackerMap: KM<IBst2Hacker> = {
collection: colme<IBst2Hacker>("bst.bst2.player.hacker"),
id: s32me(),
state0: s8me(),
state1: s8me(),
state2: s8me(),
state3: s8me(),
state4: s8me(),
updateTime: u64me("update_time")
}
export interface IBst2CrysisLog extends ICollection<"bst.bst2.player.event.crysis"> {
id: number
stageId: number
step: number
gauge: number
state: number
}
export const Bst2CrysisLogMap: KM<IBst2CrysisLog> = {
collection: colme<IBst2CrysisLog>("bst.bst2.player.event.crysis"),
id: s32me(),
stageId: s32me("stage_no"),
step: s8me(),
gauge: s32me("r_gauge"),
state: s8me("r_state")
}
export interface IBst2Bisco extends ICollection<"bst.bst2.player.bisco"> {
bnum: number
jbox: number
}
export const Bst2BiscoMap: KM<IBst2Bisco> = {
collection: colme<IBst2Bisco>("bst.bst2.player.bisco"),
bnum: s32me(),
jbox: s32me(),
}
export interface IBst2MusicRecord extends ICollection<"bst.bst2.playData.musicRecord#userId"> {
musicId: number
chart: number
playCount: number
clearCount: number
gaugeTimes10: number
score: number
grade: number
medal: number
combo: number
userId: number
updateTime: number
lastPlayTime: number
}
export const Bst2MusicRecordMap: KM<IBst2MusicRecord> = {
collection: colme<IBst2MusicRecord>("bst.bst2.playData.musicRecord#userId"),
musicId: s32me("music_id"),
chart: s32me("note_level"),
playCount: s32me("play_count"),
clearCount: s32me("clear_count"),
gaugeTimes10: s32me("best_gauge"),
score: s32me("best_score"),
grade: s32me("best_grade"),
medal: s32me("best_medal"),
combo: ignoreme(),
userId: ignoreme(),
updateTime: ignoreme(),
lastPlayTime: ignoreme()
}
export interface IBst2Course extends ICollection<"bst.bst2.playData.course#userId"> {
courseId: number
playCount: number
isTouched: boolean
clearType: number
gauge: number
score: number
grade: number
medal: number
combo: number
userId: number
updateTime: number
lastPlayTime: number
}
export const Bst2CourseMap: KM<IBst2Course> = {
collection: colme<IBst2Course>("bst.bst2.playData.course#userId"),
courseId: s32me("course_id"),
playCount: s32me("play"),
isTouched: boolme("is_touch"),
clearType: s32me("clear"),
gauge: s32me("gauge"),
score: s32me(),
grade: s32me(),
medal: s32me(),
combo: s32me(),
userId: ignoreme(),
updateTime: ignoreme(),
lastPlayTime: ignoreme()
}
export interface IBst2Player {
pdata: {
account: IBst2Account
base: IBst2Base
opened: {}
survey: IBst2Survey
item: { info?: IBst2UnlockingInfo[] }
customize: IBst2Customization
tips: IBst2Tips
hacker: { info?: IBst2Hacker[] }
playLog: { crysis?: IBst2CrysisLog[] }
bisco: { pinfo: IBst2Bisco }
record: { rec?: IBst2MusicRecord[] }
course: { record?: IBst2Course[] }
}
}
export const Bst2PlayerMap: KM<IBst2Player> = {
pdata: {
account: Bst2AccountMap,
base: Bst2BaseMap,
opened: {},
survey: Bst2SurveyMap,
item: { info: { 0: Bst2UnlockingInfoMap } },
customize: Bst2CustomizationMap,
tips: Bst2TipsMap,
hacker: { info: { 0: Bst2HackerMap } },
playLog: { crysis: { 0: Bst2CrysisLogMap }, $targetKey: "play_log" },
bisco: { pinfo: Bst2BiscoMap },
record: { rec: { 0: Bst2MusicRecordMap } },
course: { record: { 0: Bst2CourseMap } }
}
}

View File

@ -0,0 +1,80 @@
import { colme, ignoreme, KM, s32me, strme } from "../../utility/mapping"
import { ICollection } from "../utility/definitions"
export interface IBst2StageLog extends ICollection<"bst.bst2.playData.stageLog#userId"> {
playerId: number
continueCount: number
stageId: number
userId: number
lobbyId: string
musicId: number
chart: number
gaugeTimes10: number
score: number
combo: number
grade: number
medal: number
fantasticCount: number
greatCount: number
fineCount: number
missCount: number
isCourseStage: boolean
time: number
}
export const Bst2StageLogMap: KM<IBst2StageLog> = {
collection: colme<IBst2StageLog>("bst.bst2.playData.stageLog#userId"),
playerId: s32me("play_id"),
continueCount: s32me("continue_count"),
stageId: s32me("stage_no"),
userId: s32me("user_id"),
lobbyId: strme("location_id"),
musicId: s32me("select_music_id"),
chart: s32me("select_grade"),
gaugeTimes10: s32me("result_clear_gauge"),
score: s32me("result_score"),
combo: s32me("result_max_combo"),
grade: s32me("result_grade"),
medal: s32me("result_medal"),
fantasticCount: s32me("result_fanta"),
greatCount: s32me("result_great"),
fineCount: s32me("result_fine"),
missCount: s32me("result_miss"),
isCourseStage: ignoreme(),
time: ignoreme(),
}
export interface IBst2CourseLog extends ICollection<"bst.bst2.playData.courseLog#userId"> {
playerId: number
continueCount: number
userId: number
courseId: number
gauge: number
score: number
grade: number
medal: number
combo: number
fantasticCount: number
greatCount: number
fineCount: number
missCount: number
lobbyId: string
time: number
}
export const Bst2CourseLogMap: KM<IBst2CourseLog> = {
collection: colme<IBst2CourseLog>("bst.bst2.playData.courseLog#userId"),
playerId: s32me("play_id"),
continueCount: s32me("continue_count"),
userId: s32me("user_id"),
courseId: s32me("course_id"),
lobbyId: strme("lid"),
gauge: s32me(),
score: s32me(),
combo: s32me(),
grade: s32me(),
medal: s32me(),
fantasticCount: s32me("fanta"),
greatCount: s32me("great"),
fineCount: s32me("fine"),
missCount: s32me("miss"),
time: ignoreme()
}

View File

@ -0,0 +1,5 @@
import { ICollection } from "./definitions"
export interface IBatchResult extends ICollection<"bst.batchResult"> {
batchId: string
}

View File

@ -0,0 +1,3 @@
export interface ICollection<TCollectionName extends string> {
collection: TCollectionName
}

View File

@ -0,0 +1,5 @@
import { ICollection } from "./definitions"
export interface IPluginVersion<TMajor extends number = number, TMinor extends number = number, TRevision extends number = number> extends ICollection<"bst.pluginVersion"> {
version: string
}

View File

@ -0,0 +1,14 @@
import { ICollection } from "./definitions"
export interface IWebUIMessage extends ICollection<"utility.webuiMessage"> {
message: string
type: WebUIMessageType
refid?: string
version: number
}
export enum WebUIMessageType {
info = 0,
success = 1,
error = 2
}

View File

@ -0,0 +1,4 @@
export type Game = "bst"
export const game: Game = "bst"
export type PluginVersion = "1.0.0"
export const version: PluginVersion = "1.0.0"

View File

@ -0,0 +1,480 @@
import { ICollection } from "../models/utility/definitions"
export type KArrayType = KNumberType | KBigIntType
export type KGroupType = KNumberGroupType | KBigIntGroupType
export type KType = KArrayType | KGroupType | "str" | "bin" | "ip4" | "bool"
export type KTypeExtended = KType | null | "kignore"
export type TypeForKItem = number | string | bigint | BigIntProxy | boolean | Buffer | number[] | bigint[] | boolean[] | BufferArray | NumberGroup<number[] | bigint[]>
export type TypeForKObject<T> = T extends TypeForKItem ? never : T
export type TypeForKArray = number[] | bigint[] | BufferArray
export type KKey<T> = keyof T & (
T extends string ? Exclude<keyof T, keyof string> :
T extends Buffer ? Exclude<keyof T, keyof Buffer> :
T extends boolean ? Exclude<keyof T, keyof boolean> :
T extends number[] | bigint[] | boolean[] ? Exclude<keyof T, (keyof number[]) | (keyof bigint[]) | (keyof boolean[])> :
T extends any[] ? Exclude<keyof T, keyof any[]> | number :
T extends number ? Exclude<keyof T, keyof number> :
T extends bigint | BigIntProxy ? Exclude<keyof T, keyof bigint> :
T extends BufferArray ? Exclude<keyof T, keyof BufferArray> :
T extends NumberGroup<infer TGroup> ? Exclude<keyof T, keyof NumberGroup<TGroup>> :
keyof T)
export type KTypeConvert<T extends string | Buffer | number | bigint | boolean | number[] | bigint[] | unknown> =
T extends string ? "str" :
T extends Buffer ? "bin" :
T extends number ? KNumberType | "ip4" | "bool" :
T extends bigint | BigIntProxy ? KBigIntType :
T extends boolean | boolean[] ? "bool" :
T extends number[] ? KNumberType : // KARRAY
T extends bigint[] ? KBigIntType : // KARRAY
T extends NumberGroup<number[]> ? KNumberGroupType :
T extends NumberGroup<bigint[]> ? KBigIntGroupType :
T extends BufferArray ? "u8" | "s8" :
never
export type KArrayTypeConvert<T extends Buffer | number[] | bigint[] | unknown> =
T extends Buffer ? "s8" | "u8" :
T extends number[] ? KNumberType :
T extends bigint[] ? KBigIntType :
never
export type KTypeConvertBack<TKType extends KTypeExtended> =
TKType extends "str" ? string :
TKType extends "bin" ? { type: "Buffer"; data: number[] } :
TKType extends "s8" | "u8" ? [number] | number[] | { type: "Buffer"; data: number[] } :
TKType extends KNumberType ? [number] | number[] :
TKType extends KBigIntType ? [bigint] | bigint[] :
TKType extends KNumberGroupType ? number[] :
TKType extends KBigIntGroupType ? bigint[] :
unknown
export type NumberGroup<T extends number[] | bigint[] = number[]> = {
"@numberGroupValue": T
}
export const NumberGroup = <T extends number[] | bigint[] = number[]>(ng: T) => <NumberGroup>{ "@numberGroupValue": ng }
export function isNumberGroup(value: any): value is NumberGroup {
try {
return Array.isArray(BigInt(value["@numberGroupValue"]))
} catch {
return false
}
}
export type BufferArray = {
"@bufferArrayValue": Buffer
}
export const BufferArray = (ba: Buffer) => <BufferArray>{ "@bufferArrayValue": ba }
export function isBufferArray(value: any): value is BufferArray {
try {
return value["@bufferArrayValue"] instanceof Buffer
} catch {
return false
}
}
export type BigIntProxy = {
"@serializedBigInt": string
}
export const BigIntProxy = (value: bigint) => <BigIntProxy>{ "@serializedBigInt": value.toString() }
export function isBigIntProxy(value: any): value is BigIntProxy {
try {
return BigInt(value["@serializedBigInt"]).toString() == value["@serializedBigInt"]
} catch {
return false
}
}
export function toBigInt(value: bigint | BigIntProxy): bigint {
if (value == null) return null
if (value instanceof BigInt) return <bigint>value
else if (value["@serializedBigInt"] != null) return BigInt(value["@serializedBigInt"])
else return BigInt(0)
}
export type KITEM2<T> = { [K in keyof T]?: K extends KKey<T> ? KITEM2<T[K]> : never } &
{
["@attr"]: KAttrMap2<T>
["@content"]:
T extends string | Buffer | boolean | number[] | bigint[] ? T :
T extends number | bigint ? [T] :
T extends BufferArray ? Buffer :
T extends NumberGroup<infer TGroup> ? TGroup :
T extends BigIntProxy ? [bigint] : never
}
export type KAttrMap2<T> = { [key: string]: string } & {
__type?: T extends TypeForKItem ? KTypeConvert<T> : never
__count?: T extends TypeForKArray ? number : never
}
export function ITEM2<T>(ktype: KTypeConvert<T>, value: T, attr?: KAttrMap2<T>): KITEM2<T> {
// let result
// if (value instanceof NumberGroup && IsNumberGroupKType(ktype)) {
// result = K.ITEM(<KTypeConvert<T & NumberGroup>>ktype, value.value, attr)
// } else if (Array.isArray(value) && IsNumericKType(ktype)) {
// result = K.ARRAY(<KTypeConvert<T & number[]>>ktype, <any>value, <any>attr)
// } else if (value instanceof BufferArray && IsNumericKType(ktype)) {
// result = K.ARRAY(<KTypeConvert<T & BufferArray>>ktype, value.value, attr)
// } else if (typeof value != "object" && typeof value != "function") {
// result = K.ITEM(<any>ktype, <any>value, attr)
// } else {
// Object.assign(result, value, { ["@attr"]: attr })
// result["@attr"].__type = ktype
// }
// return <KITEM2<T>>result
let result = <KITEM2<T>>{}
result["@attr"] = Object.assign({}, attr, (!isNumberGroupKType(ktype) && isNumericKType(ktype) && Array.isArray(value)) ? { __type: ktype, __count: (<any[]>value).length } : { __type: ktype })
if ((ktype == "bool") && (typeof value == "boolean")) {
result["@content"] = <any>(value ? [1] : [0])
} else if ((ktype == "bin") && value instanceof Buffer) {
result = <any>K.ITEM("bin", value, result["@attr"])
} else if (((ktype == "s8") || (ktype == "u8")) && isBufferArray(value)) {
result["@content"] = <any>value["@bufferArrayValue"].toJSON()
result["@attr"].__count = <any>value["@bufferArrayValue"].byteLength
} else if (isNumericKType(ktype) && !Array.isArray(value)) {
result["@content"] = <any>[value]
} else if (isNumberGroupKType(ktype) && isNumberGroup(value)) {
result["@content"] = <any>value["@numberGroupValue"]
} else if (isBigIntProxy(value)) {
result["@content"] = <any>BigInt(value["@serializedBigInt"])
}
else {
result["@content"] = <any>value
}
if (isKIntType(ktype) && Array.isArray(result["@content"])) for (let i = 0; i < result["@content"].length; i++) (<number[]>result["@content"])[i] = Math.trunc(result["@content"][i])
return result
}
export type KObjectMappingRecord<T> = { [K in KKey<T>]: T[K] extends TypeForKItem ? KObjectMappingElementInfer<T[K]> : KObjectMappingRecord<T[K]> } & KObjectMappingElementInfer<T>
export interface KObjectMappingElement<T = any, TKType extends KTypeExtended = KTypeExtended> {
$type?: TKType,
$targetKey?: string,
$convert?: (source: T) => T
$convertBack?: (target: T) => T
$fallbackValue?: TKType extends "kignore" ? T : never
$defaultValue?: T
}
type KObjectMappingElementInfer<T> = KObjectMappingElement<T, (KTypeConvert<T> extends KType ? KTypeConvert<T> : never) | never | "kignore">
export type KAttrRecord<T> = { [K in keyof T]?: T extends TypeForKItem ? KAttrMap2<T[K]> : KAttrRecord<T[K]> } & { selfAttr?: KAttrMap2<T> }
export function getCollectionMappingElement<TCollection extends ICollection<any>>(collectionName: TCollection extends ICollection<infer TName> ? TName : never): KObjectMappingElement<TCollection extends ICollection<infer TName> ? TName : unknown, "kignore"> {
return ignoreme("collection", collectionName)
}
function isKType<TType>(type: TType): boolean {
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16", "str", "bin"].includes(type)
}
function isKIntType<TType>(type: TType): boolean {
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "2b", "3b", "4b", "vb", "vs8", "vu8", "vs16", "vu16"].includes(type)
}
function isKBigIntType<TType>(type: TType): boolean {
return (typeof (type) == "string") && ["s64", "u64"].includes(type)
}
function isNumericKType<TType>(type: TType): boolean {
return (typeof (type) == "string") && ["s8", "u8", "s16", "u16", "s32", "u32", "time", "ip4", "float", "double", "bool", "s64", "u64"].includes(type)
}
function isNumberGroupKType<TType>(type: TType): boolean {
return (typeof (type) == "string") && ["2s8", "2u8", "2s16", "2u16", "2s32", "2u32", "2f", "2d", "3s8", "3u8", "3s16", "3u16", "3s32", "3u32", "3f", "3d", "4s8", "4u8", "4s16", "4u16", "4s32", "4u32", "4f", "4d", "2b", "3b", "4b", "vb", "2s64", "2u64", "3s64", "3u64", "4s64", "4u64", "vs8", "vu8", "vs16", "vu16"].includes(type)
}
function isNumericKey(k: any): boolean {
return (typeof k == "number") || (parseInt(k).toString() == k)
}
function increaseNumericKey<T>(k: T, step: number = 1): T {
return (typeof k == "number") ? <T><unknown>(k + step) : (typeof k == "string" && parseInt(k).toString() == k) ? <T><unknown>(parseInt(k) + step) : k
}
function isEmptyKObject(o: object): boolean {
return (Object.keys(o).length == 0) || ((Object.keys(o).length == 1) && (o["@attr"] != null))
}
function isKMapRecordReservedKey(key: string): boolean {
return ["$type", "$targetKey", "$convert", "$convertBack", "$fallbackValue", "$defaultValue"].includes(key)
}
function isKArray<T>(data: KITEM2<T>): boolean {
return (data["@attr"] != null) && (data["@attr"].__count != null)
}
export function appendMappingElement<T>(map: KObjectMappingRecord<T>, element: KObjectMappingElementInfer<T>): KObjectMappingRecord<T> {
let result = <KObjectMappingRecord<T>>{}
Object.assign(result, map, element)
return result
}
export function mapKObject<T>(data: T, kMapRecord: KObjectMappingRecord<T>, kAttrRecord: KAttrRecord<T> = <KAttrRecord<T>>{}): KITEM2<T> {
if (data == null) return <KITEM2<T>>{}
let result: KITEM2<T> = <any>(((0 in data) && data instanceof Object) ? [] : {})
if (kAttrRecord.selfAttr != null) result["@attr"] = kAttrRecord.selfAttr
if (data instanceof Object) {
for (let __k in data) {
let k: keyof T = __k
let mapK: keyof T = __k
let attrK: keyof T = __k
if (!(k in kMapRecord) && isNumericKey(k)) {
for (let i = parseInt(<string>k) - 1; i >= 0; i--) if (kMapRecord[i]) {
mapK = <keyof T>i
break
}
}
if (!(k in kAttrRecord) && isNumericKey(k)) {
for (let i = parseInt(<string>k) - 1; i >= 0; i--) if (kAttrRecord[i]) {
attrK = <keyof T>i
break
}
}
if (mapK in kMapRecord) {
let target = <KITEM2<T>[keyof T]>{}
let targetMap = kMapRecord[<KKey<T>>mapK]
let targetKey: keyof T = (targetMap.$targetKey != null) ? <keyof T>targetMap.$targetKey : k
let targetValue = (targetMap.$convert != null) ? <KTypeConvertBack<KTypeConvert<T[keyof T]>>>targetMap.$convert(<any>data[k]) : data[k]
let targetAttr = kAttrRecord[attrK]
if (targetMap.$type) {
let tt = targetMap.$type
if (tt == "kignore") continue
target["@attr"] = <any>Object.assign({}, targetAttr, (!isNumberGroupKType(tt) && isNumericKType(tt) && Array.isArray(data[k]) && Array.isArray(targetValue)) ? { __type: tt, __count: (<any[]>targetValue).length } : { __type: tt })
if ((tt == "bool") && (typeof targetValue == "boolean")) {
target["@content"] = <any>(targetValue ? [1] : [0])
} else if ((tt == "bin") && targetValue instanceof Buffer) {
target = <any>K.ITEM("bin", targetValue, target["@attr"])
} else if (((tt == "s8") || (tt == "u8")) && isBufferArray(targetValue)) {
target["@content"] = <any>targetValue["@bufferArrayValue"]
} else if (isNumericKType(tt) && !Array.isArray(targetValue)) {
target["@content"] = <any>[targetValue]
} else if (isNumberGroupKType(tt) && isNumberGroup(targetValue)) {
target["@content"] = <any>targetValue["@numberGroupValue"]
} else if (isBufferArray(targetValue)) {
target["@content"] = <any>targetValue["@bufferArrayValue"].toJSON()
target["@attr"].__count = <any>targetValue["@bufferArrayValue"].byteLength
} else if (isBigIntProxy(targetValue)) {
target["@content"] = <any>BigInt(targetValue["@serializedBigInt"])
} else {
target["@content"] = <any>targetValue
}
if (isKIntType(tt) && Array.isArray(target["@content"])) for (let i = 0; i < target["@content"].length; i++) (<number[]>target["@content"])[i] = Math.trunc(target["@content"][i])
} else {
target = <any>mapKObject(<T[keyof T]>targetValue, <KObjectMappingRecord<T[keyof T]>><unknown>targetMap, <KAttrRecord<T[keyof T]>>targetAttr)
}
result[targetKey] = target
}
}
} else result = ITEM2<T>(<KTypeConvert<T>>kAttrRecord.selfAttr.$type, data, kAttrRecord.selfAttr)
return result
}
export type MapBackResult<T> = {
data: T,
attr?: KAttrRecord<T>
}
export function mapBackKObject<T extends object>(data: KITEM2<T>, kMapRecord?: KObjectMappingRecord<T>): MapBackResult<T> {
if (kMapRecord == null) {
if (data["@content"] || data["@attr"]) return { data: <any>data["@content"], attr: <any>data["@attr"] }
else return { data: <T>data }
}
let result: T = <T>((Array.isArray(data) || 0 in kMapRecord) ? [] : {})
let resultAttr: KAttrRecord<T> = <any>{ selfAttr: data["@attr"] ? data["@attr"] : null }
for (let __k in kMapRecord) {
if (isKMapRecordReservedKey(__k)) continue
let k = <keyof T>__k
let preservK = <keyof T>__k
do {
let targetMap = kMapRecord[<KKey<T>>preservK]
let targetKey = <keyof T>(targetMap.$targetKey ? targetMap.$targetKey : k)
let doOnceFlag = (isNumericKey(targetKey) && (data[targetKey] == null) && !isEmptyKObject(data))
let targetValue = <KITEM2<T>[keyof T]>(doOnceFlag ? data : data[targetKey])
if (targetMap.$type == "kignore") {
result[k] = targetMap.$fallbackValue
if ((targetValue != null) && (targetValue["@attr"] != null)) resultAttr[k] = <KAttrRecord<T>[keyof T]>{ selfAttr: targetValue["@attr"] }
continue
}
if (targetValue == null) {
if (targetMap.$convertBack != null) result[k] = targetMap.$convertBack(<any>null)
continue
}
if (targetValue["@attr"] != null) {
let targetAttr: KAttrMap2<T[keyof T]> = targetValue["@attr"]
let targetResult
if (targetAttr.__type != null) { // KITEM
targetResult = targetValue["@content"]
if (isNumberGroupKType(targetAttr.__type)) { // KITEM2<NumberGroup>
// TODO: bigint number group
targetResult = NumberGroup(targetResult)
} else if (targetAttr.__type == "bin") { // KITEM<"bin">
targetResult = targetResult
} else if ((targetAttr.__type == "s8" || targetAttr.__type == "u8") && (targetResult?.type == "Buffer") && Array.isArray(targetResult?.data)) { // KITEM2<BufferArray>
targetResult = BufferArray(Buffer.from(<number[]>targetResult.data))
} else if (targetAttr.__type == "bool") { // KITEM<"bool">
targetResult = targetResult[0] == 1 ? true : false
} else if (Array.isArray(targetResult) && (targetAttr.__count == null) && isNumericKType(targetAttr.__type)) { // KITEM<KNumberType>
targetResult = ((targetAttr.__type == "s64") || (targetAttr.__type == "u64")) ? BigIntProxy(BigInt(targetResult[0])) : targetResult[0]
}
result[k] = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetResult) : targetResult
} else { // KObject
targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetValue) : targetValue;
let partial = mapBackKObject<T[keyof T] & object>(targetResult, <any>targetMap)
result[k] = partial.data
resultAttr[k] = <any>partial.attr
}
} else { // KObject
let targetResult = (targetMap.$convertBack != null) ? targetMap.$convertBack(<any>targetValue) : targetValue;
let partial = <any>mapBackKObject<T[keyof T] & object>(<any>targetResult, <any>targetMap)
result[k] = partial.data
resultAttr[k] = <any>partial.attr
}
k = increaseNumericKey(k)
if (doOnceFlag || (isNumericKey(k) && (data[<keyof T>(targetMap.$targetKey ? targetMap.$targetKey : k)] == null))) break
} while (isNumericKey(k) && !(k in kMapRecord))
}
return { data: result, attr: resultAttr }
}
export function s8me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s8"> {
return {
$type: "s8",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function u8me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u8"> {
return {
$type: "u8",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function s16me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s16"> {
return {
$type: "s16",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function u16me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u16"> {
return {
$type: "u16",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function s32me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "s32"> {
return {
$type: "s32",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function u32me<T extends number | number[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "u32"> {
return {
$type: "u32",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function s64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement<bigint | BigIntProxy, "s64"> {
return {
$type: "s64",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function u64me(targetKey?: string, defaultValue?: bigint | BigIntProxy, convert?: (source: bigint | BigIntProxy) => bigint | BigIntProxy, convertBack?: (target: bigint | BigIntProxy) => bigint | BigIntProxy): KObjectMappingElement<bigint | BigIntProxy, "u64"> {
return {
$type: "u64",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function boolme<T extends boolean | boolean[]>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, "bool"> {
return {
$type: "bool",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function strme<TName extends string>(targetKey?: string, defaultValue?: TName, convert?: (source: TName) => TName, convertBack?: (target: TName) => TName): KObjectMappingElement<TName, "str"> {
return {
$type: "str",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function binme(targetKey?: string, defaultValue?: Buffer, convert?: (source: Buffer) => Buffer, convertBack?: (target: Buffer) => Buffer): KObjectMappingElement<Buffer, "bin"> {
return {
$type: "bin",
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export function ignoreme<T = any>(targetKey?: string, fallbackValue?: T): KObjectMappingElement<T, "kignore"> {
return {
$type: "kignore",
$fallbackValue: fallbackValue
}
}
export function me<T extends object>(targetKey?: string, defaultValue?: T, convert?: (source: T) => T, convertBack?: (target: T) => T): KObjectMappingElement<T, null> {
return {
$targetKey: targetKey,
$convert: convert,
$convertBack: convertBack,
$defaultValue: defaultValue
}
}
export const colme = getCollectionMappingElement
export const appendme = appendMappingElement
export const mapK = mapKObject
export const bacK = mapBackKObject
export function fromMap<T>(map: KObjectMappingRecord<T>): T {
let result = <T>{}
if (map.$type == "kignore") return map.$fallbackValue
if (map.$defaultValue != null) return map.$defaultValue
if (map.$type != null) {
if (isNumericKType(map.$type)) {
if (map.$type == "bool") return <any>false
else return <any>0
} else if (isKBigIntType(map.$type)) return <any>BigInt(0)
else if (isNumberGroupKType(map.$type)) return <any>NumberGroup([0])
else if (map.$type == "str") return <any>""
else return null
}
for (let k in map) {
if (isKMapRecordReservedKey(k)) continue
let value = fromMap(map[k])
if (value != null) result[k] = value
}
return result
}
export type KM<T> = KObjectMappingRecord<T>

View File

@ -0,0 +1,4 @@
export type FixedSizeArray<T, TSize extends number> = [T, ...T[]] & { readonly length: TSize }
export function fillArray<T, TSize extends number>(size: TSize, fillValue: T): FixedSizeArray<T, TSize> {
return <any>Array(size).fill(fillValue)
}

View File

@ -0,0 +1,67 @@
export function toFullWidth(s: string): string {
let resultCharCodes: number[] = []
for (let i = 0; i < s.length; i++) {
let cc = s.charCodeAt(i)
if ((cc >= 33) && (cc <= 126)) resultCharCodes.push(cc + 65281 - 33)
else if (cc == 32) resultCharCodes.push(12288) // Full-width space
else resultCharCodes.push(cc)
}
return String.fromCharCode(...resultCharCodes)
}
export function toHalfWidth(s: string): string {
let resultCharCodes: number[] = []
for (let i = 0; i < s.length; i++) {
let cc = s.charCodeAt(i)
if ((cc >= 65281) && (cc <= 65374)) resultCharCodes.push(cc - 65281 + 33)
else if (cc == 12288) resultCharCodes.push(32) // Full-width space
else resultCharCodes.push(cc)
}
return String.fromCharCode(...resultCharCodes)
}
export function isToday(st: bigint): boolean {
let now = new Date()
let today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1)
return (st >= (today.valueOf())) && (st < (tomorrow.valueOf()))
}
export async function log(data: any, file?: string) {
if (file == null) file = "./log.txt"
let s = IO.Exists(file) ? await IO.ReadFile(file, "") : ""
if (typeof data == "string") s += data + "\n"
else {
let n = ""
try {
n = JSON.stringify(data)
} catch { }
s += n + "\n"
}
await IO.WriteFile(file, s)
}
export function base64ToBuffer(str: string, size?: number): Buffer {
if (size != null) {
let rem = size - Math.trunc(size / 3) * 3
str = str.replace("=", "A").replace("=", "A").padEnd(Math.trunc(size / 3) * 4 + rem + 1, "A")
if (rem == 1) str += "=="
else if (rem == 2) str += "="
let result = Buffer.alloc(size, str, "base64")
return result
}
else return Buffer.from(str, "base64")
}
export function bufferToBase64(buffer: Buffer, isTrimZero: boolean = true): string {
if (isTrimZero) for (let i = buffer.length - 1; i >= 0; i--) if (buffer.readInt8(i) != 0) return buffer.toString("base64", 0, i + 1)
return buffer.toString("base64")
}
export function isHigherVersion(left: string, right: string): boolean {
let splitedLeft = left.split(".")
let splitedRight = right.split(".")
if (parseInt(splitedLeft[0]) < parseInt(splitedRight[0])) return true
else if (parseInt(splitedLeft[0]) == parseInt(splitedRight[0])) {
if (parseInt(splitedLeft[1]) < parseInt(splitedRight[1])) return true
else if (parseInt(splitedLeft[1]) == parseInt(splitedRight[1])) {
if (parseInt(splitedLeft[2]) < parseInt(splitedRight[2])) return true
}
}
return false
}

View File

@ -0,0 +1,243 @@
#tab-content, .tab-content {
display: none;
}
#tab-content.is-active, .tab-content.is-active {
display: block;
}
tr#tab-content.is-active, tr.tab-content.is-active {
display: table-row;
}
#tabs li.disabled a {
background-color: #c0c0c0;
border-color: #c0c0c0;
color: #7f7f7f;
cursor: default;
}
#form-pagination ul.pagination-list {
margin: 0!important;
}
.pagination-link, .pagination-next, .pagination-previous {
border-color: transparent;
transition: .2s linear;
}
.pagination-next, .pagination-previous {
color: #209CEE;
}
.pagination-next:not([disabled]):hover, .pagination-previous:not([disabled]):hover {
color: #118fe4;
}
/* Set all link color to Asphyxia CORE blue */
::selection {
color: white;
background-color: #209CEE;
}
a {
color: #209CEE;
}
.tabs.is-toggle li.is-active a {
background-color: #209CEE;
border-color: #209CEE;
}
.tabs li.is-active a {
color: #209CEE;
border-color: #209CEE;
}
.pagination-link.is-current {
background-color: #209CEE;
border-color: #209CEE;
cursor: default;
}
.select:not(.is-multiple):not(.is-loading):after {
border-color: #209CEE;
}
.select select:active, .select select:focus {
border-color: #209CEE;
}
.button.is-link {
background-color: #209CEE;
}
.button.is-link.is-active, .button.is-link:active, .button.is-link.is-hovered, .button.is-link:hover {
background-color: #118fe4;
}
.input:active, .input:focus {
border-color: #209CEE;
}
.table tr.is-selected {
background-color: #209CEE;
}
#card-content.is-hidden {
display: none;
}
#card-content {
display: block;
}
.marquee-label {
display: inline-block;
}
.marquee-label-container {
overflow-x: hidden;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
/* from Bulma */
.button.is-danger.is-light {
background-color: #feecf0;
color: #cc0f35;
}
.button.is-link.is-light {
background-color: #edf8ff;
color: #209CEE;
}
.button.is-danger.is-light.is-hovered, .button.is-danger.is-light:hover {
background-color: #fde0e6;
color: #cc0f35;
}
.button.is-link.is-light.is-hovered, .button.is-link.is-light:hover {
background-color: #e0f1fc;
color: #209CEE;
}
.tag.is-link.is-light {
background-color: #edf8ff;
color: #0D7DC6;
}
.tag.is-link.is-light:hover {
background-color: #209CEE;
color: white;
}
.tag.is-delete:hover {
background-color: #FF3860!important;
color: white;
}
@media screen and (max-width: 768px) {
.pagination {
flex-wrap: nowrap;
justify-content: left;
}
}
.pagination-list {
flex-wrap: nowrap;
list-style: none!important;
margin-top: 0.25em!important;
margin-bottom: 0.25em!important;
}
.content li + li {
margin-top: 0;
}
.one-quarter#forwide, .one-third#forwide {
display: block;
min-width: 100px;
}
.one-quarter#fornarrow, .one-third#fornarrow {
display: none;
min-width: 50px;
}
@media only screen and (max-width: 1023px) {
.one-quarter#forwide {
display: none;
}
.one-quarter#fornarrow {
display: block;
}
}
@media only screen and (max-width: 700px) {
.one-third#forwide {
display: none;
}
.one-third#fornarrow {
display: block;
}
}
@keyframes notification-fadeout {
0% {
opacity: 1;
display: block;
}
80% {
opacity: 1;
display: block;
}
99.99% {
opacity: 0;
display: block;
}
100% {
opacity: 0;
display: none;
}
}
.notification {
animation: notification-fadeout 8s forwards;
animation-play-state: paused;
}
.notification:hover {
animation-play-state: paused;
}
.modal {
padding-bottom: 13px;
}
@media screen and (max-width:1024px) {
.modal {
transition: padding-left .2s ease-in-out 50ms;
}
}
@media screen and (min-width:1023px) {
.modal {
padding-left: 256px;
transition: padding-left .2s ease-in-out 50ms;
}
}
.tag {
transition: linear .2s;
}
.tags .tag:not(:last-child) {
cursor: default;
}
.modal table tr {
border: solid #dbdbdb;
border-width: 0 0 1px;
}
.modal table tbody tr:last-child {
border-bottom-width: 0;
}
.hidden-wrapper {
overflow: hidden;
}
.hidden-x-wrapper {
overflow-x: hidden;
}
.hidden-y-wrapper {
overflow-y: hidden;
}
.scrolling-wrapper {
overflow: auto;
}
.scrolling-x-wrapper {
overflow-x: auto;
}
.scrolling-y-wrapper {
overflow-y: auto;
}
a.pagination-previous {
overflow: hidden;
}
a.pagination-next {
overflow: hidden;
}
.button.checkbox, .button.checkbox .checkmark {
transition: linear .2s;
}

View File

@ -0,0 +1,618 @@
function initializePaginatedContent() {
let containers = document.querySelectorAll(".paginated-container")
for (let container of containers) {
let pageSizeInput = container.querySelector("input.page-size")
let paginations = container.querySelectorAll(".pagination")
let contents = container.querySelectorAll(".paginated-content")
let group = container.getAttribute("pagination-group")
let flags = { isFirst: true }
let refreshEllipsis = (param) => {
if (flags.isFirst) return
let maxWidth = container.offsetWidth / 2
for (let pagination of paginations) {
let buttons = pagination.querySelector("ul.pagination-list")
if (buttons.childElementCount == 0) return
let show = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "block"
let hide = (index) => buttons.querySelector("li[tab-index=\"" + index + "\"]").style.display = "none"
let previousButton = pagination.querySelector("a.pagination-previous")
let nextButton = pagination.querySelector("a.pagination-next")
let leftEllipsis = buttons.querySelector("li.ellipsis-left")
let rightEllipsis = buttons.querySelector("li.ellipsis-right")
let width = buttons.firstChild.offsetWidth.toString()
leftEllipsis.style.width = width + "px"
rightEllipsis.style.width = width + "px"
let count = buttons.childElementCount - 2
let maxButtonCount = Math.max((buttons.firstChild.offsetWidth == 0) ? 5 : Math.trunc(maxWidth / buttons.firstChild.offsetWidth), 5)
let current = (param instanceof HTMLElement) ? param : buttons.querySelector("li.is-active")
let index = parseInt((current == null) ? 0 : current.getAttribute("tab-index"))
if (index == 0) previousButton.setAttribute("disabled", "")
else previousButton.removeAttribute("disabled")
if (index == (count - 1)) nextButton.setAttribute("disabled", "")
else nextButton.removeAttribute("disabled")
if (count <= maxButtonCount) {
for (let i = 0; i < count; i++) buttons.querySelector("li[tab-index=\"" + i + "\"]").style.display = "block"
leftEllipsis.style.display = "none"
rightEllipsis.style.display = "none"
} else {
maxButtonCount = Math.trunc((maxButtonCount - 1) / 2) * 2 + 1
let maxSurroundingButtonCount = (maxButtonCount - 5) / 2
let maxNoEllipsisIndex = maxButtonCount - 2 - maxSurroundingButtonCount - 1
if (index <= maxNoEllipsisIndex) {
for (let i = 0; i <= (maxNoEllipsisIndex + maxSurroundingButtonCount); i++) show(i)
for (let i = (maxNoEllipsisIndex + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i)
show(count - 1)
leftEllipsis.style.display = "none"
rightEllipsis.style.display = "block"
} else if (index >= (count - maxNoEllipsisIndex - 1)) {
for (let i = 1; i < (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i++) hide(i)
for (let i = (count - maxNoEllipsisIndex - maxSurroundingButtonCount - 1); i < count; i++) show(i)
show(0)
leftEllipsis.style.display = "block"
rightEllipsis.style.display = "none"
} else {
for (let i = 1; i < (index - maxSurroundingButtonCount); i++) hide(i)
for (let i = (index - maxSurroundingButtonCount); i <= (index + maxSurroundingButtonCount); i++) show(i)
for (let i = (index + maxSurroundingButtonCount) + 1; i < count - 1; i++) hide(i)
show(0)
show(count - 1)
leftEllipsis.style.display = "block"
rightEllipsis.style.display = "block"
}
}
}
}
let refresh = () => {
if ((pageSizeInput == null) || (parseInt(pageSizeInput.value) <= 0)) {
for (let pagination of paginations) pagination.style.display = "none"
return
}
let pageSize = parseInt(pageSizeInput.value)
let pageCount = Math.ceil(contents.length / pageSize)
if (!flags.isFirst && (flags.pageSize == pageSize) && (flags.pageCount == pageCount)) return
for (let pagination of paginations) {
let buttons = pagination.querySelector("ul.pagination-list")
buttons.innerHTML = ""
buttons.id = "tabs"
}
for (let i = 0; i < pageCount; i++) {
for (let j = i * pageSize; j < (i + 1) * pageSize; j++) {
if (contents[j] == null) break
contents[j].classList.add("tab-content")
contents[j].setAttribute("tab-group", group)
contents[j].setAttribute("tab-index", i)
if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) contents[j].classList.add("is-active")
if (j == ((i + 1) * pageSize - 1)) for (let td of contents[j].querySelectorAll("td")) td.style.borderBottom = "0"
}
if (pageCount > 1) for (let pagination of paginations) {
let buttons = pagination.querySelector("ul.pagination-list")
let a = document.createElement("a")
a.classList.add("pagination-link")
a.innerText = i + 1
let li = document.createElement("li")
li.appendChild(a)
if ((i == 0) && (flags.isFirst || (flags.pageCount != pageCount))) {
li.classList.add("is-active")
a.classList.add("is-current")
}
li.setAttribute("tab-group", group)
li.setAttribute("tab-index", i)
buttons.appendChild(li)
li.addEventListener("click", () => {
refreshEllipsis(li)
})
}
}
if (pageCount > 1) for (let pagination of paginations) {
pagination.style.display = "flex"
let buttons = pagination.querySelector("ul.pagination-list")
let leftEllipsis = document.createElement("li")
leftEllipsis.style.display = "none"
leftEllipsis.classList.add("ellipsis-left", "ignore")
leftEllipsis.innerHTML = "<span class=\"pagination-ellipsis\">&hellip;</span>"
let rightEllipsis = document.createElement("li")
rightEllipsis.style.display = "none"
rightEllipsis.classList.add("ellipsis-right", "ignore")
rightEllipsis.innerHTML = "<span class=\"pagination-ellipsis\">&hellip;</span>"
buttons.firstChild.after(leftEllipsis)
buttons.lastChild.before(rightEllipsis)
let previousButton = pagination.querySelector("a.pagination-previous")
let nextButton = pagination.querySelector("a.pagination-next")
previousButton.addEventListener("click", () => {
let current = buttons.querySelector("li.is-active")
let index = parseInt(current.getAttribute("tab-index"))
if (index <= 0) return
let prev = buttons.querySelector("li[tab-index=\"" + (index - 1) + "\"]")
prev.dispatchEvent(new Event("click"))
})
nextButton.addEventListener("click", () => {
let current = buttons.querySelector("li.is-active")
let index = parseInt(current.getAttribute("tab-index"))
if (index >= (buttons.childElementCount - 3)) return // includes left & right ellipsis
let next = buttons.querySelector("li[tab-index=\"" + (index + 1) + "\"]")
next.dispatchEvent(new Event("click"))
})
} else for (let pagination of paginations) pagination.style.display = "none"
flags.pageCount = pageCount
flags.pageSize = pageSize
flags.isFirst = false
}
refresh()
pageSizeInput.addEventListener("change", refresh)
let o = new ResizeObserver(refreshEllipsis)
o.observe(container)
}
}
function initializeTabs() {
let tabs = document.querySelectorAll("#tabs li")
let tabContents = document.querySelectorAll("#tab-content, .tab-content")
let updateActiveTab = (tabGroup, tabIndex) => {
for (let t of tabs) if (t && (t.getAttribute("tab-group") == tabGroup)) {
if (t.getAttribute("tab-index") != tabIndex) {
t.classList.remove("is-active")
for (let a of t.querySelectorAll("a")) a.classList.remove("is-current")
} else {
t.classList.add("is-active")
for (let a of t.querySelectorAll("a")) a.classList.add("is-current")
}
}
}
let updateActiveContent = (tabGroup, tabIndex) => {
for (let item of tabContents) {
let group = item.getAttribute("tab-group")
let index = item.getAttribute("tab-index")
if (item && (group == tabGroup)) item.classList.remove("is-active")
if ((index == tabIndex) && (group == tabGroup)) item.classList.add("is-active")
}
}
for (let t of tabs) {
if (!t.classList.contains("disabled") && !t.classList.contains("ignore")) t.addEventListener("click", () => {
let group = t.getAttribute("tab-group")
let index = t.getAttribute("tab-index")
updateActiveTab(group, index)
updateActiveContent(group, index)
})
}
}
function initializeToggles() {
let toggles = document.querySelectorAll(".card-header .card-toggle")
let contents = document.querySelectorAll(".card-content")
for (let t of toggles) {
let card = t.getAttribute("card")
if (card == null) continue
let cc = []
for (let c of contents) if (c.getAttribute("card") == card) cc.push(c)
t.style.transition = "0.2s linear"
t.addEventListener("click", (e) => {
if (e.currentTarget.style.transform == "rotate(180deg)") {
e.currentTarget.style.transform = ""
for (let c of cc) c.classList.remove("is-hidden")
} else {
e.currentTarget.style.transform = "rotate(180deg)"
for (let c of cc) c.classList.add("is-hidden")
}
})
}
}
function initializeModals() {
let modaltriggers = $(".modal-trigger")
for (let t of modaltriggers) {
let m = t.querySelector(".modal")
let c = m.querySelectorAll("#close")
t.addEventListener("click", (e) => { m.style.display = "flex" })
for (let v of c) v.addEventListener("click", (e) => {
m.style.display = "none"
e.stopPropagation()
})
}
}
function initializeFormSelects() {
let formSelects = document.querySelectorAll("#form-select")
for (let s of formSelects) {
let input = s.querySelector("input#form-select-input")
let select = s.querySelector("select#form-select-select")
let options = select.querySelectorAll("option")
for (let i = 0; i < options.length; i++) {
let o = options[i]
let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
let enabled = (o.getAttribute("disabled") == null) ? true : false
if (value == input.value) select.selectedIndex = i
if (!enabled) o.style.display = "none"
}
select.addEventListener("change", () => {
for (let i = 0; i < options.length; i++) {
let o = options[i]
if (o.selected) {
input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
input.dispatchEvent(new Event("change"))
break
}
}
})
}
}
function initializeFormPaginations() {
let formPags = document.querySelectorAll("#form-pagination")
for (let p of formPags) {
let input = p.querySelector("input#form-pagination-input")
let options = p.querySelectorAll("ul.pagination-list li a.pagination-link")
for (let i = 0; i < options.length; i++) {
let o = options[i]
let value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
if (value == input.value) {
if (!o.classList.contains("is-current")) o.classList.add("is-current")
} else o.classList.remove("is-current")
o.addEventListener("click", () => {
for (let i = 0; i < options.length; i++) options[i].classList.remove("is-current")
if (!o.classList.contains("is-current")) o.classList.add("is-current")
input.value = (o.getAttribute("value") == null) ? i : o.getAttribute("value")
})
}
}
}
function initializeFormValidation() {
let forms = document.querySelectorAll("form#validatable")
for (let f of forms) {
let validatableFields = f.querySelectorAll(".field#validatable")
let validatableButtons = f.querySelectorAll("button#validatable")
let getParams = (input) => {
return {
minLength: input.getAttribute("min-length"),
maxLength: input.getAttribute("max-length"),
recommendedLength: input.getAttribute("recommended-length"),
minPattern: input.getAttribute("min-pattern"),
recommendedPattern: input.getAttribute("recommended-pattern"),
isNumeric: (input.getAttribute("numeric") != null) ? true : false
}
}
let isValid = (value, params) => {
let t = value.trim()
if (params.minLength != null) if (t.length < parseInt(params.minLength)) return false
if (params.maxLength != null) if (t.length > parseInt(params.maxLength)) return false
if (params.minPattern != null) if (!(new RegExp(params.minPattern).test(t))) return false
if (params.isNumeric == true) if (parseInt(t).toString() != t) return false
return true
}
let isFormValid = () => {
for (let field of validatableFields) for (let i of field.querySelectorAll("input#validatable")) if (!isValid(i.value, getParams(i))) return false
return true
}
for (let field of validatableFields) {
let inputs = field.querySelectorAll("input#validatable")
let tips = field.querySelectorAll(".help")
for (let i of inputs) i.addEventListener("change", () => {
let params = getParams(i)
// inputs
if (isValid(i.value, params)) {
i.classList.remove("is-danger")
for (let t of tips) t.classList.remove("is-danger")
} else if (!i.classList.contains("is-danger")) {
i.classList.add("is-danger")
for (let t of tips) t.classList.add("is-danger")
}
// buttons
if (isFormValid()) {
for (let b of validatableButtons) b.removeAttribute("disabled")
} else {
for (let b of validatableButtons) if (b.getAttribute("disabled") == null) b.setAttribute("disabled", "")
}
})
}
}
}
function initializeFormCollections() {
let collections = document.querySelectorAll("#form-collection")
for (let c of collections) {
let maxLength = parseInt(c.getAttribute("max-length"))
let fallbackValue = JSON.parse(c.getAttribute("fallback"))
let input = c.querySelector("#form-collection-input")
let tags = c.querySelectorAll("#form-collection-tag")
let modButton = c.querySelector("#form-collection-modify")
let modTable = c.querySelector("table#multi-select")
let modInput = modTable.querySelector("input#multi-select-input")
let modTitle = modTable.querySelector("input#multi-select-title")
let deleteButtonClickEventListener = (tag) => () => {
let tvalue = JSON.parse(tag.getAttribute("value"))
let value = JSON.parse(input.value)
value.splice(value.indexOf(tvalue), 1)
if (fallbackValue != null) value.push(fallbackValue)
input.value = JSON.stringify(value)
modInput.value = input.value
modInput.dispatchEvent(new Event("change"))
tag.remove()
}
for (let t of tags) {
let d = t.querySelector(".delete, .is-delete")
d.addEventListener("click", deleteButtonClickEventListener(t))
}
modInput.value = input.value
modInput.setAttribute("max-length", maxLength)
modInput.setAttribute("fallback", JSON.stringify(fallbackValue))
modInput.addEventListener("change", () => {
let fallbackValue = JSON.parse(c.getAttribute("fallback"))
let oldValue = JSON.parse(input.value)
let newValue = JSON.parse(modInput.value)
let tags = c.querySelectorAll("#form-collection-tag")
for (let o of oldValue) if (!newValue.includes(o) && (o != fallbackValue)) {
for (let t of tags) if (JSON.parse(t.getAttribute("value")) == o) t.remove()
}
for (let n = 0; n < newValue.length; n++) if (!oldValue.includes(newValue[n]) && (newValue[n] != fallbackValue)) {
let tag = document.createElement("div")
tag.classList.add("control")
tag.id = "form-collection-tag"
tag.setAttribute("value", newValue[n])
tag.innerHTML = "<span class=\"tags has-addons\"><span class=\"tag is-link is-light\" id=\"form-collection-tag-title\">" + JSON.parse(modTitle.value)[n] + "</span><a class=\"tag is-delete\" /></span>"
tag.querySelector("a.is-delete").addEventListener("click", deleteButtonClickEventListener(tag))
modButton.before(tag)
}
input.value = modInput.value
})
}
}
function initializeMultiSelectTables() {
let tables = document.querySelectorAll("table#multi-select")
for (let table of tables) {
let valueInput = table.querySelector("input#multi-select-input")
let titleInput = table.querySelector("input#multi-select-title")
let trimValues = (values, fallback) => {
while (values.includes(fallback)) values.splice(values.indexOf(fallback), 1)
return values
}
let fillValues = (values, fallback) => {
let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length"))
while (values.length < maxLength) values.push(fallback)
return values
}
let lines = table.querySelectorAll("tbody tr")
let refresh = () => {
let fallbackValue = JSON.parse(valueInput.getAttribute("fallback"))
let value = trimValues(JSON.parse(valueInput.value), fallbackValue)
let title = []
for (let l of lines) {
let lvalue = JSON.parse(l.getAttribute("multi-select-value"))
if (value.includes(lvalue)) {
if (!l.classList.contains("is-selected")) l.classList.add("is-selected")
title[value.indexOf(lvalue)] = l.getAttribute("multi-select-title")
l.style.fontWeight = "bold"
} else {
l.classList.remove("is-selected")
l.style.fontWeight = ""
}
}
titleInput.value = JSON.stringify(title)
}
for (let l of lines) {
l.onclick = () => {
let fallbackValue = JSON.parse(valueInput.getAttribute("fallback"))
let maxLength = (valueInput.getAttribute("max-length") == null) ? -1 : parseInt(valueInput.getAttribute("max-length"))
let value = trimValues(JSON.parse(valueInput.value), fallbackValue)
let lvalue = JSON.parse(l.getAttribute("multi-select-value"))
if (value.includes(lvalue)) value.splice(value.indexOf(lvalue), 1)
else if (maxLength >= 0) {
if (value.length < maxLength) value.push(lvalue)
else alert("Cannot add more items, items are up to " + maxLength + ".")
} else value.push(lvalue)
valueInput.value = JSON.stringify(fillValues(value, fallbackValue))
refresh()
valueInput.dispatchEvent(new Event("change"))
}
refresh()
}
valueInput.addEventListener("change", refresh)
}
}
function initializeFormNumerics() {
let numerics = document.querySelectorAll("#form-numeric")
for (let n of numerics) {
let add = n.querySelector("#form-numeric-add")
let sub = n.querySelector("#form-numeric-sub")
let inputs = n.querySelectorAll("#form-numeric-input")
add.addEventListener("click", (e) => {
for (let i of inputs) {
let maxValue = parseFloat(i.getAttribute("max-value"))
let step = parseFloat(i.getAttribute("step"))
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
let value = (parseFloat(i.value) * 10 + step * 10) / 10
if (value * Math.sign(step) <= maxValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
}
e.stopPropagation()
})
sub.addEventListener("click", (e) => {
for (let i of inputs) {
let minValue = parseFloat(i.getAttribute("min-value"))
let step = parseFloat(i.getAttribute("step"))
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
let value = (parseFloat(i.value) * 10 - step * 10) / 10
if (value * Math.sign(step) >= minValue * Math.sign(step)) i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
}
e.stopPropagation()
})
for (let i of inputs) {
let digitCount = (i.getAttribute("digit-count") == null) ? -1 : parseInt(i.getAttribute("digit-count"))
let value = parseFloat(i.value)
i.value = (digitCount >= 0) ? value.toFixed(digitCount) : value
}
}
}
function initializeUploader() {
let uploaders = document.querySelectorAll("div#uploader")
for (let uploader of uploaders) {
let input = uploader.querySelector("input#uploader-input")
let text = uploader.querySelector("input#uploader-text")
let placeholder = uploader.querySelector("#uploader-placeholder")
let remove = uploader.querySelector("#uploader-delete")
let reader = new FileReader()
input.addEventListener("change", () => {
if (input.files.length > 0) {
remove.style.display = "block"
placeholder.innerText = input.files[0].name
reader.readAsText(input.files[0])
reader.onload = () => text.value = reader.result
} else {
placeholder.innerText = ""
remove.style.display = "none"
text.value = null
}
})
remove.addEventListener("click", (e) => {
e.stopPropagation()
input.value = null
input.dispatchEvent(new Event("change"))
})
remove.style.display = "none"
}
}
function checkImg() {
let imgs = document.querySelectorAll("#exist-or-not")
for (let img of imgs) {
let general = img.querySelector("img#general")
let specified = img.querySelector("img#specified")
if (specified.width == 0) specified.style.display = "none"
else general.style.display = "none"
}
}
function initializeMarqueeLabels() {
let marqueeContainers = document.querySelectorAll(".marquee-label-container")
for (let c of marqueeContainers) {
let marquees = c.querySelectorAll(".marquee-label")
for (let marquee of marquees) {
if (marquee.closest(".marquee-label-container") != c) continue
let refresh = () => {
let lpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-left"))
if (lpad == NaN) lpad = 0
let rpad = parseInt(window.getComputedStyle(c, null).getPropertyValue("padding-right"))
if (rpad == NaN) rpad = 20
let hpad = lpad + rpad
let speed = marquee.getAttribute("speed")
if (speed == null) speed = 1
let stopingTime = 0.5
let duration = (20 * (marquee.offsetWidth - c.offsetWidth + hpad)) / speed + 2 * stopingTime
if ((marquee.offsetWidth > 0) && (marquee.offsetWidth > c.offsetWidth - hpad)) {
marquee.animate([
{ transform: "translateX(0)", offset: 0 },
{ transform: "translateX(0)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: stopingTime / duration },
{ transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", easing: "cubic-bezier(0.67, 0, 0.33, 1)", offset: 1 - stopingTime / duration },
{ transform: "translateX(" + (c.offsetWidth - marquee.offsetWidth - hpad) + "px)", offset: 1 }
], { duration: (20 * (marquee.offsetWidth - c.offsetWidth) + 1000) / speed, direction: "alternate-reverse", iterations: Infinity })
} else marquee.style.animation = "none"
}
let o = new ResizeObserver(refresh)
o.observe(c)
}
}
}
function initializeNotificatioAnimation() {
let notifications = document.querySelectorAll(".notification.temporary")
for (let n of notifications) {
let remove = n.querySelector(".delete")
let startSubmitter = n.querySelector("form.start")
let startPath = startSubmitter.getAttribute("action")
let startRequest = new XMLHttpRequest()
startRequest.open("POST", startPath, true)
startRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
let endSubmitter = n.querySelector("form.end")
let endPath = startSubmitter.getAttribute("action")
let endRequest = new XMLHttpRequest()
endRequest.open("POST", endPath, true)
endRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
if (startSubmitter != null) startRequest.send()
let end = () => {
n.style.display = "none"
if (endSubmitter != null) endRequest.send()
}
n.style.animationPlayState = "running"
remove.addEventListener("click", end)
n.addEventListener("animationend", end)
n.addEventListener("webkitAnimationEnd", end)
}
}
function initializeCheckBoxes() {
let checks = document.querySelectorAll(".checkbox")
for (let c of checks) {
let input = c.querySelector("input[type=checkbox]")
let mark = c.querySelector(".checkmark")
let refresh = (value) => {
value = input.getAttribute("checked")
if (value == null) {
input.removeAttribute("checked")
mark.style.opacity = 0
if (!c.classList.contains("is-light")) c.classList.add("is-light")
} else {
input.setAttribute("checked", "checked")
mark.style.opacity = 100
c.classList.remove("is-light")
}
}
c.addEventListener("click", () => {
let value = input.getAttribute("checked")
if (value == null) input.setAttribute("checked", "checked")
else input.removeAttribute("checked")
refresh()
})
refresh()
}
}
function removeLoadingModal() {
let loading = document.querySelector(".loading")
setTimeout(() => (loading == null) ? null : loading.remove(), 505)
try {
let a = loading.animate([
{ offset: 0, opacity: 1 },
{ offset: 0.25, opacity: 0 },
{ offset: 1, opacity: 0 }
], { duration: 2000 })
a.onfinish = loading.remove
a.play()
} catch { }
}
$(document).ready(() => {
initializeNotificatioAnimation()
initializePaginatedContent()
initializeTabs()
initializeToggles()
initializeModals()
initializeFormSelects()
initializeFormNumerics()
initializeFormPaginations()
initializeFormValidation()
initializeFormCollections()
initializeMultiSelectTables()
initializeUploader()
checkImg()
initializeMarqueeLabels()
initializeCheckBoxes()
removeLoadingModal()
})

File diff suppressed because one or more lines are too long

18
ddr@asphyxia/README.md Normal file
View File

@ -0,0 +1,18 @@
# Dance Dance Revolution
![Version](https://img.shields.io/badge/Version-v1.0.0-brightgreen?style=for-the-badge)
---
Supported version
- Dance Dance Revolution A20
- Dance Dance Revolution A
---
Changelogs
**v1.0.0**
- Initial release

View File

@ -0,0 +1,18 @@
export const eventLog: EPR = (info, data, send) => {
return send.object({
gamesession: K.ITEM("s64", BigInt(1)),
logsendflg: K.ITEM("s32", 0),
logerrlevel: K.ITEM("s32", 0),
evtidnosendflg: K.ITEM("s32", 0)
});
};
export const convcardnumber: EPR = (info, data, send) => {
return send.object({
result: K.ITEM("s32", 0),
data: {
card_number: K.ITEM("str", $(data).str("data.card_id").split("|")[0])
}
});
};

View File

@ -0,0 +1,275 @@
import { CommonOffset, LastOffset, OptionOffset, Profile } from "../models/profile";
import { formatCode } from "../utils";
import { Score } from "../models/score";
import { Ghost } from "../models/ghost";
enum GameStyle {
SINGLE,
DOUBLE,
VERSUS
}
export const usergamedata: EPR = async (info, data, send) => {
const mode = $(data).str("data.mode");
const refId = $(data).str("data.refid");
switch (mode) {
case "userload":
return send.object(await userload(refId));
case "usernew":
return send.object(await usernew(refId, data));
case "usersave":
return send.object(await usersave(refId, data));
case "rivalload":
return send.object(await rivalload(refId, data));
case "ghostload":
return send.object(await ghostload(refId, data));
case "inheritance":
return send.object(inheritance(refId));
default:
return send.deny();
}
};
const userload = async (refId: string) => {
let resObj = {
result: K.ITEM("s32", 0),
is_new: K.ITEM("bool", false),
music: [],
eventdata: []
};
if (!refId.startsWith("X000")) {
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) resObj.is_new = K.ITEM("bool", true);
const scores = await DB.Find<Score>(refId, { collection: "score" });
for (const score of scores) {
const note = [];
for (let i = 0; i < 9; i++) {
if (score.difficulty !== i) {
note.push({
count: K.ITEM("u16", 0),
rank: K.ITEM("u8", 0),
clearkind: K.ITEM("u8", 0),
score: K.ITEM("s32", 0),
ghostid: K.ITEM("s32", 0)
});
} else {
note.push({
count: K.ITEM("u16", 1),
rank: K.ITEM("u8", score.rank),
clearkind: K.ITEM("u8", score.clearKind),
score: K.ITEM("s32", score.score),
ghostid: K.ITEM("s32", score.songId)
});
}
}
resObj.music.push({
mcode: K.ITEM("u32", score.songId),
note
});
}
resObj["grade"] = {
single_grade: K.ITEM("u32", profile.singleGrade || 0),
dougle_grade: K.ITEM("u32", profile.doubleGrade || 0)
};
}
return resObj;
};
const usernew = async (refId: string, data: any) => {
const shopArea = $(data).str("data.shoparea", "");
let profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) {
profile = (await DB.Upsert<Profile>(refId, { collection: "profile" }, {
collection: "profile",
ddrCode: _.random(1, 99999999),
shopArea
})).docs[0];
}
return {
result: K.ITEM("s32", 0),
seq: K.ITEM("str", formatCode(profile.ddrCode)),
code: K.ITEM("s32", profile.ddrCode),
shoparea: K.ITEM("str", profile.shopArea),
};
};
const usersave = async (refId: string, serverData: any) => {
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (profile) {
const data = $(serverData).element("data");
const notes = data.elements("note");
const events = data.elements("event");
const common = profile.usergamedata.COMMON.strdata.split(",");
const option = profile.usergamedata.OPTION.strdata.split(",");
const last = profile.usergamedata.LAST.strdata.split(",");
if (data.bool("isgameover")) {
const style = data.number("playstyle");
if (style === GameStyle.DOUBLE) {
common[CommonOffset.DOUBLE_PLAYS] = (parseInt(common[CommonOffset.DOUBLE_PLAYS]) + 1) + "";
} else {
common[CommonOffset.SINGLE_PLAYS] = (parseInt(common[CommonOffset.SINGLE_PLAYS]) + 1) + "";
}
common[CommonOffset.TOTAL_PLAYS] = (+common[CommonOffset.DOUBLE_PLAYS]) + (+common[CommonOffset.SINGLE_PLAYS]) + "";
const workoutEnabled = !!+common[CommonOffset.WEIGHT_DISPLAY];
const workoutWeight = +common[CommonOffset.WEIGHT];
if (workoutEnabled && workoutWeight > 0) {
let total = 0;
for (const note of notes) {
total = total + note.number("calorie", 0);
}
last[LastOffset.CALORIES] = total + "";
}
for (const event of events) {
const eventId = event.number("eventid", 0);
const eventType = event.number("eventtype", 0);
if (eventId === 0 || eventType === 0) continue;
const eventCompleted = event.number("comptime") !== 0;
const eventProgress = event.number("savedata");
if (!profile.events) profile.events = {};
profile.events[eventId] = {
completed: eventCompleted,
progress: eventProgress
};
}
const gradeNode = data.element("grade");
if (gradeNode) {
const single = gradeNode.number("single_grade", 0);
const double = gradeNode.number("double_grade", 0);
profile.singleGrade = single;
profile.doubleGrade = double;
}
}
let scoreData: KDataReader | null;
let stageNum = 0;
for (const note of notes) {
if (note.number("stagenum") > stageNum) {
scoreData = note;
stageNum = note.number("stagenum");
}
}
if (scoreData) {
const songId = scoreData.number("mcode");
const difficulty = scoreData.number("notetype");
const rank = scoreData.number("rank");
const clearKind = scoreData.number("clearkind");
const score = scoreData.number("score");
const maxCombo = scoreData.number("maxcombo");
const ghostSize = scoreData.number("ghostsize");
const ghost = scoreData.str("ghost");
option[OptionOffset.SPEED] = scoreData.number("opt_speed").toString(16);
option[OptionOffset.BOOST] = scoreData.number("opt_boost").toString(16);
option[OptionOffset.APPEARANCE] = scoreData.number("opt_appearance").toString(16);
option[OptionOffset.TURN] = scoreData.number("opt_turn").toString(16);
option[OptionOffset.STEP_ZONE] = scoreData.number("opt_dark").toString(16);
option[OptionOffset.SCROLL] = scoreData.number("opt_scroll").toString(16);
option[OptionOffset.ARROW_COLOR] = scoreData.number("opt_arrowcolor").toString(16);
option[OptionOffset.CUT] = scoreData.number("opt_cut").toString(16);
option[OptionOffset.FREEZE] = scoreData.number("opt_freeze").toString(16);
option[OptionOffset.JUMP] = scoreData.number("opt_jump").toString(16);
option[OptionOffset.ARROW_SKIN] = scoreData.number("opt_arrowshape").toString(16);
option[OptionOffset.FILTER] = scoreData.number("opt_filter").toString(16);
option[OptionOffset.GUIDELINE] = scoreData.number("opt_guideline").toString(16);
option[OptionOffset.GAUGE] = scoreData.number("opt_gauge").toString(16);
option[OptionOffset.COMBO_POSITION] = scoreData.number("opt_judgepriority").toString(16);
option[OptionOffset.FAST_SLOW] = scoreData.number("opt_timing").toString(16);
await DB.Upsert<Score>(refId, {
collection: "score",
songId,
difficulty
}, {
$set: {
rank,
clearKind,
score,
maxCombo
}
});
await DB.Upsert<Ghost>(refId, {
collection: "ghost",
songId,
difficulty
}, {
$set: {
ghostSize,
ghost
}
});
}
await DB.Update<Profile>(refId, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": common.join(","),
"usergamedata.OPTION.strdata": option.join(","),
"usergamedata.LAST.strdata": last.join(","),
}
});
}
return {
result: K.ITEM("s32", 0)
};
};
const rivalload = (refId: string, data: any) => {
const loadFlag = $(data).number("data.loadflag");
const record = [];
return {
result: K.ITEM("s32", 0),
data: {
recordtype: K.ITEM("s32", loadFlag),
record
}
};
};
const ghostload = (refId: string, data: any) => {
const ghostdata = {};
return {
result: K.ITEM("s32", 0),
ghostdata
};
};
const inheritance = (refId: string) => {
return {
result: K.ITEM("s32", 0),
InheritanceStatus: K.ITEM("s32", 1)
};
};

View File

@ -0,0 +1,45 @@
import { Profile } from "../models/profile";
export const usergamedata_recv: EPR = async (info, data, send) => {
const refId = $(data).str("data.refid");
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
let recordNum = 0;
const record = [];
const d = [];
const types = $(data).str("data.recv_csv").split(",").filter((_, i) => (i % 2 === 0));
for (const type of types) {
let strdata = "<NODATA>";
let bindata = "<NODATA>";
if (profile) {
strdata = profile.usergamedata[type]["strdata"];
bindata = profile.usergamedata[type]["bindata"];
if (type === "OPTION") {
const split = strdata.split(",");
split[0] = U.GetConfig("save_option") ? "1" : "0";
strdata = split.join(",");
}
}
d.push({
...K.ITEM("str", !profile ? strdata : Buffer.from(strdata).toString("base64")),
...profile && { bin1: K.ITEM("str", Buffer.from(bindata).toString("base64")) }
});
recordNum++;
}
record.push({ d });
return send.object({
result: K.ITEM("s32", 0),
player: {
record,
record_num: K.ITEM("u32", recordNum)
}
});
};

View File

@ -0,0 +1,31 @@
import { Profile } from "../models/profile";
export const usergamedata_send: EPR = async (info, data, send) => {
const refId = $(data).str("data.refid");
const profile = await DB.FindOne<Profile>(refId, { collection: "profile" });
if (!profile) return send.deny();
for (const record of $(data).elements("data.record.d")) {
const decodeStr = Buffer.from(record.str("", ""), "base64").toString("ascii");
const decodeBin = Buffer.from(record.str("bin1", ""), "base64").toString("ascii");
const strdata = decodeStr.split(",");
const type = Buffer.from(strdata[1]).toString("utf-8");
if (!profile.usergamedata) profile.usergamedata = {};
if (!profile.usergamedata[type]) profile.usergamedata[type] = {};
profile.usergamedata[type] = {
strdata: strdata.slice(2, -1).join(","),
bindata: decodeBin
};
}
try {
await DB.Update<Profile>(refId, { collection: "profile" }, profile);
return send.object({ result: K.ITEM("s32", 0) });
} catch {
return send.deny();
}
};

135
ddr@asphyxia/index.ts Normal file
View File

@ -0,0 +1,135 @@
import { convcardnumber, eventLog } from "./handlers/common";
import { usergamedata } from "./handlers/usergamedata";
import { usergamedata_recv } from "./handlers/usergamedata_recv";
import { usergamedata_send } from "./handlers/usergamedata_send";
import { CommonOffset, OptionOffset, Profile } from "./models/profile";
export function register() {
R.GameCode("MDX");
R.Config("save_option", {
name: "Save option",
desc: "Gets the previously set options as they are.",
default: true,
type: "boolean"
});
R.Route("playerdata.usergamedata_advanced", usergamedata);
R.Route("playerdata.usergamedata_recv", usergamedata_recv);
R.Route("playerdata.usergamedata_send", usergamedata_send);
R.Route("system.convcardnumber", convcardnumber);
R.Route("eventlog.write", eventLog);
R.WebUIEvent("updateName", async ({ refid, name }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.NAME] = name;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateWeight", async ({ refid, weight }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.WEIGHT] = weight;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateDisplayCalories", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.COMMON.strdata.split(",");
strdata[CommonOffset.WEIGHT_DISPLAY] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.COMMON.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateArrowSkin", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.ARROW_SKIN] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateGuideline", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.GUIDELINE] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateFilter", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.FILTER] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateJudgmentPriority", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.COMBO_POSITION] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
R.WebUIEvent("updateDisplayTiming", async ({ refid, selected }) => {
let strdata: Profile | string[] = await DB.FindOne<Profile>(refid, { collection: "profile" });
if (strdata) {
strdata = strdata.usergamedata.OPTION.strdata.split(",");
strdata[OptionOffset.FAST_SLOW] = selected;
await DB.Update<Profile>(refid, { collection: "profile" }, {
$set: {
"usergamedata.OPTION.strdata": strdata.join(",")
}
});
}
});
}

View File

@ -0,0 +1,8 @@
export interface Ghost {
collection: "ghost";
songId: number;
difficulty: number;
ghostSize: number;
ghost: string;
}

View File

@ -0,0 +1,77 @@
export enum CommonOffset {
AREA = 1,
SEQ_HEX = 1,
WEIGHT_DISPLAY = 3,
CHARACTER,
EXTRA_CHARGE,
TOTAL_PLAYS = 9,
SINGLE_PLAYS = 11,
DOUBLE_PLAYS,
WEIGHT = 17,
NAME = 25,
SEQ
}
export enum OptionOffset {
SPEED = 1,
BOOST,
APPEARANCE,
TURN,
STEP_ZONE,
SCROLL,
ARROW_COLOR,
CUT,
FREEZE,
JUMP,
ARROW_SKIN,
FILTER,
GUIDELINE,
GAUGE,
COMBO_POSITION,
FAST_SLOW
}
export enum LastOffset {
SONG = 3,
CALORIES = 10
}
export enum RivalOffset {
RIVAL_1_ACTIVE = 1,
RIVAL_2_ACTIVE,
RIVAL_3_ACTIVE,
RIVAL_1_DDRCODE = 9,
RIVAL_2_DDRCODE,
RIVAL_3_DDRCODE,
}
export interface Profile {
collection: "profile";
ddrCode: number;
shopArea: string;
singleGrade?: number;
doubleGrade?: number;
events?: {};
usergamedata?: {
COMMON?: {
strdata?: string;
bindata?: string;
};
OPTION?: {
strdata?: string;
bindata?: string;
};
LAST?: {
strdata?: string;
bindata?: string;
};
RIVAL?: {
strdata?: string;
bindata?: string;
};
};
}

View File

@ -0,0 +1,49 @@
export enum Difficulty {
SINGLE_BEGINNER,
SINGLE_BASIC,
SINGLE_DIFFICULT,
SINGLE_EXPERT,
SINGLE_CHALLENGE,
DOUBLE_BASIC,
DOUBLE_DIFFICULT,
DOUBLE_EXPERT,
DOUBLE_CHALLENGE
}
export enum Rank {
AAA,
AA_PLUS,
AA,
AA_MINUS,
A_PLUS,
A,
A_MINUS,
B_PLUS,
B,
B_MINUS,
C_PLUS,
C,
C_MINUS,
D_PLUS,
D,
E
}
export enum ClearKind {
NONE = 6,
GOOD_COMBO,
GREAT_COMBO,
PERPECT_COMBO,
MARVELOUS_COMBO
}
export interface Score {
collection: "score";
songId: number;
difficulty: Difficulty;
rank: Rank;
clearKind: ClearKind;
score: number;
maxCombo: number;
}

13
ddr@asphyxia/utils.ts Normal file
View File

@ -0,0 +1,13 @@
export function getVersion(info: EamuseInfo) {
const dateCode = parseInt(info.model.split(":")[4]);
if (dateCode >= 2019022600 && dateCode <= 2020020300) return 10;
return 0;
}
export function formatCode(ddrCode: number) {
const pad = (ddrCode + "").padStart(8, "0");
return pad.replace(/^([0-9]{4})([0-9]{4})$/, "$1-$2");
}

View File

@ -0,0 +1,49 @@
$('#change-name').on('click', () => {
const name = $('#dancer_name').val().toUpperCase();
emit('updateName', { refid, name }).then(() => location.reload());
});
$('#change-weight').on('click', () => {
const weight1 = $('#weight_1').val();
const weight2 = $('#weight_2').val();
const weight = weight1 + '.' + weight2;
emit('updateWeight', { refid, weight }).then(() => location.reload());
});
$('#change-display-calories').on('click', () => {
const selected = $('#display_calories option:selected').val();
emit('updateDisplayCalories', { refid, selected }).then(() => location.reload());
});
$('#change-arrow-skin').on('click', () => {
const selected = $('#arrow_skin option:selected').val();
emit('updateArrowSkin', { refid, selected }).then(() => location.reload());
});
$('#change-guideline').on('click', () => {
const selected = $('#guideline option:selected').val();
emit('updateGuideline', { refid, selected }).then(() => location.reload());
});
$('#change-filter').on('click', () => {
const selected = $('#filter option:selected').val();
emit('updateFilter', { refid, selected }).then(() => location.reload());
});
$('#change-judgment-priority').on('click', () => {
const selected = $('#judgment_priority option:selected').val();
emit('updateJudgmentPriority', { refid, selected }).then(() => location.reload());
});
$('#change-display-timing').on('click', () => {
const selected = $('#display_timing option:selected').val();
emit('updateDisplayTiming', { refid, selected }).then(() => location.reload());
});

View File

@ -0,0 +1,147 @@
//DATA//
profile: DB.FindOne(refid, { collection: "profile" })
-
const onOff = [ "Off", "On" ];
const characters = [ "All Character Random", "Man Random", "Female Random", "Yuni", "Rage", "Afro", "Jenny", "Emi", "Baby-Lon", "Gus", "Ruby", "Alice", "Julio", "Bonnie", "Zero", "Rinon" ];
const arrowSkins = [ "Normal", "X", "Classic", "Cyber", "Medium", "Small", "Dot" ];
const guidelines = [ "Off", "Border", "Center" ];
const filters = [ "Off", "Dark", "Darker", "Darkest" ];
const judgmentPrioritys = [ "Judgment priority", "Arrow priority" ];
if (profile.usergamedata)
-
const common = profile.usergamedata.COMMON.strdata.split(",");
const option = profile.usergamedata.OPTION.strdata.split(",");
const name = common[25];
const weight = common[17];
const displayCalories = parseInt(common[3]);
const character = parseInt(common[4]);
const arrowSkin = parseInt(option[11]);
const guideline = parseInt(option[13]);
const filter = parseInt(option[12]);
const judgmentPriority = parseInt(option[15]);
const displayTiming = parseInt(option[16]);
div
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-cog
| Profile Settings
.card-content
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Dancer Name
.field-body
p.control
input.input(type="text", id="dancer_name", pattern="[A-Z]{8}", maxlength=8, value=name)
p.control
a.button.is-primary#change-name Change
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Workout Weight
.field-body
p.control
input.input(type="number", id="weight_1", value=weight.split(".")[0])
p.control
input.input(type="number", id="weight_2", value=weight.split(".")[1])
p.control
a.button.is-primary#change-weight Change
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Workout Display Calories
.field-body
p.control
.select
select#display_calories
if (displayCalories === 1)
option(value=0) Off
option(value=1, selected) On
else
option(value=0, selected) Off
option(value=1) On
p.control
a.button.is-primary#change-display-calories Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Arrow Skin
.field-body
p.control
.select
select#arrow_skin
each v, i in arrowSkins
if (arrowSkin === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-arrow-skin Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Guideline
.field-body
p.control
.select
select#guideline
each v, i in guidelines
if (guideline === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-guideline Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Filter concentration
.field-body
p.control
.select
select#filter
each v, i in filters
if (filter === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-filter Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Judgment display priority
.field-body
p.control
.select
select#judgment_priority
each v, i in judgmentPrioritys
if (judgmentPriority === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-judgment-priority Submit
.field.is-horizontal.has-addons
.field-label.is-normal
label.label Display Timing judgment
.field-body
p.control
.select
select#display_timing
each v, i in ["Off", "On"]
if (displayTiming === i)
option(value=i, selected) #{v}
else
option(value=i) #{v}
p.control
a.button.is-primary#change-display-timing Submit
script(src="static/js/profile_settings.js")

1
gitadora@asphyxia/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
apisamples/

122
gitadora@asphyxia/README.md Normal file
View File

@ -0,0 +1,122 @@
GITADORA Plugin for Asphyxia-Core
=================================
![Version: v1.4.0](https://img.shields.io/badge/version-v1.4.0-blue)
This plugin is based on converted from public-exported Asphyxia's Routes.
Supported Versions
==================
- Tri-Boost Re:EVOLVE
- Matixx
- EXCHAIN
- NEX+AGE
- HIGH-VOLTAGE
- FUZZ-UP
- GALAXY WAVE
When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------
The folder structure between v1.0 and v1.1 is quite different. Do not overwrite plugin folder.
<br>If you encounter errors, Please try these steps:
1. Remove `gitadora@asphyxia` folder.
2. Ctrl-C and Ctrl-V the newest version of `gitadora@asphyxia`
3. (Custom MDB Users) Reupload MDB or move `data/custom_mdb.xml` to `data/mdb/custom.xml`
Known Issues
============
* ~Information dialog keep showing as plugin doesn't store item data currently.~ (Fixed as of version 1.2.1)
* Special Premium Encore on Nextage is unimplemented. However, a workaround is available. Try it.
* Friends and Rivals are unimplemented.
Shared Data Options
===================
Two experimental options allow operators to share data across versions:
* **Shared Favorite Songs** (`shared_favorite_songs`, default: `false`): When enabled, favorite lists are unified across Guitar Freaks, DrumMania, and supported versions.
* **Shared Song Scores** (`shared_song_scores`, default: `false`): When enabled, the server merges the best results for each chart across every stored version and saves them under a shared version identifier. The merged record uses the following shape (fields marked with `//` describe their meaning):
```
scores: {
"<musicid>": {
update: [<seq>, <new_skill>], // Highest new_skill value seen and its associated seq
diffs: {
"<seq>": {
perc: <number>, // Highest achievement percentage
rank: <number>, // Highest rank reached for the chart
clear: <boolean>, // Whether the chart has been cleared
fc: <boolean>, // Whether a full combo was achieved
ex: <boolean>, // Whether an excellent was achieved
meter: "<string>",// Best meter value as a stringified bigint
prog: <number>, // Highest progression value
}
}
}
}
```
Scores are stored under `version: "shared"` but are automatically applied to the active module when loading a profile, ensuring players benefit from their best combined results regardless of the client version.
Release Notes
=============
v1.4.0
----------------
* Added support for Tri-Boost Re:EVOLVE, HIGH-VOLTAGE, FUZZ-UP, GALAXY WAVE
* Bugfix for launch core with "--dev/--console"
v1.3.0
----------------
* Added experimental 'Shared Favorite Songs' option. If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.
* Added a leaderboards page to the WebUI. This page displays the rank of all players per game and version, ordered by Skill rating.
* More code cleanups to Profiles.ts
v1.2.4
----------------
* Fixed note scroll speed defaulting to 0.5x for newly registered profiles.
* Misc code cleanup. No changes expected to plugin behaviour.
v1.2.3
----------------
* Fixed bug preventing MDB files in XML format from loading (Thanks to DualEdge for reporting this ).
v1.2.2
----------------
* Major improvements to the MDB (song data) loader. MDB files can now be in .json, .xml or .b64 format. This applies to both the per-version defaults and custom MDBs. To use a custom MDB, enable it in the web UI, and place a 'custom.xml', 'custom.json' or 'custom.b64' file in the data/mdb subfolder.
* Added several player profile stats to the web UI.
* MDB loader now logs the number of loaded songs available to GF and DM when in dev mode.
* MDB: Fixed "is_secret" field being ignored (always set to false)
v1.2.1
----------------
* Secret Music (unlocked songs) are now saved and loaded correctly. Partially fixes Github issue #34. Note that all songs are already marked as unlocked by the server - there is no need to unlock them manually. If you would like to lock them, consider using a custom MDB.
* Rewards field is now saved and loaded correctly. Fixes Github issue #34
NOTE: Rewards and secret music data is saved at the end of each session, so you will see the unlock notifications one last time after updating the plugin to this version.
v1.2.0
----------------
* Fixed server error when saving profiles for two Guitar Freaks players at the end of a session. Fixes Github issue #39.
* Fixed another server error when two players are present, but only one player is using a profile.
* Added support for the "ranking" field. Gitadora will now correctly display your server ranking (based on Skill) on the post-game screen.
* "Recommended to friends" songs are now saved and loaded correctly. Since you don't have any friends, this won't be terribly useful, but it does at least provide an extra five slots for saving your favourite songs.
* Fixed "Recommended to friends" song list being incorrectly initialized to "I think about you".
* misc: Added logging for profile loading/saving when Asphyxia is running in dev mode.
* misc: Added more logging to mdb (song database) loading.
* misc: Removed some unneeded duplicate code.
* misc: Latest getPlayer() and savePlayers() API requests and responses are now saved to file when Asphyxia is in dev mode. Useful for debugging.
v1.1.1
----------------
* fix: Error when create new profile on exchain.
* fix: last song doesn't work correctly.
* misc: Add logger for tracking problem.
v1.1.0
------
* NEX+AGE Support (Not full support.)
* Restructure bit for maintaining.
v1.0.0
------
* Initial release for public

View File

@ -0,0 +1 @@
export const PLUGIN_VER = 1;

View File

@ -0,0 +1,132 @@
import { getVersion } from "../utils";
interface EncoreStageData {
level: number
musics: number[]
unlock_challenge?: number[]
}
export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
const fallback = { level: 10, musics: [0] }
const level: number = U.GetConfig("encore_version")
const ntDummyEncore = U.GetConfig("nextage_dummy_encore")
switch (getVersion(info)) {
case 'galaxywave':
return {
level,
musics: [
2866, // Calm days
2893, // 愛はToxic! feat.Lilymone
2885, // Astrum
2897, // DESPERATE ERROR
2884, // Multiverse
2919, // DOGMA
2922, // Stay By My Side
2937, // Prog for your Soul
2963, // Zero Visibility
2939, // Hopeful Daybreak!!!
2956, // Over Time Groove
]
}
case 'fuzzup':
return {
level,
musics: [
2812, // THE LAST OF FIREFACE
2814, // ENCOUNT
2783, // Q転直下
2848, // Bloody Iron Maiden
2860, // Serious Joke
2844, // HyperNebula
2877, // AVEL
2892, // Elliptic Orbits
]
}
case 'highvoltage':
return {
level,
musics: [
2686, // CYCLONICxSTORM
2687, // Heptagram
2700, // Saiph
2706, // LUCID NIGHTMARE
2740, // Mobius
2748, // Under The Shades Of The Divine Ray
2772, // REBELLION
2812, // THE LAST OF FIREFACE
]
}
case 'nextage':
return {
level,
musics: !ntDummyEncore ? [
2587, // 悪魔のハニープリン
2531, // The ULTIMATES -reminiscence-
2612, // ECLIPSE 2
2622, // Slip Into My Royal Blood
2686, // CYCLONICxSTORM
// FIXME: Fix special encore.
305, 602, 703, 802, 902, 1003, 1201, 1400, 1712, 1916, 2289, 2631, // DD13 and encores.
1704, 1811, 2121, 2201, 2624, // Soranaki and encores.
1907, 2020, 2282, 2341, 2666 // Stargazer and encores.
] : [
2622, 305, 1704, 1907, 2686 // Dummy.
]
}
case 'exchain':
return {
level,
musics: [
2246, // 箱庭の世界
2498, // Cinnamon
2500, // キヤロラ衛星の軌跡
2529, // グリーンリーフ症候群
2548, // Let's Dance
2587, // 悪魔のハニープリン
5020, // Timepiece phase II (CLASSIC)
5033, // MODEL FT2 Miracle Version (CLASSIC)
2586, // 美麗的夏日風
5060, // EXCELSIOR DIVE (CLASSIC)
2530, // The ULTIMATES -CHRONICLE-
2581, // 幸せの代償
5046, // Rock to Infinity (CLASSIC)
]
}
case 'matixx':
return {
level,
musics: [
2432, // Durian
2445, // ヤオヨロズランズ
2456, // Fate of the Furious
2441, // PIRATES BANQUET
2444, // Aion
2381, // Duella Lyrica
2471, // triangulum
2476, // MODEL FT4
2486, // 煉獄事変
2496, // CAPTURING XANADU
2497, // Physical Decay
2499, // Cinnamon
2498, // けもののおうじゃ★めうめう
]
}
case 're':
return {
level,
musics: [
2341, // Anathema
2384, // White Forest
2393, // REFLEXES MANIPULATION
2392, // 主亡き機械人形のまなざし
2406, // Exclamation
2414, // MEDUSA
2422, // BLACK ROSES
2411, // ギタドラシカ
2432, // Durian
]
}
default:
return fallback
}
}

11
gitadora@asphyxia/data/mdb/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
ex.xml
mt.xml
nt.xml
hv.xml
ex.json
mt.json
nt.json
hv.json
custom.xml
custom.json
blacklist.txt

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,198 @@
import Logger from "../../utils/logger";
import { CommonMusicData } from "../../models/commonmusicdata";
export enum DATAVersion {
GALAXYWAVE = "gw",
FUZZUP = "fz",
HIGHVOLTAGE = "hv",
NEXTAGE = "nt",
EXCHAIN = "ex",
MATTIX = "mt",
TBRE = "re"
}
const allowedFormats = ['.json', '.xml', '.b64']
const mdbFolder = "data/mdb/"
type processRawDataHandler = (path: string) => Promise<CommonMusicData>
const logger = new Logger("mdb")
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readMDBFile(path: string, processHandler?: processRawDataHandler): Promise<CommonMusicData> {
if (!IO.Exists(path)) {
throw "Unable to find MDB file at " + path
}
logger.debugInfo(`Loading MDB data from ${path}.`)
let result : CommonMusicData;
const fileType = path.substring(path.lastIndexOf('.')).toLowerCase()
switch (fileType) {
case '.json':
const str = await IO.ReadFile(path, 'utf-8');
result = JSON.parse(str)
break;
case '.xml':
processHandler = processHandler ?? defaultProcessRawXmlData
result = await processHandler(path)
// Uncomment to save the loaded XML file as JSON.
// await IO.WriteFile(path.replace(".xml", ".json"), JSON.stringify(result))
break;
case '.b64':
const buff = await IO.ReadFile(path, 'utf-8');
const bufferCtor = (globalThis as {
Buffer?: {
from(input: string, encoding: string): { toString(encoding: string): string }
}
}).Buffer
if (!bufferCtor) {
throw new Error('Buffer is not available in the current environment.')
}
const json = bufferCtor.from(buff, 'base64').toString('utf-8')
// Uncomment to save the decoded base64 file as JSON.
// await IO.WriteFile(path.replace(".b64",".json"), json)
result = JSON.parse(json)
break;
default:
throw `Invalid MDB file type: ${fileType}. Only .json, .xml, .b64 are supported.`
}
// Some MDB sources may not provide seq_release_state. Ensure it is present for every song entry.
result.music.forEach((entry) => {
if (entry.seq_release_state == null) {
entry.seq_release_state = K.ITEM('s32', 1)
}
})
let gfCount = result.music.filter((e) => e.cont_gf["@content"][0]).length
let dmCount = result.music.filter((e) => e.cont_dm["@content"][0]).length
logger.debugInfo(`Loaded ${result.music.length} songs from MDB file. ${gfCount} songs for GF, ${dmCount} songs for DM.`)
return result
}
export function gameVerToDataVer(ver: string): DATAVersion {
switch(ver) {
case 'galaxywave':
return DATAVersion.GALAXYWAVE
case 'fuzzup':
return DATAVersion.FUZZUP
case 'highvoltage':
return DATAVersion.HIGHVOLTAGE
case 'nextage':
return DATAVersion.NEXTAGE
case 'exchain':
return DATAVersion.EXCHAIN
case 'matixx':
return DATAVersion.MATTIX
default:
return DATAVersion.TBRE
}
}
/**
* Attempts to find a .json, .xml, or .b64 file (in that order) matching the given name in the specified folder.
* @param fileNameWithoutExtension - The name of the file to find (without the extension).
* @param path - The path to the folder to search. If left null, the default MDB folder ('data/mdb' in the plugin folder) will be used.
* @returns - The path of the first matching file found, or null if no file was found.
*/
export function findMDBFile(fileNameWithoutExtension: string, path: string = null): string {
path = path ?? mdbFolder
if (!IO.Exists(path)) {
throw `Path does not exist: ${path}`
}
if (!path.endsWith("/")) {
path += "/"
}
for (const ext of allowedFormats) {
const candidateFileNames = ext === ".xml"
? [
`mdb_${fileNameWithoutExtension}${ext}`,
`${fileNameWithoutExtension}${ext}`,
]
: [`${fileNameWithoutExtension}${ext}`]
for (const fileName of candidateFileNames) {
const filePath = path + fileName
if (IO.Exists(filePath)) {
return filePath
}
}
}
return null
}
export async function loadSongsForGameVersion(gameVer: string, processHandler?: processRawDataHandler) {
const ver = gameVerToDataVer(gameVer)
let mdbFile = findMDBFile(ver, mdbFolder)
if (mdbFile == null) {
throw `No valid MDB files were found in the data/mdb subfolder. Ensure that this folder contains at least one of the following: ${ver}.json, mdb_${ver}.xml (${ver}.xml as fallback) or ${ver}.b64`
}
const music = await readMDBFile(mdbFile, processHandler ?? defaultProcessRawXmlData)
return music
}
export async function defaultProcessRawXmlData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', m.number("is_secret", 0)),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver", 255)),
seq_release_state: K.ITEM('s32', 1),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
import { Extra } from "../models/extra";
import { FavoriteMusic } from "../models/favoritemusic";
import { isSharedFavoriteMusicEnabled } from "../utils/index";
import Logger from "../utils/logger";
const logger = new Logger("FavoriteMusic");
/**
* Extracts favorite music data from the given extra data container, and saves it to the database as shared favorite music data for the player with the given refid.
* This function has no effect if the 'Shared favorite music' option is not enabled.
* Note that shared favorite music is shared across both Guitar Freaks and Drummania, as well as all supported versions of the game.
* @param refid The refid of the player.
* @param extra The extra data container of the player, containing the favorite music lists to be saved.
* @returns {boolean} - whether the favorite music data was successfully saved.
*/
export async function saveSharedFavoriteMusicFromExtra(refid: string, extra: Extra) : Promise<boolean>
{
if (!isSharedFavoriteMusicEnabled()) {
return false
}
let result : FavoriteMusic = {
collection: 'favoritemusic',
pluginVer: 1,
list_1: extra.list_1,
list_2: extra.list_2,
list_3: extra.list_3,
recommend_musicid_list: extra.recommend_musicid_list,
}
try
{
await saveFavoriteMusic(refid, result)
logger.debugInfo(`Saved shared favorite music for profile ${refid} successfully.`);
return true
}
catch (e)
{
logger.error(`Failed to save shared favorite music for profile ${refid}.`);
logger.error(e);
return false
}
}
/**
* Retrieves shared favorite music data from the database for the player with the given refid, and applies the data to the provided extra data container.
* This function has no effect if the 'Shared favorite music' option is not enabled.
* Note that shared favorite music is shared across both Guitar Freaks and Drummania, as well as all supported versions of the game.
* @param refid - The refid of the player.
* @param extra - The destination object where favorite music data should be applied.
*/
export async function applySharedFavoriteMusicToExtra(refid : string, extra : Extra) : Promise<void>
{
if (!isSharedFavoriteMusicEnabled()) {
return
}
try
{
let favoriteMusic = await loadFavoriteMusic(refid)
if (favoriteMusic == null) {
return
}
extra.list_1 = favoriteMusic.list_1
extra.list_2 = favoriteMusic.list_2
extra.list_3 = favoriteMusic.list_3
extra.recommend_musicid_list = favoriteMusic.recommend_musicid_list
logger.debugInfo(`Loaded shared favorite music for profile ${refid} successfully.`);
}
catch (e)
{
logger.error(`Failed to load shared favorite music for profile ${refid}.`);
logger.error(e);
}
}
export async function saveFavoriteMusic(refid: string, data : FavoriteMusic) : Promise<any>
{
return await DB.Upsert<FavoriteMusic>(refid, {
collection: 'favoritemusic',
}, data)
}
export async function loadFavoriteMusic(refid : string) : Promise<FavoriteMusic | null>
{
const favoriteMusic = await DB.FindOne<FavoriteMusic>(refid, {
collection: 'favoritemusic'
})
if (!favoriteMusic) {
logger.debugInfo(`No shared favourite music available for profile ${refid}. Using game specific favorites. Favorites will be saved as shared favorites at the end of the game session.`);
return null
}
return favoriteMusic
}

View File

@ -0,0 +1,41 @@
import { getVersion } from "../utils";
import { findMDBFile, readMDBFile, loadSongsForGameVersion } from "../data/mdb";
import { CommonMusicDataField } from "../models/commonmusicdata";
import Logger from "../utils/logger"
import { getPlayableMusicResponse, PlayableMusicResponse } from "../models/Responses/playablemusicresponse";
import { isAsphyxiaDebugMode } from "../utils/index";
const logger = new Logger("MusicList")
export const playableMusic: EPR = async (info, data, send) => {
const version = getVersion(info);
const start = Date.now()
let music: CommonMusicDataField[] = [];
try {
if (U.GetConfig("enable_custom_mdb")) {
let customMdb = findMDBFile("custom")
music = (await readMDBFile(customMdb)).music
}
} catch (e) {
logger.warn("Read Custom MDB failed. Using default MDB as a fallback.")
logger.debugWarn(e.stack);
music = [];
}
if (music.length == 0) {
music = (await loadSongsForGameVersion(version)).music
}
const end = Date.now()
const timeDiff = end - start
logger.debugInfo(`MDB loading took ${timeDiff} ms`)
let response : PlayableMusicResponse = getPlayableMusicResponse(music)
await send.object(response)
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/playableMusicList.json`, JSON.stringify(music, null, 4))
}
};

View File

@ -0,0 +1,90 @@
import { PLUGIN_VER } from "../const";
import { Scores } from "../models/scores";
type ScoreDiff = Scores['scores'][string]['diffs'][string];
type ScoreEntry = Scores['scores'][string];
function selectBetterMeter(existing?: string, incoming?: string): string {
if (!incoming) return existing ?? "0";
if (!existing) return incoming;
try {
return BigInt(incoming) > BigInt(existing) ? incoming : existing;
} catch (e) {
return incoming || existing;
}
}
function mergeScoreDiff(existing: ScoreDiff | undefined, incoming: ScoreDiff): ScoreDiff {
if (!existing) return incoming;
return {
perc: Math.max(existing.perc ?? 0, incoming.perc ?? 0),
rank: Math.max(existing.rank ?? 0, incoming.rank ?? 0),
meter: selectBetterMeter(existing.meter, incoming.meter),
prog: Math.max(existing.prog ?? 0, incoming.prog ?? 0),
clear: (existing.clear ?? false) || (incoming.clear ?? false),
fc: (existing.fc ?? false) || (incoming.fc ?? false),
ex: (existing.ex ?? false) || (incoming.ex ?? false),
};
}
function mergeScoreEntry(existing: ScoreEntry | undefined, incoming: ScoreEntry): ScoreEntry {
const mergedDiffs: ScoreEntry['diffs'] = existing ? { ...existing.diffs } : {};
for (const [seq, diff] of Object.entries(incoming.diffs)) {
mergedDiffs[seq] = mergeScoreDiff(mergedDiffs[seq], diff);
}
const mergedUpdate = existing?.update ? [...existing.update] : [0, 0];
if (incoming.update && (mergedUpdate[1] ?? 0) < incoming.update[1]) {
mergedUpdate[0] = incoming.update[0];
mergedUpdate[1] = incoming.update[1];
}
return {
update: mergedUpdate,
diffs: mergedDiffs,
};
}
function mergeScoreCollections(target: Scores['scores'], incoming: Scores['scores']): Scores['scores'] {
const merged = { ...target } as Scores['scores'];
for (const [mid, entry] of Object.entries(incoming)) {
merged[mid] = mergeScoreEntry(merged[mid], entry);
}
return merged;
}
async function persistSharedScores(refid: string, game: 'gf' | 'dm', scores: Scores['scores']) {
await DB.Upsert<Scores>(refid, { collection: 'scores', game, version: 'shared' }, {
collection: 'scores',
version: 'shared',
pluginVer: PLUGIN_VER,
game,
scores,
});
}
/**
* Load and merge scores across all versions for a player/game pair and persist them under version "shared".
*/
export async function getMergedSharedScores(refid: string, game: 'gf' | 'dm'): Promise<Scores['scores']> {
const scoreDocs = await DB.Find<Scores>(refid, { collection: 'scores', game });
const mergedScores = scoreDocs.reduce<Scores['scores']>((acc, doc) => mergeScoreCollections(acc, doc.scores), {} as Scores['scores']);
await persistSharedScores(refid, game, mergedScores);
return mergedScores;
}
/**
* Merge the provided score set into the shared scores document for the player/game pair.
*/
export async function mergeScoresIntoShared(refid: string, game: 'gf' | 'dm', scores: Scores['scores']) {
const existingShared = await DB.FindOne<Scores>(refid, { collection: 'scores', game, version: 'shared' });
const mergedScores = mergeScoreCollections(existingShared?.scores ?? {}, scores);
await persistSharedScores(refid, game, mergedScores);
}

View File

@ -0,0 +1,492 @@
/// <reference lib="es2020.bigint" />
import { getEncoreStageData } from "../data/extrastage";
import Logger from "../utils/logger";
import { getVersion } from "../utils";
const logger = new Logger('info');
export const shopInfoRegist: EPR = async (info, data, send) => {
send.object({
data: {
cabid: K.ITEM('u32', 1),
locationid: K.ITEM('str', 'Asphyxia'),
},
temperature: {
is_send: K.ITEM('bool', 0),
},
tax: {
tax_phase: K.ITEM('s32', 0),
},
})
}
export const gameInfoGet: EPR = async (info, data, send) => {
const eventData = getEventDataResponse()
const extraData = getEncoreStageData(info)
const VER = getVersion(info)
if (VER == "galaxywave"){
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('s32', extraData.level),
extramusic: {
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
get_border: K.ITEM('u8', 0),
}
})
}
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('s32', 0) },
battle: { term: K.ITEM('s32', 0) },
battle_chara: { term: K.ITEM('s32', 0) },
data_ver_limit: { term: K.ITEM('s32', 0) },
ea_pass_propel: { term: K.ITEM('s32', 0) },
monthly_skill: {
term: K.ITEM('u8', 0),
target_music: {
music: {
musicid: K.ITEM('s32', 0),
},
},
},
update_prog: { term: K.ITEM('s32', 0) },
rockwave: {
event_list: {
event: {
data_id: K.ITEM('s32', 0),
data_version: K.ITEM('s32', 0),
event_id: K.ITEM('s32', 0),
event_type: K.ITEM('s32', 0),
start_date: K.ITEM('u64', BigInt(0)),
end_date: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
bg_no: K.ITEM('s32', 0),
target_musicid: K.ITEM('s32', 0),
clear_border: K.ITEM('s32', 0),
reward_musicid: K.ITEM('s32', 0),
reward_musicid_border_list: K.ITEM('s32', 0),
reward_stickerid: K.ITEM('s32', 0),
reward_stickerid_list: K.ITEM('s32', 0),
reward_stickerid_border_list: K.ITEM('s32', 0),
firstbit: K.ITEM('s32', 0),
quest_no: K.ITEM('s32', 0),
target_music_list: {
music: {
musicid: K.ITEM('s32', 0),
}
},
ranking_list: K.ITEM('u64', BigInt(0)),
}
}
},
general_term: {
termdata: {
type: K.ITEM('str', ''),
term: K.ITEM('s32', 0),
state: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
jubeat_omiyage_challenge: {},
kac2017: {},
nostalgia_concert: {},
trbitemdata: {},
ctrl_movie: {},
ng_jacket: {},
ng_recommend_music: {},
ranking: {
skill_0_999: {},
skill_1000_1499: {},
skill_1500_1999: {},
skill_2000_2499: {},
skill_2500_2999: {},
skill_3000_3499: {},
skill_3500_3999: {},
skill_4000_4499: {},
skill_4500_4999: {},
skill_5000_5499: {},
skill_5500_5999: {},
skill_6000_6499: {},
skill_6500_6999: {},
skill_7000_7499: {},
skill_7500_7999: {},
skill_8000_8499: {},
skill_8500_9999: {},
total: {},
original: {},
bemani: {},
famous: {},
anime: {},
band: {},
western: {},
},
processing_report_state: K.ITEM('u8', 0),
assert_report_state: K.ITEM('u8', 0),
recommendmusic: { '@attr': { nr: 0 } },
demomusic: { '@attr': { nr: 0 } },
event_skill: {},
temperature: { is_send: K.ITEM('bool', 0) },
bemani_summer_2018: { is_open: K.ITEM('bool', 0) },
kac2018: {
event: {
term: K.ITEM('s32', 0),
since: K.ITEM('u64', BigInt(0)),
till: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
target_music: {
music_id: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
},
...eventData,
});
} else if (VER == "fuzzup"){
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
get_border: K.ITEM('u8', 0),
}
})
}
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('u8', 0) },
battle: { term: K.ITEM('u8', 0) },
battle_chara: { term: K.ITEM('u8', 0) },
data_ver_limit: { term: K.ITEM('s32', 0) },
ea_pass_propel: { term: K.ITEM('u8', 0) },
monthly_skill: {
term: K.ITEM('u8', 0),
target_music: {
music: {
musicid: K.ITEM('s32', 0),
},
},
},
update_prog: { term: K.ITEM('u8', 0) },
rockwave: { event_list: {} },
general_term: {},
jubeat_omiyage_challenge: {},
kac2017: {},
nostalgia_concert: {},
trbitemdata: {},
ctrl_movie: {},
ng_jacket: {},
ng_recommend_music: {},
ranking: {
skill_0_999: {},
skill_1000_1499: {},
skill_1500_1999: {},
skill_2000_2499: {},
skill_2500_2999: {},
skill_3000_3499: {},
skill_3500_3999: {},
skill_4000_4499: {},
skill_4500_4999: {},
skill_5000_5499: {},
skill_5500_5999: {},
skill_6000_6499: {},
skill_6500_6999: {},
skill_7000_7499: {},
skill_7500_7999: {},
skill_8000_8499: {},
skill_8500_9999: {},
total: {},
original: {},
bemani: {},
famous: {},
anime: {},
band: {},
western: {},
},
processing_report_state: K.ITEM('u8', 0),
assert_report_state: K.ITEM('u8', 0),
recommendmusic: { '@attr': { nr: 0 } },
demomusic: { '@attr': { nr: 0 } },
event_skill: {},
temperature: { is_send: K.ITEM('bool', 0) },
bemani_summer_2018: { is_open: K.ITEM('bool', 0) },
kac2018: {
event: {
term: K.ITEM('s32', 0),
since: K.ITEM('u64', BigInt(0)),
till: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
target_music: {
music_id: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
},
livehouse: {
event_list: {
event: {
is_open: K.ITEM('bool', 0),
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
livehouse_name: K.ITEM('str', 'Asphyxia'),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
},
},
requirements_musicid: K.ITEM('s32', 0),
member_table: K.ITEM('s32', 0),
},
},
bonus: {
term: K.ITEM('u8', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
},
},
...eventData,
});
}//Older
else {
await send.object({
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
get_border: K.ITEM('u8', 0),
}
})
}
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('u8', 0) },
battle: { term: K.ITEM('u8', 0) },
battle_chara: { term: K.ITEM('u8', 0) },
data_ver_limit: { term: K.ITEM('u8', 0) },
ea_pass_propel: { term: K.ITEM('u8', 0) },
monthly_skill: {
term: K.ITEM('u8', 0),
target_music: {
music: {
musicid: K.ITEM('s32', 0),
},
},
},
update_prog: { term: K.ITEM('u8', 0) },
rockwave: { event_list: {} },
general_term: {},
jubeat_omiyage_challenge: {},
kac2017: {},
nostalgia_concert: {},
trbitemdata: {},
ctrl_movie: {},
ng_jacket: {},
ng_recommend_music: {},
ranking: {
skill_0_999: {},
skill_1000_1499: {},
skill_1500_1999: {},
skill_2000_2499: {},
skill_2500_2999: {},
skill_3000_3499: {},
skill_3500_3999: {},
skill_4000_4499: {},
skill_4500_4999: {},
skill_5000_5499: {},
skill_5500_5999: {},
skill_6000_6499: {},
skill_6500_6999: {},
skill_7000_7499: {},
skill_7500_7999: {},
skill_8000_8499: {},
skill_8500_9999: {},
total: {},
original: {},
bemani: {},
famous: {},
anime: {},
band: {},
western: {},
},
processing_report_state: K.ITEM('u8', 0),
assert_report_state: K.ITEM('u8', 0),
recommendmusic: { '@attr': { nr: 0 } },
demomusic: { '@attr': { nr: 0 } },
event_skill: {},
temperature: { is_send: K.ITEM('bool', 0) },
bemani_summer_2018: { is_open: K.ITEM('bool', 0) },
kac2018: {
event: {
term: K.ITEM('s32', 0),
since: K.ITEM('u64', BigInt(0)),
till: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
target_music: {
music_id: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
},
KAC2016: {
is_entry: K.ITEM('bool', 0),
term: K.ITEM('u8', 0),
musicid: K.ITEM('s32', 0),
},
KAC2016_skill_ranking: { term: K.ITEM('u8', 0) },
season_sticker: { term: K.ITEM('u8', 0) },
paseli_point_lose: { term: K.ITEM('u8', 0) },
nostal_link: { term: K.ITEM('u8', 0) },
encore_advent: { term: K.ITEM('u8', 0) },
sdvx_stamprally: { term: K.ITEM('u8', 0) },
sdvx_stamprally2: { term: K.ITEM('u8', 0) },
floor_policy_2_info: { term: K.ITEM('u8', 0) },
long_otobear_fes_2: {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
},
...eventData,
});
}
};
function getEventDataResponse() {
const addition: any = {
monstar_subjugation: {
bonus_musicid: K.ITEM('s32', 0),
},
bear_fes: {},
nextadium: {},
galaxy_parade: {
corner_list: {
corner: {
is_open: K.ITEM('bool', 0),
data_ver: K.ITEM('s32', 0),
genre: K.ITEM('s32', 0),
corner_id: K.ITEM('s32', 0),
corner_name: K.ITEM('str', ''),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
requirements_musicid: K.ITEM('s32', 0),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
}
},
}
},
gacha_table: {
chara_odds: {
chara_id: K.ITEM('s32', 0),
odds: K.ITEM('s32', 0),
}
},
bonus: {
term: K.ITEM('s32', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
};
const time = BigInt(31536000);
for (let i = 1; i <= 20; ++i) {
const obj = {
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', time),
end_date_ms: K.ITEM('u64', time),
};
if (i == 1) {
addition[`phrase_combo_challenge`] = obj;
addition[`long_otobear_fes_1`] = {
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', time),
end_date_ms: K.ITEM('u64', time),
//bonus_musicid: {},
bonus_musicid: K.ITEM('s32', 0),
};
addition[`sdvx_stamprally3`] = obj;
addition[`chronicle_1`] = obj;
addition[`paseli_point_lottery`] = obj;
addition['sticker_campaign'] = {
term: K.ITEM('u8', 0),
sticker_list: {},
};
addition['thanksgiving'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
addition['lotterybox'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
if (i <= 4) {
addition['monstar_subjugation'][`monstar_subjugation_${i}`] = obj;
addition['bear_fes'][`bear_fes_${i}`] = obj;
}
if (i <= 2) {
addition[`gitadora_oracle_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`gitadora_oracle_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
}
if (i <= 3) {
addition[`kouyou_challenge_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`dokidoki_valentine2_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`wakuteka_whiteday2_${i}`] = { term: K.ITEM('u8', 0) };
addition[`ohanami_challenge_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`otobear_in_the_tsubo_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
addition[`summer_craft_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
}
}
return addition
}

View File

@ -0,0 +1,885 @@
/// <reference lib="es2020.bigint" />
import { getDefaultPlayerInfo, PlayerInfo } from "../models/playerinfo";
import { PlayerRanking } from "../models/playerranking";
import { getDefaultProfile, Profile } from "../models/profile";
import { getDefaultRecord, Record } from "../models/record";
import { Extra, getDefaultExtra } from "../models/extra";
import { getVersion, isDM } from "../utils";
import { getDefaultScores, Scores } from "../models/scores";
import { PLUGIN_VER } from "../const";
import Logger from "../utils/logger"
import { isAsphyxiaDebugMode, isSharedSongScoresEnabled } from "../utils/index";
import { SecretMusicEntry } from "../models/secretmusicentry";
import { CheckPlayerResponse, getCheckPlayerResponse } from "../models/Responses/checkplayerresponse";
import { getPlayerStickerResponse, PlayerStickerResponse } from "../models/Responses/playerstickerresponse";
import { getSecretMusicResponse, SecretMusicResponse } from "../models/Responses/secretmusicresponse";
import { getSaveProfileResponse } from "../models/Responses/saveprofileresponse";
import { getDefaultBattleDataResponse } from "../models/Responses/battledataresponse";
import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic";
import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse";
import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse";
import { getMergedSharedScores, mergeScoresIntoShared } from "./SharedScores";
const logger = new Logger("profiles")
export const regist: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info);
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no);
await send.object({
player: K.ATTR({ no: `${no}` }, {
is_succession: K.ITEM("bool", 0), //FIX THIS with upsert result.
did: K.ITEM("s32", playerInfo.id)
})
})
}
export const check: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info)
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no)
const result : CheckPlayerResponse = getCheckPlayerResponse(no, playerInfo.name, playerInfo.id)
await send.object(result)
}
export const getPlayer: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info);
const time = BigInt(31536000);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`)
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
const dmProfile = await getProfile(refid, version, 'dm')
const gfProfile = await getProfile(refid, version, 'gf')
const dmRecord = await getRecord(refid, version, 'dm')
const gfRecord = await getRecord(refid, version, 'gf')
const dmExtra = await getExtra(refid, version, 'dm')
const gfExtra = await getExtra(refid, version, 'gf')
const dmScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'dm') : (await getScore(refid, version, 'dm')).scores
const gfScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'gf') : (await getScore(refid, version, 'gf')).scores
const profile = dm ? dmProfile : gfProfile;
const extra = dm ? dmExtra : gfExtra;
await applySharedFavoriteMusicToExtra(refid, extra)
const record: any = {
gf: getPlayerRecordResponse(gfProfile, gfRecord),
dm: getPlayerRecordResponse(dmProfile, dmRecord),
};
// Format scores
const musicdata = [];
const scores = dm ? dmScores : gfScores;
for (const [musicid, score] of _.entries(scores)) {
musicdata.push(K.ATTR({ musicid }, {
mdata: K.ARRAY('s16', [
-1,
_.get(score, 'diffs.1.clear', false) ? _.get(score, 'diffs.1.perc', -2) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.perc', -2) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.perc', -2) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.perc', -2) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.perc', -2) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.perc', -2) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.perc', -2) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.perc', -2) : -1,
_.get(score, 'diffs.1.clear', false) ? _.get(score, 'diffs.1.rank', 0) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.rank', 0) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.rank', 0) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.rank', 0) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.rank', 0) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.rank', 0) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.rank', 0) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.rank', 0) : -1,
0,
0,
0,
]),
flag: K.ARRAY('u16', [
_.get(score, 'diffs.1.fc', false) * 2 +
_.get(score, 'diffs.2.fc', false) * 4 +
_.get(score, 'diffs.3.fc', false) * 8 +
_.get(score, 'diffs.4.fc', false) * 16 +
_.get(score, 'diffs.5.fc', false) * 32 +
_.get(score, 'diffs.6.fc', false) * 64 +
_.get(score, 'diffs.7.fc', false) * 128 +
_.get(score, 'diffs.8.fc', false) * 256,
_.get(score, 'diffs.1.ex', false) * 2 +
_.get(score, 'diffs.2.ex', false) * 4 +
_.get(score, 'diffs.3.ex', false) * 8 +
_.get(score, 'diffs.4.ex', false) * 16 +
_.get(score, 'diffs.5.ex', false) * 32 +
_.get(score, 'diffs.6.ex', false) * 64 +
_.get(score, 'diffs.7.ex', false) * 128 +
_.get(score, 'diffs.8.ex', false) * 256,
_.get(score, 'diffs.1.clear', false) * 2 +
_.get(score, 'diffs.2.clear', false) * 4 +
_.get(score, 'diffs.3.clear', false) * 8 +
_.get(score, 'diffs.4.clear', false) * 16 +
_.get(score, 'diffs.5.clear', false) * 32 +
_.get(score, 'diffs.6.clear', false) * 64 +
_.get(score, 'diffs.7.clear', false) * 128 +
_.get(score, 'diffs.8.clear', false) * 256,
0,
0,
]),
sdata: K.ARRAY('s16', score.update),
meter: K.ARRAY('u64', [
BigInt(_.get(score, 'diffs.1.meter', '0')),
BigInt(_.get(score, 'diffs.2.meter', '0')),
BigInt(_.get(score, 'diffs.3.meter', '0')),
BigInt(_.get(score, 'diffs.4.meter', '0')),
BigInt(_.get(score, 'diffs.5.meter', '0')),
BigInt(_.get(score, 'diffs.6.meter', '0')),
BigInt(_.get(score, 'diffs.7.meter', '0')),
BigInt(_.get(score, 'diffs.8.meter', '0')),
]),
meter_prog: K.ARRAY('s16', [
_.get(score, 'diffs.1.prog', 0),
_.get(score, 'diffs.2.prog', 0),
_.get(score, 'diffs.3.prog', 0),
_.get(score, 'diffs.4.prog', 0),
_.get(score, 'diffs.5.prog', 0),
_.get(score, 'diffs.6.prog', 0),
_.get(score, 'diffs.7.prog', 0),
_.get(score, 'diffs.8.prog', 0),
]),
}));
}
const sticker: PlayerStickerResponse[] = getPlayerStickerResponse(name.card);
const playinfo: PlayerPlayInfoResponse = getPlayerPlayInfoResponse(profile);
const playerData: any = {
playerboard: {
index: K.ITEM('s32', 1),
is_active: K.ITEM('bool', _.isArray(name.card) ? 1 : 0),
sticker,
},
player_info: {
player_type: K.ITEM('s8', 0),
did: K.ITEM('s32', 13376666),
name: K.ITEM('str', name.name),
title: K.ITEM('str', name.title),
charaid: K.ITEM('s32', 0),
},
customdata: {
playstyle: K.ARRAY('s32', extra.playstyle),
custom: K.ARRAY('s32', extra.custom),
},
playinfo: playinfo,
tutorial: {
progress: K.ITEM('s32', profile.progress),
disp_state: K.ITEM('u32', profile.disp_state),
},
skilldata: {
skill: K.ITEM('s32', profile.skill),
all_skill: K.ITEM('s32', profile.all_skill),
old_skill: K.ITEM('s32', 0),
old_all_skill: K.ITEM('s32', 0),
},
favoritemusic: {
list_1: K.ARRAY('s32', extra.list_1),
list_2: K.ARRAY('s32', extra.list_2),
list_3: K.ARRAY('s32', extra.list_3),
},
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
record,
groove: {
extra_gauge: K.ITEM('s32', (profile.extra_gauge+95)),
encore_gauge: K.ITEM('s32', profile.encore_gauge),
encore_cnt: K.ITEM('s32', profile.encore_cnt),
encore_success: K.ITEM('s32', profile.encore_success),
unlock_point: K.ITEM('s32', profile.unlock_point),
},
musiclist: { '@attr': { nr: musicdata.length }, musicdata },
deluxe: {
deluxe_content: K.ITEM('s32', 0),
target_id: K.ITEM('s32', 0),
multiply: K.ITEM('s32', 0),
point: K.ITEM('s32', 0),
},
galaxy_parade: {
score_list: {},
last_corner_id: K.ITEM('s32', 0),
chara_list: {},
last_sort_category: K.ITEM('s32', 0),
last_sort_order: K.ITEM('s32', 0),
team_member: {
chara_id_guitar: K.ITEM('s32', 0),
chara_id_bass: K.ITEM('s32', 0),
chara_id_drum: K.ITEM('s32', 0),
chara_id_free1: K.ITEM('s32', 0),
chara_id_free2: K.ITEM('s32', 0),
},
},
};
const playerRanking = await getPlayerRanking(refid, version, game)
const addition: any = {
monstar_subjugation: {},
bear_fes: {},
galaxy_parade: {
corner_list: {
corner: {
is_open: K.ITEM('bool', 0),
data_ver: K.ITEM('s32', 0),
genre: K.ITEM('s32', 0),
corner_id: K.ITEM('s32', 0),
corner_name: K.ITEM('str', ''),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
requirements_musicid: K.ITEM('s32', 0),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
}
},
}
},
gacha_table: {
chara_odds: {
chara_id: K.ITEM('s32', 0),
odds: K.ITEM('s32', 0),
}
},
bonus: {
term: K.ITEM('s32', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
};
for (let i = 1; i <= 20; ++i) {
const obj = { point: K.ITEM('s32', 0) };
if (i == 1) {
addition['long_otobear_fes_1'] = obj;
addition['long_otobear_fes_2'] = obj;
addition['phrase_combo_challenge'] = obj;
addition['sdvx_stamprally'] = obj;
addition['sdvx_stamprally2'] = obj;
addition['sdvx_stamprally3'] = obj;
addition['chronicle_1'] = obj;
addition['gitadora_oracle_1'] = obj;
addition['gitadora_oracle_2'] = obj;
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
if (i <= 4) {
addition.bear_fes[`bear_fes_${i}`] = {
stage: K.ITEM('s32', 0),
point: K.ARRAY('s32', [0, 0, 0, 0, 0, 0, 0, 0]),
};
}
if (i <= 3) {
addition.monstar_subjugation[`monstar_subjugation_${i}`] = {
stage: K.ITEM('s32', 0),
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
};
addition[`kouyou_challenge_${i}`] = { point: K.ITEM('s32', 0) };
addition[`dokidoki_valentine2_${i}`] = { point: K.ITEM('s32', 0) };
addition[`ohanami_challenge_${i}`] = { point: K.ITEM('s32', 0) };
addition[`otobear_in_the_tsubo_${i}`] = { point: K.ITEM('s32', 0) };
addition[`summer_craft_${i}`] = { point: K.ITEM('s32', 0) };
addition[`wakuteka_whiteday2_${i}`] = {
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
};
}
}
const innerSecretMusic = getSecretMusicResponse(profile)
const innerFriendData = getFriendDataResponse(profile)
const innerBattleData = getDefaultBattleDataResponse()
const response = {
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: {
music: innerSecretMusic
},
chara_list: {},
title_parts: {},
information: {
info: K.ARRAY('u32', Array(50).fill(0)),
},
reward: {
status: K.ARRAY('u32', extra.reward_status ?? Array(50).fill(0)),
},
rivaldata: {},
frienddata: {
friend: innerFriendData
},
thanks_medal: {
medal: K.ITEM('s32', 0),
grant_medal: K.ITEM('s32', 0),
grant_total_medal: K.ITEM('s32', 0),
},
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
skindata: {
skin: K.ARRAY('u32', Array(100).fill(-1)),
},
battledata: innerBattleData,
is_free_ok: K.ITEM('bool', 0),
ranking: {
skill: { rank: K.ITEM('s32', playerRanking.skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
all_skill: { rank: K.ITEM('s32', playerRanking.all_skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
},
stage_result: {},
monthly_skill: {},
event_skill: {
skill: K.ITEM('s32', 0),
ranking: {
rank: K.ITEM('s32', 0),
total_nr: K.ITEM('s32', 0),
},
eventlist: {},
},
event_score: { eventlist: {} },
rockwave: { score_list: {} },
livehouse: {
score_list: {
score: {
term: K.ITEM('u8', -1),
reward_id: K.ITEM('s32', -1),
unlock_point: K.ITEM('s32', -1),
chara_id_guitar: K.ITEM('s32', -1),
chara_id_bass: K.ITEM('s32', -1),
chara_id_drum: K.ITEM('s32', -1),
chara_id_other: K.ITEM('s32', -1),
leader: K.ITEM('s32', -1),
},
last_livehouse: K.ITEM('s32', -1),
}
},
jubeat_omiyage_challenge: {},
light_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
standard_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
delux_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
kac2018: {
entry_status: K.ITEM('s32', 0),
data: {
term: K.ITEM('s32', 0),
total_score: K.ITEM('s32', 0),
score: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
music_type: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
play_count: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
sticker_campaign: {},
kac2017: {
entry_status: K.ITEM('s32', 0),
},
KAC2016: {
is_entry: K.ITEM('bool', 0),
},
KAC2016_skill_ranking: {
skill: {
skill: K.ITEM('s32', -1),
rank: K.ITEM('s32', -1),
total_nr: K.ITEM('s32', -1),
}
},
nostalgia_concert: {},
bemani_summer_2018: {
linkage_id: K.ITEM('s32', -1),
is_entry: K.ITEM('bool', 0),
target_music_idx: K.ITEM('s32', -1),
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
point_4: K.ITEM('s32', 0),
point_5: K.ITEM('s32', 0),
point_6: K.ITEM('s32', 0),
point_7: K.ITEM('s32', 0),
reward_1: K.ITEM('bool', 0),
reward_2: K.ITEM('bool', 0),
reward_3: K.ITEM('bool', 0),
reward_4: K.ITEM('bool', 0),
reward_5: K.ITEM('bool', 0),
reward_6: K.ITEM('bool', 0),
reward_7: K.ITEM('bool', 0),
unlock_status_1: K.ITEM('s32', 0),
unlock_status_2: K.ITEM('s32', 0),
unlock_status_3: K.ITEM('s32', 0),
unlock_status_4: K.ITEM('s32', 0),
unlock_status_5: K.ITEM('s32', 0),
unlock_status_6: K.ITEM('s32', 0),
unlock_status_7: K.ITEM('s32', 0),
},
thanksgiving: {
term: K.ITEM("u8", 0),
score: {
one_day_play_cnt: K.ITEM("s32", 0),
one_day_lottery_cnt: K.ITEM("s32", 0),
lucky_star: K.ITEM("s32", 0),
bear_mark: K.ITEM("s32", 0),
play_date_ms: K.ITEM("u64", BigInt(0))
},
lottery_result: {
unlock_bit: K.ITEM("u64", BigInt(0))
}
},
lotterybox: {},
...addition,
...playerData,
finish: K.ITEM('bool', 1),
}),
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastGetPlayerRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastGetPlayerResponse.json`, JSON.stringify(response, null, 4))
}
send.object(response);
}
async function getOrRegisterPlayerInfo(refid: string, version: string, no: number) {
let playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
});
if (!playerInfo) {
logger.debugInfo(`Registering new profile for player ${no} with refid: ${refid}`);
playerInfo = await registerUser(refid, version);
}
return playerInfo;
}
function getPlayerNo(data: any): number {
return parseInt($(data).attr("player").no || '1', 10)
}
async function registerUser(refid: string, version: string, id = _.random(0, 99999999)) {
while (await DB.FindOne<Profile>(null, { collection: 'profile', id })) {
id = _.random(0, 99999999);
}
const defaultInfo: PlayerInfo = getDefaultPlayerInfo(version, id)
const gf = { game: 'gf', version };
const dm = { game: 'dm', version };
await DB.Upsert(refid, { collection: 'playerinfo', version }, defaultInfo);
await DB.Upsert(refid, { collection: 'profile', ...gf }, getDefaultProfile('gf', version, id));
await DB.Upsert(refid, { collection: 'profile', ...dm }, getDefaultProfile('dm', version, id));
await DB.Upsert(refid, { collection: 'record', ...gf }, getDefaultRecord('gf', version));
await DB.Upsert(refid, { collection: 'record', ...dm }, getDefaultRecord('dm', version));
await DB.Upsert(refid, { collection: 'extra', ...gf }, getDefaultExtra('gf', version, id));
await DB.Upsert(refid, { collection: 'extra', ...dm }, getDefaultExtra('dm', version, id));
await DB.Upsert(refid, { collection: 'scores', ...gf }, getDefaultScores('gf', version));
await DB.Upsert(refid, { collection: 'scores', ...dm }, getDefaultScores('dm', version));
return defaultInfo
}
export const savePlayers: EPR = async (info, data, send) => {
const version = getVersion(info);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
let players = $(data).elements("player")
let response = {
player: [],
gamemode: _.get(data, 'gamemode'),
};
try
{
for (let player of players) {
const no = parseInt(player.attr().no || '1', 10)
// Only save players that are using a profile. Don't try to save guest players.
const hasCard = player.attr().card === 'use'
if (!hasCard) {
logger.debugInfo(`Skipping save for guest ${game} player ${no}.`)
continue
}
const refid = player.str('refid')
if (!refid) {
throw "Request data is missing required parameter: player.refid"
}
await saveSinglePlayer(player, refid, no, version, game, sharedScoresEnabled);
let ranking = await getPlayerRanking(refid, version, game)
let responsePart = getSaveProfileResponse(no, ranking)
response.player.push(responsePart)
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastSavePlayersRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastSavePlayersResponse.json`, JSON.stringify(response, null, 4))
}
await send.object(response);
}
catch (e) {
logger.error(e)
logger.error(e.stack)
return send.deny();
}
};
async function saveSinglePlayer(dataplayer: KDataReader, refid: string, no: number, version: string, game: 'gf' | 'dm', sharedScoresEnabled: boolean)
{
logger.debugInfo(`Saving ${game} profile for player ${no} with refid: ${refid}`)
const profile = await getProfile(refid, version, game) as any;
const extra = await getExtra(refid, version, game) as any;
const rec = await getRecord(refid, version, game) as any;
const autoSet = function (field: keyof Profile, path: string, array = false): void {
if (array) {
profile[field] = dataplayer.numbers(path, profile[field])
} else {
profile[field] = dataplayer.number(path, profile[field])
}
};
const autoExtra = (field: keyof Extra, path: string, array = false): void => {
if (array) {
extra[field] = dataplayer.numbers(path, extra[field])
} else {
extra[field] = dataplayer.number(path, extra[field])
}
};
const autoRec = (field: keyof Record, path: string, array = false): void => {
if (array) {
rec[field] = dataplayer.numbers(path, rec[field])
} else {
rec[field] = dataplayer.number(path, rec[field])
}
};
let newSecretMusic = parseSecretMusic(dataplayer)
profile.secretmusic = {
music: newSecretMusic
}
autoSet('max_skill', 'record.max.skill');
autoSet('max_all_skill', 'record.max.all_skill');
autoSet('clear_diff', 'record.max.clear_diff');
autoSet('full_diff', 'record.max.full_diff');
autoSet('exce_diff', 'record.max.exce_diff');
autoSet('clear_music_num', 'record.max.clear_music_num');
autoSet('full_music_num', 'record.max.full_music_num');
autoSet('exce_music_num', 'record.max.exce_music_num');
autoSet('clear_seq_num', 'record.max.clear_seq_num');
autoSet('classic_all_skill', 'record.max.classic_all_skill');
autoSet('play', 'playinfo.play');
autoSet('playtime', 'playinfo.playtime');
autoSet('playterm', 'playinfo.playterm');
autoSet('session_cnt', 'playinfo.session_cnt');
autoSet('extra_stage', 'playinfo.extra_stage');
autoSet('extra_play', 'playinfo.extra_play');
autoSet('extra_clear', 'playinfo.extra_clear');
autoSet('encore_play', 'playinfo.encore_play');
autoSet('encore_clear', 'playinfo.encore_clear');
autoSet('pencore_play', 'playinfo.pencore_play');
autoSet('pencore_clear', 'playinfo.pencore_clear');
autoSet('max_clear_diff', 'playinfo.max_clear_diff');
autoSet('max_full_diff', 'playinfo.max_full_diff');
autoSet('max_exce_diff', 'playinfo.max_exce_diff');
autoSet('clear_num', 'playinfo.clear_num');
autoSet('full_num', 'playinfo.full_num');
autoSet('exce_num', 'playinfo.exce_num');
autoSet('no_num', 'playinfo.no_num');
autoSet('e_num', 'playinfo.e_num');
autoSet('d_num', 'playinfo.d_num');
autoSet('c_num', 'playinfo.c_num');
autoSet('b_num', 'playinfo.b_num');
autoSet('a_num', 'playinfo.a_num');
autoSet('s_num', 'playinfo.s_num');
autoSet('ss_num', 'playinfo.ss_num');
autoSet('last_category', 'playinfo.last_category');
autoSet('last_musicid', 'playinfo.last_musicid');
autoSet('last_seq', 'playinfo.last_seq');
autoSet('disp_level', 'playinfo.disp_level');
autoSet('extra_gauge', 'groove.extra_gauge');
autoSet('encore_gauge', 'groove.encore_gauge');
autoSet('encore_cnt', 'groove.encore_cnt');
autoSet('encore_success', 'groove.encore_success');
autoSet('unlock_point', 'groove.unlock_point');
autoSet('progress', 'tutorial.progress');
autoSet('disp_state', 'tutorial.disp_state');
autoSet('skill', 'skilldata.skill');
autoSet('all_skill', 'skilldata.all_skill');
autoRec('diff_100_nr', 'record.diff.diff_100_nr');
autoRec('diff_150_nr', 'record.diff.diff_150_nr');
autoRec('diff_200_nr', 'record.diff.diff_200_nr');
autoRec('diff_250_nr', 'record.diff.diff_250_nr');
autoRec('diff_300_nr', 'record.diff.diff_300_nr');
autoRec('diff_350_nr', 'record.diff.diff_350_nr');
autoRec('diff_400_nr', 'record.diff.diff_400_nr');
autoRec('diff_450_nr', 'record.diff.diff_450_nr');
autoRec('diff_500_nr', 'record.diff.diff_500_nr');
autoRec('diff_550_nr', 'record.diff.diff_550_nr');
autoRec('diff_600_nr', 'record.diff.diff_600_nr');
autoRec('diff_650_nr', 'record.diff.diff_650_nr');
autoRec('diff_700_nr', 'record.diff.diff_700_nr');
autoRec('diff_750_nr', 'record.diff.diff_750_nr');
autoRec('diff_800_nr', 'record.diff.diff_800_nr');
autoRec('diff_850_nr', 'record.diff.diff_850_nr');
autoRec('diff_900_nr', 'record.diff.diff_900_nr');
autoRec('diff_950_nr', 'record.diff.diff_950_nr');
autoRec('diff_100_clear', 'record.diff.diff_100_clear', true);
autoRec('diff_150_clear', 'record.diff.diff_150_clear', true);
autoRec('diff_200_clear', 'record.diff.diff_200_clear', true);
autoRec('diff_250_clear', 'record.diff.diff_250_clear', true);
autoRec('diff_300_clear', 'record.diff.diff_300_clear', true);
autoRec('diff_350_clear', 'record.diff.diff_350_clear', true);
autoRec('diff_400_clear', 'record.diff.diff_400_clear', true);
autoRec('diff_450_clear', 'record.diff.diff_450_clear', true);
autoRec('diff_500_clear', 'record.diff.diff_500_clear', true);
autoRec('diff_550_clear', 'record.diff.diff_550_clear', true);
autoRec('diff_600_clear', 'record.diff.diff_600_clear', true);
autoRec('diff_650_clear', 'record.diff.diff_650_clear', true);
autoRec('diff_700_clear', 'record.diff.diff_700_clear', true);
autoRec('diff_750_clear', 'record.diff.diff_750_clear', true);
autoRec('diff_800_clear', 'record.diff.diff_800_clear', true);
autoRec('diff_850_clear', 'record.diff.diff_850_clear', true);
autoRec('diff_900_clear', 'record.diff.diff_900_clear', true);
autoRec('diff_950_clear', 'record.diff.diff_950_clear', true);
autoExtra('list_1', 'favoritemusic.music_list_1', true);
autoExtra('list_2', 'favoritemusic.music_list_2', true);
autoExtra('list_3', 'favoritemusic.music_list_3', true);
autoExtra('recommend_musicid_list', 'recommend_musicid_list', true);
autoExtra('playstyle', 'customdata.playstyle', true);
autoExtra('custom', 'customdata.custom', true);
autoExtra('reward_status', 'reward.status', true)
await DB.Upsert(refid, { collection: 'profile', game, version }, profile)
await DB.Upsert(refid, { collection: 'record', game, version }, rec)
await DB.Upsert(refid, { collection: 'extra', game, version }, extra)
const playedStages = dataplayer.elements('stage');
logStagesPlayed(playedStages)
const scores = await updatePlayerScoreCollection(refid, playedStages, version, game)
await saveScore(refid, version, game, scores);
if (sharedScoresEnabled) {
await mergeScoresIntoShared(refid, game, scores);
}
await saveSharedFavoriteMusicFromExtra(refid, extra)
}
async function updatePlayerScoreCollection(refid, playedStages, version, game) {
const scores = (await getScore(refid, version, game)).scores;
for (const stage of playedStages) {
const mid = stage.number('musicid', -1);
const seq = stage.number('seq', -1);
if (mid < 0 || seq < 0) continue;
// const skill = stage.number('skill', 0);
const newSkill = stage.number('new_skill', 0);
const clear = stage.bool('clear');
const fc = stage.bool('fullcombo');
const ex = stage.bool('excellent');
const newMeter = stage.bool('is_new_meter');
const perc = stage.number('perc', 0);
const rank = stage.number('rank', 0);
const meter = stage.bigint('meter', BigInt(0));
const prog = stage.number('meter_prog', 0);
if(!scores[mid]) {
scores[mid] = {
update: [0, 0],
diffs: {}
}
}
if (newSkill > scores[mid].update[1]) {
scores[mid].update[0] = seq;
scores[mid].update[1] = newSkill;
}
scores[mid].diffs[seq] = { //FIXME: Real server is bit complicated. this one is too buggy.
perc: Math.max(_.get(scores[mid].diffs[seq], 'perc', 0), perc),
rank: Math.max(_.get(scores[mid].diffs[seq], 'rank', 0), rank),
meter: newMeter ? meter.toString() : _.get(scores[mid].diffs[seq], 'meter', 0),
prog: Math.max(_.get(scores[mid].diffs[seq], 'prog', 0), prog),
clear: _.get(scores[mid].diffs[seq], 'clear') || clear,
fc: _.get(scores[mid].diffs[seq], 'fc') || fc,
ex: _.get(scores[mid].diffs[seq], 'ex') || ex,
};
}
return scores
}
async function getPlayerRanking(refid: string, version: string, game: 'gf' | 'dm') : Promise<PlayerRanking> {
let profiles = await getAllProfiles(version, game)
let playerCount = profiles.length
let sortedProfilesA = profiles.sort((a,b) => b.skill - a.skill)
let sortedProfilesB = profiles.sort((a,b) => b.all_skill - a.all_skill)
let idxA = _.findIndex(sortedProfilesA, (e) => e.__refid === refid)
idxA = idxA > -1 ? idxA + 1 : playerCount // Default to last place if not found in the DB.
let idxB = _.findIndex(sortedProfilesB, (e) => e.__refid === refid)
idxB = idxB > -1 ? idxB + 1 : playerCount // Default to last place if not found in the DB.
return {
refid,
skill: idxA,
all_skill: idxB,
totalPlayers: playerCount
}
}
async function getAllProfiles( version: string, game: 'gf' | 'dm') {
return await DB.Find<Profile>(null, {
collection: 'profile',
version: version,
game: game
})
}
async function getProfile(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Profile>(refid, {
collection: 'profile',
version: version,
game: game
})
}
async function getExtra(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Extra>(refid, {
collection: 'extra',
version: version,
game: game
})
}
async function getRecord(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Record>(refid, {
collection: 'record',
version: version,
game: game
})
}
async function getScore(refid: string, version: string, game: 'gf' | 'dm'): Promise<Scores> {
return (await DB.FindOne<Scores>(refid, {
collection: 'scores',
version: version,
game: game
})) || {
collection: 'scores',
version: version,
pluginVer: PLUGIN_VER,
game: game,
scores: {}
}
}
async function saveScore(refid: string, version: string, game: 'gf' | 'dm', scores: Scores['scores']) {
return await DB.Upsert<Scores>(refid, {
collection: 'scores',
version,
game
}, {
collection: 'scores',
version,
game,
scores
})
}
function parseSecretMusic(playerData: KDataReader) : SecretMusicEntry[]
{
let response : SecretMusicEntry[] = []
let elements = playerData.element('secretmusic')?.elements('music')
if (!elements) {
return response
}
for (let el of elements) {
let item : SecretMusicEntry = {
musicid: el.number('musicid'),
seq: el.number('seq'),
kind: el.number('kind')
}
response.push(item)
}
return response
}
function getFriendDataResponse(profile: Profile) {
let response = []
return response;
}
function logStagesPlayed(playedStages: KDataReader[]) {
let result = "Stages played: "
for (let stage of playedStages) {
let id = stage.number('musicid')
result += `${id}, `
}
logger.debugLog(result)
}

View File

@ -0,0 +1,28 @@
import { PlayerInfo } from "../models/playerinfo"
export const updatePlayerInfo = async (data: {
refid: string;
version: string;
name?: string;
title?: string;
}) => {
if (data.refid == null) return;
const update: Update<PlayerInfo>['$set'] = {};
if (data.name && data.name.length > 0) {
//TODO: name validator
update.name = data.name;
}
if (data.title && data.title.length > 0) {
//TODO: title validator
update.title = data.title;
}
await DB.Update<PlayerInfo>(
data.refid,
{ collection: 'playerinfo', version: data.version },
{ $set: update }
);
};

100
gitadora@asphyxia/index.ts Normal file
View File

@ -0,0 +1,100 @@
import { gameInfoGet, shopInfoRegist } from "./handlers/info";
import { playableMusic } from "./handlers/MusicList"
import { getPlayer, check, regist, savePlayers } from "./handlers/profiles";
import { updatePlayerInfo } from "./handlers/webui";
import { isAsphyxiaDebugMode, isRequiredCoreVersion } from "./utils";
import Logger from "./utils/logger";
const logger = new Logger("main")
export function register() {
if(!isRequiredCoreVersion(1, 20)) {
console.error("A newer version of Asphyxia Core (v1.20 or later) is required.")
}
R.GameCode('M32');
R.Config("encore_version", {
name: "Encore Version",
desc: "Set encore version",
type: "integer",
default: 13,
})
R.Config("nextage_dummy_encore", {
name: "Dummy Encore for SPE (Nextage Only)",
desc: "Since Nextage's Special Premium Encore system is bit complicated, \n"
+ "SPE System isn't fully implemented. \n"
+ "This option is a workaround for this issue as limiting some Encores for SPE.",
type: "boolean",
default: false
})
R.Config("enable_custom_mdb", {
name: "Enable Custom MDB",
desc: "If disabled, the server will provide the default MDB (song list) to Gitadora clients, depending on which version of the game they are running." +
"Enable this option to provide your own custom MDB instead. MDB files are stored in the 'gitadora@asphyxia/data/mdb' folder, and can be in .xml, .json or .b64 format.",
type: "boolean",
default: false,
})
R.Config("shared_favorite_songs", {
name: "Shared Favorite Songs (Experimental)",
desc: "If disabled, players will be able to keep separate lists of favorite songs for each version of Gitadora, as well as between Guitar Freaks and Drummania. " +
"Enable this option to have a single unified list of favorite songs for both games, and across all versions. Default is false, to match original arcade behaviour.",
type: "boolean",
default: false,
})
R.Config("shared_song_scores", {
name: "Shared Song Scores (Experimental)",
desc: "If disabled, players will keep separate scoreboards per version. Enable to merge best scores across all versions and games into a shared store.",
type: "boolean",
default: false,
})
R.DataFile("data/mdb/custom.xml", {
accept: ".xml",
name: "Custom MDB",
desc: "Remember to enable the 'Enable Custom MDB' option for the uploaded file to have any effect."
})
R.WebUIEvent('updatePlayerInfo', updatePlayerInfo);
const MultiRoute = (method: string, handler: EPR | boolean) => {
// Helper for register multiple versions.
R.Route(`${method}`, handler);
R.Route(`re_${method}`, handler);
R.Route(`matixx_${method}`, handler);
R.Route(`exchain_${method}`, handler);
R.Route(`matixx_${method}`, handler);
R.Route(`nextage_${method}`, handler)
R.Route(`highvoltage_${method}`, handler)
R.Route(`fuzzup_${method}`, handler)
R.Route(`galaxywave_${method}`, handler)
R.Route(`galaxywave_delta_${method}`, handler)
// TODO: TB, and more older version?
};
// Info
MultiRoute('shopinfo.regist', shopInfoRegist)
MultiRoute('gameinfo.get', gameInfoGet)
// MusicList
MultiRoute('playablemusic.get', playableMusic)
// Profile
MultiRoute('cardutil.regist', regist);
MultiRoute('cardutil.check', check);
MultiRoute('gametop.get', getPlayer);
MultiRoute('gameend.regist', savePlayers);
// Misc
R.Route('bemani_gakuen.get_music_info', true)
R.Unhandled(async (info, data, send) => {
if (["eventlog"].includes(info.module)) return;
logger.error(`Received Unhandled Request on Method "${info.method}" by ${info.model}/${info.module}`)
logger.debugError(`Received Request: ${JSON.stringify(data, null, 4)}`)
})
}

View File

@ -0,0 +1,79 @@
export interface BattleDataResponse
{
info: {
orb: KITEM<'s32'>,
get_gb_point: KITEM<'s32'>,
send_gb_point: KITEM<'s32'>,
}
greeting: {
greeting_1: KITEM<'str'>,
greeting_2: KITEM<'str'>,
greeting_3: KITEM<'str'>,
greeting_4: KITEM<'str'>,
greeting_5: KITEM<'str'>,
greeting_6: KITEM<'str'>,
greeting_7: KITEM<'str'>,
greeting_8: KITEM<'str'>,
greeting_9: KITEM<'str'>,
}
setting: {
matching: KITEM<'s32'>,
info_level: KITEM<'s32'>,
}
score: {
battle_class: KITEM<'s32'>,
max_battle_class: KITEM<'s32'>,
battle_point: KITEM<'s32'>,
win: KITEM<'s32'>,
lose: KITEM<'s32'>,
draw: KITEM<'s32'>,
consecutive_win: KITEM<'s32'>,
max_consecutive_win: KITEM<'s32'>,
glorious_win: KITEM<'s32'>,
max_defeat_skill: KITEM<'s32'>,
latest_result: KITEM<'s32'>,
}
history: {}
}
export function getDefaultBattleDataResponse() : BattleDataResponse {
return {
info: {
orb: K.ITEM('s32', 0),
get_gb_point: K.ITEM('s32', 0),
send_gb_point: K.ITEM('s32', 0),
},
greeting: {
greeting_1: K.ITEM('str', ''),
greeting_2: K.ITEM('str', ''),
greeting_3: K.ITEM('str', ''),
greeting_4: K.ITEM('str', ''),
greeting_5: K.ITEM('str', ''),
greeting_6: K.ITEM('str', ''),
greeting_7: K.ITEM('str', ''),
greeting_8: K.ITEM('str', ''),
greeting_9: K.ITEM('str', ''),
},
setting: {
matching: K.ITEM('s32', 0),
info_level: K.ITEM('s32', 0),
},
score: {
battle_class: K.ITEM('s32', 0),
max_battle_class: K.ITEM('s32', 0),
battle_point: K.ITEM('s32', 0),
win: K.ITEM('s32', 0),
lose: K.ITEM('s32', 0),
draw: K.ITEM('s32', 0),
consecutive_win: K.ITEM('s32', 0),
max_consecutive_win: K.ITEM('s32', 0),
glorious_win: K.ITEM('s32', 0),
max_defeat_skill: K.ITEM('s32', 0),
latest_result: K.ITEM('s32', 0),
},
history: {},
}
}

View File

@ -0,0 +1,30 @@
export interface CheckPlayerResponse {
player: {
name: KITEM<'str'>,
charaid: KITEM<'s32'>,
did: KITEM<'s32'>,
skilldata: {
skill: KITEM<'s32'>
all_skill: KITEM<'s32'>
old_skill: KITEM<'s32'>
old_all_skill: KITEM<'s32'>
},
}
}
export function getCheckPlayerResponse(playerNo : number, name: string, id: number) : CheckPlayerResponse
{
return {
player: K.ATTR({ no: `${playerNo}`, state: '2' }, {
name: K.ITEM('str', name),
charaid: K.ITEM('s32', 0),
did: K.ITEM('s32', id),
skilldata: {
skill: K.ITEM('s32', 0),
all_skill: K.ITEM('s32', 0),
old_skill: K.ITEM('s32', 0),
old_all_skill: K.ITEM('s32', 0),
}
})
}
}

View File

@ -0,0 +1,22 @@
import { CommonMusicDataField } from "../commonmusicdata"
export interface PlayableMusicResponse
{
hot: {
major: KITEM<'s32'>,
minor: KITEM<'s32'>
}
musicinfo: KATTR<any>
}
export function getPlayableMusicResponse(music : CommonMusicDataField[]) : PlayableMusicResponse {
return {
hot: {
major: K.ITEM('s32', 1),
minor: K.ITEM('s32', 1),
},
musicinfo: K.ATTR({ nr: `${music.length}` }, {
music,
}),
}
}

View File

@ -0,0 +1,71 @@
import { Profile } from "../profile";
export interface PlayerPlayInfoResponse {
cabid: KITEM<'s32'>,
play: KITEM<'s32'>,
playtime: KITEM<'s32'>,
playterm: KITEM<'s32'>,
session_cnt: KITEM<'s32'>,
matching_num: KITEM<'s32'>,
extra_stage: KITEM<'s32'>,
extra_play: KITEM<'s32'>,
extra_clear: KITEM<'s32'>,
encore_play: KITEM<'s32'>,
encore_clear: KITEM<'s32'>,
pencore_play: KITEM<'s32'>,
pencore_clear: KITEM<'s32'>,
max_clear_diff: KITEM<'s32'>,
max_full_diff: KITEM<'s32'>,
max_exce_diff: KITEM<'s32'>,
clear_num: KITEM<'s32'>,
full_num: KITEM<'s32'>,
exce_num: KITEM<'s32'>,
no_num: KITEM<'s32'>,
e_num: KITEM<'s32'>,
d_num: KITEM<'s32'>,
c_num: KITEM<'s32'>,
b_num: KITEM<'s32'>,
a_num: KITEM<'s32'>,
s_num: KITEM<'s32'>,
ss_num: KITEM<'s32'>,
last_category: KITEM<'s32'>,
last_musicid: KITEM<'s32'>,
last_seq: KITEM<'s32'>,
disp_level: KITEM<'s32'>,
}
export function getPlayerPlayInfoResponse(profile : Profile) : PlayerPlayInfoResponse {
return {
cabid: K.ITEM('s32', 0),
play: K.ITEM('s32', profile.play),
playtime: K.ITEM('s32', profile.playtime),
playterm: K.ITEM('s32', profile.playterm),
session_cnt: K.ITEM('s32', profile.session_cnt),
matching_num: K.ITEM('s32', 0),
extra_stage: K.ITEM('s32', profile.extra_stage),
extra_play: K.ITEM('s32', profile.extra_play),
extra_clear: K.ITEM('s32', profile.extra_clear),
encore_play: K.ITEM('s32', profile.encore_play),
encore_clear: K.ITEM('s32', profile.encore_clear),
pencore_play: K.ITEM('s32', profile.pencore_play),
pencore_clear: K.ITEM('s32', profile.pencore_clear),
max_clear_diff: K.ITEM('s32', profile.max_clear_diff),
max_full_diff: K.ITEM('s32', profile.max_full_diff),
max_exce_diff: K.ITEM('s32', profile.max_exce_diff),
clear_num: K.ITEM('s32', profile.clear_num),
full_num: K.ITEM('s32', profile.full_num),
exce_num: K.ITEM('s32', profile.exce_num),
no_num: K.ITEM('s32', profile.no_num),
e_num: K.ITEM('s32', profile.e_num),
d_num: K.ITEM('s32', profile.d_num),
c_num: K.ITEM('s32', profile.c_num),
b_num: K.ITEM('s32', profile.b_num),
a_num: K.ITEM('s32', profile.a_num),
s_num: K.ITEM('s32', profile.s_num),
ss_num: K.ITEM('s32', profile.ss_num),
last_category: K.ITEM('s32', profile.last_category),
last_musicid: K.ITEM('s32', profile.last_musicid),
last_seq: K.ITEM('s32', profile.last_seq),
disp_level: K.ITEM('s32', profile.disp_level),
}
}

View File

@ -0,0 +1,110 @@
import { Profile } from "../profile"
import { Record } from "../record"
export interface PlayerRecordResponse {
max_record: {
skill: KITEM<'s32'>,
all_skill: KITEM<'s32'>,
clear_diff: KITEM<'s32'>,
full_diff: KITEM<'s32'>,
exce_diff: KITEM<'s32'>,
clear_music_num: KITEM<'s32'>,
full_music_num: KITEM<'s32'>,
exce_music_num: KITEM<'s32'>,
clear_seq_num: KITEM<'s32'>,
classic_all_skill: KITEM<'s32'>
},
diff_record: {
diff_100_nr: KITEM<'s32'>,
diff_150_nr: KITEM<'s32'>,
diff_200_nr: KITEM<'s32'>,
diff_250_nr: KITEM<'s32'>,
diff_300_nr: KITEM<'s32'>,
diff_350_nr: KITEM<'s32'>,
diff_400_nr: KITEM<'s32'>,
diff_450_nr: KITEM<'s32'>,
diff_500_nr: KITEM<'s32'>,
diff_550_nr: KITEM<'s32'>,
diff_600_nr: KITEM<'s32'>,
diff_650_nr: KITEM<'s32'>,
diff_700_nr: KITEM<'s32'>,
diff_750_nr: KITEM<'s32'>,
diff_800_nr: KITEM<'s32'>,
diff_850_nr: KITEM<'s32'>,
diff_900_nr: KITEM<'s32'>,
diff_950_nr: KITEM<'s32'>,
diff_100_clear: KARRAY<'s32'>
diff_150_clear: KARRAY<'s32'>
diff_200_clear: KARRAY<'s32'>
diff_250_clear: KARRAY<'s32'>
diff_300_clear: KARRAY<'s32'>
diff_350_clear: KARRAY<'s32'>
diff_400_clear: KARRAY<'s32'>
diff_450_clear: KARRAY<'s32'>
diff_500_clear: KARRAY<'s32'>
diff_550_clear: KARRAY<'s32'>
diff_600_clear: KARRAY<'s32'>
diff_650_clear: KARRAY<'s32'>
diff_700_clear: KARRAY<'s32'>
diff_750_clear: KARRAY<'s32'>
diff_800_clear: KARRAY<'s32'>
diff_850_clear: KARRAY<'s32'>
diff_900_clear: KARRAY<'s32'>
diff_950_clear: KARRAY<'s32'>
}
}
export function getPlayerRecordResponse(profile: Profile, rec: Record) : PlayerRecordResponse {
return {
max_record: {
skill: K.ITEM('s32', profile.max_skill),
all_skill: K.ITEM('s32', profile.max_all_skill),
clear_diff: K.ITEM('s32', profile.clear_diff),
full_diff: K.ITEM('s32', profile.full_diff),
exce_diff: K.ITEM('s32', profile.exce_diff),
clear_music_num: K.ITEM('s32', profile.clear_music_num),
full_music_num: K.ITEM('s32', profile.full_music_num),
exce_music_num: K.ITEM('s32', profile.exce_music_num),
clear_seq_num: K.ITEM('s32', profile.clear_seq_num),
classic_all_skill: K.ITEM('s32', profile.classic_all_skill),
},
diff_record: {
diff_100_nr: K.ITEM('s32', rec.diff_100_nr),
diff_150_nr: K.ITEM('s32', rec.diff_150_nr),
diff_200_nr: K.ITEM('s32', rec.diff_200_nr),
diff_250_nr: K.ITEM('s32', rec.diff_250_nr),
diff_300_nr: K.ITEM('s32', rec.diff_300_nr),
diff_350_nr: K.ITEM('s32', rec.diff_350_nr),
diff_400_nr: K.ITEM('s32', rec.diff_400_nr),
diff_450_nr: K.ITEM('s32', rec.diff_450_nr),
diff_500_nr: K.ITEM('s32', rec.diff_500_nr),
diff_550_nr: K.ITEM('s32', rec.diff_550_nr),
diff_600_nr: K.ITEM('s32', rec.diff_600_nr),
diff_650_nr: K.ITEM('s32', rec.diff_650_nr),
diff_700_nr: K.ITEM('s32', rec.diff_700_nr),
diff_750_nr: K.ITEM('s32', rec.diff_750_nr),
diff_800_nr: K.ITEM('s32', rec.diff_800_nr),
diff_850_nr: K.ITEM('s32', rec.diff_850_nr),
diff_900_nr: K.ITEM('s32', rec.diff_900_nr),
diff_950_nr: K.ITEM('s32', rec.diff_950_nr),
diff_100_clear: K.ARRAY('s32', rec.diff_100_clear),
diff_150_clear: K.ARRAY('s32', rec.diff_150_clear),
diff_200_clear: K.ARRAY('s32', rec.diff_200_clear),
diff_250_clear: K.ARRAY('s32', rec.diff_250_clear),
diff_300_clear: K.ARRAY('s32', rec.diff_300_clear),
diff_350_clear: K.ARRAY('s32', rec.diff_350_clear),
diff_400_clear: K.ARRAY('s32', rec.diff_400_clear),
diff_450_clear: K.ARRAY('s32', rec.diff_450_clear),
diff_500_clear: K.ARRAY('s32', rec.diff_500_clear),
diff_550_clear: K.ARRAY('s32', rec.diff_550_clear),
diff_600_clear: K.ARRAY('s32', rec.diff_600_clear),
diff_650_clear: K.ARRAY('s32', rec.diff_650_clear),
diff_700_clear: K.ARRAY('s32', rec.diff_700_clear),
diff_750_clear: K.ARRAY('s32', rec.diff_750_clear),
diff_800_clear: K.ARRAY('s32', rec.diff_800_clear),
diff_850_clear: K.ARRAY('s32', rec.diff_850_clear),
diff_900_clear: K.ARRAY('s32', rec.diff_900_clear),
diff_950_clear: K.ARRAY('s32', rec.diff_950_clear),
},
};
}

View File

@ -0,0 +1,46 @@
export interface PlayerStickerResponse {
id: KITEM<'s32'>,
pos_x: KITEM<'float'> ,
pos_y: KITEM<'float'>,
scale_x: KITEM<'float'> ,
scale_y: KITEM<'float'>,
rotate: KITEM<'float'>
}
export function getPlayerStickerResponse(playerCard : any[]) : PlayerStickerResponse[] {
let stickers : PlayerStickerResponse[] = []
if (!_.isArray(playerCard)) {
return stickers
}
for (const item of playerCard) {
const id = _.get(item, 'id');
const posX = _.get(item, 'position.0');
const posY = _.get(item, 'position.1');
const scaleX = _.get(item, 'scale.0');
const scaleY = _.get(item, 'scale.1');
const rotation = _.get(item, 'rotation');
if (
!isFinite(id) ||
!isFinite(posX) ||
!isFinite(posY) ||
!isFinite(scaleX) ||
!isFinite(scaleY) ||
!isFinite(rotation)
) {
continue;
}
stickers.push({
id: K.ITEM('s32', id),
pos_x: K.ITEM('float', posX),
pos_y: K.ITEM('float', posY),
scale_x: K.ITEM('float', scaleX),
scale_y: K.ITEM('float', scaleY),
rotate: K.ITEM('float', rotation),
});
}
return stickers
}

View File

@ -0,0 +1,41 @@
import { PlayerRanking } from "../playerranking"
export interface SaveProfileResponse
{
skill: {
rank: KITEM<'s32'>,
total_nr: KITEM<'s32'>
}
all_skill: {
rank: KITEM<'s32'>,
total_nr: KITEM<'s32'>
}
kac2018: {
data: {
term: KITEM<'s32'>,
total_score: KITEM<'s32'>,
score: KARRAY<'s32'>,
music_type: KARRAY<'s32'>,
play_count: KARRAY<'s32'>
}
}
}
export function getSaveProfileResponse(playerNo: number, ranking : PlayerRanking)
{
const result : SaveProfileResponse = K.ATTR({ no: `${playerNo}` }, {
skill: { rank: K.ITEM('s32', ranking.skill), total_nr: K.ITEM('s32', ranking.totalPlayers) },
all_skill: { rank: K.ITEM('s32', ranking.all_skill), total_nr: K.ITEM('s32', ranking.totalPlayers) },
kac2018: {
data: {
term: K.ITEM('s32', 0),
total_score: K.ITEM('s32', 0),
score: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
music_type: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
play_count: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
})
return result
}

View File

@ -0,0 +1,25 @@
import { Profile } from "../profile";
export interface SecretMusicResponse {
musicid: KITEM<'s32'>;
seq: KITEM<'u16'>;
kind: KITEM<'s32'>;
}
export function getSecretMusicResponse(profile: Profile) : SecretMusicResponse[] {
let response : SecretMusicResponse[] = []
if (!profile.secretmusic?.music ) {
return response
}
for (let music of profile.secretmusic.music) {
response.push({
musicid: K.ITEM('s32', music.musicid),
seq: K.ITEM('u16', music.seq),
kind: K.ITEM('s32', music.kind)
})
}
return response
}

View File

@ -0,0 +1,14 @@
export interface CommonMusicDataField {
id: KITEM<"s32">;
cont_gf: KITEM<"bool">;
cont_dm: KITEM<"bool">;
is_secret: KITEM<"bool">;
is_hot: KITEM<"bool">;
data_ver: KITEM<"s32">;
diff: KARRAY<"u16">;
seq_release_state: KITEM<"s32">;
}
export interface CommonMusicData {
music: CommonMusicDataField[]
}

View File

@ -0,0 +1,41 @@
import { PLUGIN_VER } from "../const";
export interface Extra {
collection: 'extra';
game: 'gf' | 'dm';
version: string;
pluginVer: number
id: number;
playstyle: number[];
custom: number[];
list_1: number[];
list_2: number[];
list_3: number[];
recommend_musicid_list: number[];
reward_status: number[];
}
export function getDefaultExtra(game: 'gf' | 'dm', version: string, id: number) : Extra {
const result : Extra = {
collection: 'extra',
pluginVer: PLUGIN_VER,
game,
version,
id,
playstyle: Array(50).fill(0),
custom: Array(50).fill(0),
list_1: Array(100).fill(-1),
list_2: Array(100).fill(-1),
list_3: Array(100).fill(-1),
recommend_musicid_list: Array(5).fill(-1),
reward_status: Array(50).fill(0),
}
result.playstyle[1] = 1 // Note scroll speed (should default to 1.0x)
result.playstyle[36] = 20 // Target Timing Adjustment
result.playstyle[48] = 20 // Note Display Adjustment
return result
}

View File

@ -0,0 +1,10 @@
export interface FavoriteMusic {
collection: 'favoritemusic',
pluginVer: number;
list_1: number[];
list_2: number[];
list_3: number[];
recommend_musicid_list: number[];
}

View File

@ -0,0 +1,32 @@
import { PLUGIN_VER } from "../const";
export interface PlayerInfo {
collection: 'playerinfo',
pluginVer: number;
id: number;
version: string,
name: string;
title: string;
card?: {
id: number;
position: number[];
scale: number[];
rotation: number;
}[];
// TODO: Add Board things.
}
export function getDefaultPlayerInfo(version: string, id: number) : PlayerInfo {
return {
collection: 'playerinfo',
pluginVer: PLUGIN_VER,
id,
version,
name: 'ASPHYXIA-CORE USER',
title: 'Please edit on WebUI',
}
}

View File

@ -0,0 +1,7 @@
export interface PlayerRanking
{
refid: string;
skill: number;
all_skill: number;
totalPlayers: number;
}

View File

@ -0,0 +1,8 @@
export interface PlayerStickerResponse {
id: KITEM<'s32'>,
pos_x: KITEM<'float'> ,
pos_y: KITEM<'float'>,
scale_x: KITEM<'float'> ,
scale_y: KITEM<'float'>,
rotate: KITEM<'float'>
}

View File

@ -0,0 +1,126 @@
import { PLUGIN_VER } from "../const";
import { SecretMusicEntry } from "./secretmusicentry";
export interface Profile {
collection: 'profile';
game: 'gf' | 'dm';
version: string;
pluginVer: number
id: number;
play: number;
playtime: number;
playterm: number;
session_cnt: number;
extra_stage: number;
extra_play: number;
extra_clear: number;
encore_play: number;
encore_clear: number;
pencore_play: number;
pencore_clear: number;
max_clear_diff: number;
max_full_diff: number;
max_exce_diff: number;
clear_num: number;
full_num: number;
exce_num: number;
no_num: number;
e_num: number;
d_num: number;
c_num: number;
b_num: number;
a_num: number;
s_num: number;
ss_num: number;
last_category: number;
last_musicid: number;
last_seq: number;
disp_level: number;
progress: number;
disp_state: number;
skill: number;
all_skill: number;
extra_gauge: number;
encore_gauge: number;
encore_cnt: number;
encore_success: number;
unlock_point: number;
max_skill: number;
max_all_skill: number;
clear_diff: number;
full_diff: number;
exce_diff: number;
clear_music_num: number;
full_music_num: number;
exce_music_num: number;
clear_seq_num: number;
classic_all_skill: number;
secretmusic: {
music: SecretMusicEntry[];
}
}
export function getDefaultProfile (game: 'gf' | 'dm', version: string, id: number): Profile {
return {
collection: 'profile',
pluginVer: PLUGIN_VER,
game,
version,
id,
play: 0,
playtime: 0,
playterm: 0,
session_cnt: 0,
extra_stage: 0,
extra_play: 0,
extra_clear: 0,
encore_play: 0,
encore_clear: 0,
pencore_play: 0,
pencore_clear: 0,
max_clear_diff: 0,
max_full_diff: 0,
max_exce_diff: 0,
clear_num: 0,
full_num: 0,
exce_num: 0,
no_num: 0,
e_num: 0,
d_num: 0,
c_num: 0,
b_num: 0,
a_num: 0,
s_num: 0,
ss_num: 0,
last_category: 0,
last_musicid: -1,
last_seq: 0,
disp_level: 0,
progress: 0,
disp_state: 0,
skill: 0,
all_skill: 0,
extra_gauge: 0,
encore_gauge: 0,
encore_cnt: 0,
encore_success: 0,
unlock_point: 0,
max_skill: 0,
max_all_skill: 0,
clear_diff: 0,
full_diff: 0,
exce_diff: 0,
clear_music_num: 0,
full_music_num: 0,
exce_music_num: 0,
clear_seq_num: 0,
classic_all_skill: 0,
secretmusic: {
music: []
}
}
};

View File

@ -0,0 +1,92 @@
import { PLUGIN_VER } from "../const";
export interface Record {
collection: 'record';
game: 'gf' | 'dm';
version: string;
pluginVer: number
diff_100_nr: number;
diff_150_nr: number;
diff_200_nr: number;
diff_250_nr: number;
diff_300_nr: number;
diff_350_nr: number;
diff_400_nr: number;
diff_450_nr: number;
diff_500_nr: number;
diff_550_nr: number;
diff_600_nr: number;
diff_650_nr: number;
diff_700_nr: number;
diff_750_nr: number;
diff_800_nr: number;
diff_850_nr: number;
diff_900_nr: number;
diff_950_nr: number;
diff_100_clear: number[];
diff_150_clear: number[];
diff_200_clear: number[];
diff_250_clear: number[];
diff_300_clear: number[];
diff_350_clear: number[];
diff_400_clear: number[];
diff_450_clear: number[];
diff_500_clear: number[];
diff_550_clear: number[];
diff_600_clear: number[];
diff_650_clear: number[];
diff_700_clear: number[];
diff_750_clear: number[];
diff_800_clear: number[];
diff_850_clear: number[];
diff_900_clear: number[];
diff_950_clear: number[];
}
export function getDefaultRecord(game: 'gf' | 'dm', version: string): Record {
return {
collection: 'record',
pluginVer: PLUGIN_VER,
game,
version,
diff_100_nr: 0,
diff_150_nr: 0,
diff_200_nr: 0,
diff_250_nr: 0,
diff_300_nr: 0,
diff_350_nr: 0,
diff_400_nr: 0,
diff_450_nr: 0,
diff_500_nr: 0,
diff_550_nr: 0,
diff_600_nr: 0,
diff_650_nr: 0,
diff_700_nr: 0,
diff_750_nr: 0,
diff_800_nr: 0,
diff_850_nr: 0,
diff_900_nr: 0,
diff_950_nr: 0,
diff_100_clear: [0, 0, 0, 0, 0, 0, 0],
diff_150_clear: [0, 0, 0, 0, 0, 0, 0],
diff_200_clear: [0, 0, 0, 0, 0, 0, 0],
diff_250_clear: [0, 0, 0, 0, 0, 0, 0],
diff_300_clear: [0, 0, 0, 0, 0, 0, 0],
diff_350_clear: [0, 0, 0, 0, 0, 0, 0],
diff_400_clear: [0, 0, 0, 0, 0, 0, 0],
diff_450_clear: [0, 0, 0, 0, 0, 0, 0],
diff_500_clear: [0, 0, 0, 0, 0, 0, 0],
diff_550_clear: [0, 0, 0, 0, 0, 0, 0],
diff_600_clear: [0, 0, 0, 0, 0, 0, 0],
diff_650_clear: [0, 0, 0, 0, 0, 0, 0],
diff_700_clear: [0, 0, 0, 0, 0, 0, 0],
diff_750_clear: [0, 0, 0, 0, 0, 0, 0],
diff_800_clear: [0, 0, 0, 0, 0, 0, 0],
diff_850_clear: [0, 0, 0, 0, 0, 0, 0],
diff_900_clear: [0, 0, 0, 0, 0, 0, 0],
diff_950_clear: [0, 0, 0, 0, 0, 0, 0],
}
}

View File

@ -0,0 +1,36 @@
import { PLUGIN_VER } from "../const";
export interface Scores {
collection: 'scores';
game: 'gf' | 'dm';
version?: string;
pluginVer: number
scores: {
[mid: string]: {
update: number[];
diffs: {
[seq: string]: {
perc: number;
rank: number;
clear: boolean;
fc: boolean;
ex: boolean;
meter: string;
prog: number;
};
};
};
};
}
export function getDefaultScores (game: 'gf' | 'dm', version: string): Scores {
return {
collection: 'scores',
version,
pluginVer: PLUGIN_VER,
game,
scores: {}
}
};

View File

@ -0,0 +1,5 @@
export interface SecretMusicEntry {
musicid: number;
seq: number;
kind: number;
}

View File

@ -0,0 +1,5 @@
export interface SecretMusicResponse {
musicid: KITEM<'s32'>;
seq: KITEM<'u16'>;
kind: KITEM<'s32'>;
}

View File

@ -0,0 +1,39 @@
export const isGF = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'A';
};
export const isDM = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'B';
};
export const getVersion = (info: EamuseInfo) => {
const moduleName: string = info.module;
const moduleMatch = moduleName.match(/([^_]*)_(.*)/);
if (moduleMatch && moduleMatch[1]) {
return moduleMatch[1];
}
console.error(`Unable to parse version from module name "${moduleName}".`);
return "unknown";
};
export function isRequiredCoreVersion(major: number, minor: number) {
// version value exposed since Core v1.19
const core_major = typeof CORE_VERSION_MAJOR === "number" ? CORE_VERSION_MAJOR : 1
const core_minor = typeof CORE_VERSION_MINOR === "number" ? CORE_VERSION_MINOR : 18
return core_major > major || (core_major === major && core_minor >= minor)
};
export function isAsphyxiaDebugMode() : boolean {
const argv = (globalThis as { process?: { argv?: string[] } }).process?.argv ?? [];
return argv.includes("--dev") || argv.includes("--console");
}
export function isSharedFavoriteMusicEnabled() : boolean{
return Boolean(U.GetConfig("shared_favorite_songs"))
}
export function isSharedSongScoresEnabled() : boolean{
return Boolean(U.GetConfig("shared_song_scores"))
}

View File

@ -0,0 +1,57 @@
import { isAsphyxiaDebugMode } from ".";
export default class Logger {
public category: string | null;
public constructor(category?: string) {
this.category = (category == null) ? null : `[${category}]`
}
public error(...args: any[]) {
this.argsHandler(console.error, ...args)
}
public debugError(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.error, ...args)
}
}
public warn(...args: any[]) {
this.argsHandler(console.warn, ...args)
}
public debugWarn(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.warn, ...args)
}
}
public info(...args: any[]) {
this.argsHandler(console.info, ...args)
}
public debugInfo(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.info, ...args)
}
}
public log(...args: any[]) {
this.argsHandler(console.log, ...args)
}
public debugLog(...args: any[]) {
if (isAsphyxiaDebugMode()) {
this.argsHandler(console.log, ...args)
}
}
private argsHandler(target: Function, ...args: any[]) {
if (this.category == null) {
target(...args)
} else {
target(this.category, ...args)
}
}
}

View File

@ -0,0 +1,107 @@
//DATA//
infos: DB.Find(null, { collection: 'playerinfo' })
profiles: DB.Find(null, { collection: 'profile' })
-
-
function getFullGameName(shortName) {
switch (shortName) {
case "dm" :
return "DrumMania"
case "gf":
return "GuitarFreaks"
default:
return "Unknown"
}
}
function getFullGameVersion(shortVer) {
switch (shortVer) {
case "re" :
return "Tri-Boost Re:EVOLVE"
case "matixx":
return "Matixx"
case "EXCHAIN":
return "exchain"
case "nextage":
return "NEX+AGE"
case "highvoltage":
return "HIGH-VOLTAGE"
case "fuzzup":
return "FUZZ-UP"
case "galaxywave":
return "GALAXY WAVE"
case "galaxywave_delta":
return "GALAXY WAVE DELTA"
default:
return "Unknown"
}
}
const versions = ["re", "matixx", "exchain", "nextage", "highvoltage", "fuzzup", "galaxywave", "galaxywave_delta"]
const games = ["gf", "dm"]
function generateLeaderboards(infos, profiles) {
let result = []
for (const version of versions) {
for (const game of games) {
result.push(generateLeaderboard(infos, profiles, version, game))
}
}
// Hide versions and games with no entries
result = result.filter((e) => e.entries.length > 0)
return result
}
function generateLeaderboard(infos, profiles, version, game) {
let entries = []
let idx = 1
let currentProfiles = profiles.filter((e) => e.game === game && e.version === version)
currentProfiles = currentProfiles.sort((a, b) => b.skill - a.skill)
for (const profile of currentProfiles) {
const info = infos.find(i => i.__refid === profile.__refid)
const name = info ? info.name : "Unknown"
const scoreData = {
rank: idx,
name: name,
skill: profile.skill / 100,
all_skill: profile.all_skill / 100,
clear_music_num : profile.clear_music_num,
clear_diff: profile.clear_diff / 100
}
entries.push(scoreData)
idx++
}
let result = {
version: version,
game: game,
entries: entries
}
return result
}
-
each board in generateLeaderboards(infos, profiles)
h3 #{getFullGameName(board.game)} #{getFullGameVersion(board.version)}
table
tr
th Rank
th Name
th Skill
th All Skill
th Songs Cleared
th Hardest Clear
each e in board.entries
tr
td #{e.rank}
td #{e.name}
td #{e.skill}
td #{e.all_skill}
td #{e.clear_music_num}
td #{e.clear_diff}

View File

@ -0,0 +1,119 @@
//DATA//
info: DB.Find(refid, { collection: 'playerinfo' })
profile: DB.Find(refid, { collection: 'profile' })
-
-
function getFullGameName(shortName) {
switch (shortName) {
case "gf":
return "GuitarFreaks"
case "dm" :
return "DrumMania"
default:
return "Unknown"
}
}
function getFullGameVersion(shortVer) {
switch (shortVer) {
case "re" :
return "Tri-Boost Re:EVOLVE"
case "matixx":
return "Matixx"
case "EXCHAIN":
return "exchain"
case "nextage":
return "NEX+AGE"
case "highvoltage":
return "HIGH-VOLTAGE"
case "fuzzup":
return "FUZZ-UP"
case "galaxywave":
return "GALAXY WAVE"
case "galaxywave_delta":
return "GALAXY WAVE DELTA"
default:
return "Unknown"
}
}
-
div
each i in info
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| User Detail (#{getFullGameVersion(i.version)})
.card-content
form(method="post" action="/emit/updatePlayerInfo")
.field
label.label ID
.control
input.input(type="text" name="refid", value=refid readonly)
.field
label.label Version
.control
input.input(type="text" name="version", value=i.version readonly)
.field
label.label Name
.control
input.input(type="text" name="name", value=i.name)
.field
label.label Title
.control
input.input(type="text" name="title", value=i.title)
.field
button.button.is-primary(type="submit")
span.icon
i.mdi.mdi-check
span Submit
div
each pr in profile
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-details
| Profile Detail (#{getFullGameName(pr.game)} #{getFullGameVersion(pr.version)})
.card-content
form(method="post")
.field
label.label Skill
.control
input.input(type="text" name="skill", value=(pr.skill/100) readonly)
.field
label.label Skill (All Songs)
.control
input.input(type="text" name="all_skill", value=(pr.all_skill/100) readonly)
.field
label.label Songs Cleared
.control
input.input(type="text" name="clear_num", value=pr.clear_num readonly)
.field
label.label Full Combos
.control
input.input(type="text" name="full_num", value=pr.full_num readonly)
.field
label.label Excellent Full Combos
.control
input.input(type="text" name="exce_num", value=pr.exce_num readonly)
.field
label.label Highest Difficulty Cleared
.control
input.input(type="text" name="max_clear_diff", value=(pr.max_clear_diff/100) readonly)
.field
label.label Highest Difficulty Full Combo
.control
input.input(type="text" name="max_full_diff", value=(pr.max_full_diff/100) readonly)
.field
label.label Highest Difficulty Excellent Full Combo
.control
input.input(type="text" name="max_exce_diff", value=(pr.max_exce_diff/100) readonly)
.field
label.label Sessions
.control
input.input(type="text" name="session_cnt", value=pr.session_cnt readonly)

170
iidx@asphyxia/README.md Normal file
View File

@ -0,0 +1,170 @@
# beatmaniaIIDX
Plugin Version: **v0.1.17**
---
Supported Versions
- beatmaniaIIDX 14 GOLD (2007072301)
- beatmaniaIIDX 15 DJ TROOPERS (2008031100)
- beatmaniaIIDX 16 EMPRESS (2009072200)
- beatmaniaIIDX 17 SIRIUS (2010071200)
- beatmaniaIIDX 18 Resort Anthem (2011071200)
- beatmaniaIIDX 19 Lincle (2012090300)
- beatmaniaIIDX 20 tricoro (2013090900)
- beatmaniaIIDX 21 SPADA (2014071600)
- beatmaniaIIDX 22 PENDUAL (2015080500)
- beatmaniaIIDX 23 copula (2016083100)
- beatmaniaIIDX 24 SINOBUZ (2017082800)
- beatmaniaIIDX 25 CANNON BALLERS (2018091900)
- beatmaniaIIDX 26 Rootage (2019090200)
- beatmaniaIIDX 27 HEROIC VERSE (2020092900)
- beatmaniaIIDX 28 BISTROVER (2021091500)
- beatmaniaIIDX 29 CastHour (2022082400)
- beatmaniaIIDX 30 RESIDENT (2023090500)
- beatmaniaIIDX 31 EPOLIS (2024082600)
- beatmaniaIIDX 32 Pinky Crush (2025082500)
---
Features
- STEP UP (Partial)
- SKILL ANALYZER
- EVENT (Partial)
- ARENA (LOCAL only)
- RANDOME LANE TICKET
- FAVORITE/SONG SELECTION NOTES
- ORIGINAL FILTER
---
Known Issues
- Clear Lamps may display invalid lamps due to missing conversion code
- DJ LEVEL folders are broken in ~ DJ TROOPERS due to missing rank\_id
- LEGGENDARIA play records before HEROIC VERSE may not display on higher version due to missing conversion code
- SUPER FUTURE 2323 play records doesn't display on other version due to missing conversion code
- ONE MORE EXTRA STAGE progress won't save (can't test this due to skill issue)
- Some of licensed songs are locked behind (kinda solved with music\_open but needs to be verified)
- Some of badges aren't saving in RESIDENT ~ (needs to figure out name to id)
---
Changelogs
**v0.1.0**
- Added Initial support for Lincle
**v0.1.1**
- Added Initial support for HEROIC VERSE
- Expanded score array to adapting newer difficulty (SPN ~ DPA [6] -> SPB ~ DPL [10])
- This borked previous score datas recorded with v0.1.0
- All score data now shared with all version
- as it doesn't have music\_id conversion, it will display incorrect data on certain versions
- Added Initial customize support (no webui)
**v0.1.2**
- Added Initial support for BISTROVER
- Added Initial Rival support (partial webui)
**v0.1.3**
- Added Initial support for CastHour
**v0.1.4**
- Added Initial support for RESIDENT
**v0.1.5**
- Added Initial support for Resort Anthem
- LEAGUE, STORY does not work yet
- Fixed where s\_hispeed/d\_hispeed doesn't save correctly
**v0.1.6**
- Added Initial support for tricoro
- Some of event savings are broken
- Added movie\_upload url setting on plugin setting (BISTROVER ~)
- This uses JSON instead of XML and this requires additional setup (can't test or implement this as I don't own NVIDIA GPU)
**v0.1.7**
- Added Initial support for SPADA
- Some of event savings are broken
- Fixed where rtype didn't save correctly (BISTROVER ~)
**v0.1.8**
- Added RIVAL pacemaker support
- Added Initial support for PENDUAL
- Some of event savings are broken
- Fixed where old\_linkage\_secret\_flg is missing on pc.get response (RESIDENT)
- Fixed where game could crash due to invalid rival qprodata
- Fixed where lift isn't saving (SPADA)
**v0.1.9**
- Added Initial support for copula
- Some of event savings are broken
- Added shop.getconvention/shop.setconvention/shop.getname/shop.savename response
**v0.1.10**
- Added Initial support for SINOBUZ ~ Rootage
- Converted from asphyxia\_route\_public
**v0.1.11**
- Added Shop Ranking support
- Changed pc.common/gameSystem.systemInfo response not to use pugFile
- IIDX\_CPUS on models/arena.ts came from asphyxia\_route\_public
**v0.1.12**
- Exposed some of pc.common attributes to plugin settings (WIP)
- Added Experimental WebUI (WIP)
- Added music.crate/music.breg response
- CLEAR RATE and BEGINNER clear lamp may not work on certain versions
- Added Initial support for SIRIUS
- Fixed where Venue Top didn't save correctly (BISTROVER ~)
- Fixed where music.appoint send empty response even rival has score data but player doesn't have score data
- Fixed where FAVORITE may work only on specific version
- Fixed where shop name always displayed as "CORE" instead of saved one
- Fixed where rlist STEP UP achieve value was fixed value instead of saved one
- Fixed where fcombo isn't saving (Resort Anthem)
- Removed shop.savename as not working as intented
**v0.1.13**
- Added Initial support for DJ TROOPERS
- Added Initial support for EMPRESS
- Fixed where EXPERT result does not display total cleared users and ranking position
**v0.1.14**
- Added Experimental OMEGA-Attack event saving support on tricoro
- Reworked on SINOBUZ ~ Rootage responses
- Fixed where Base64toBuffer returns invalid value sometimes
- Fixed where timing display option isn't saving on certain versions
**v0.1.15**
- Added Initial support for GOLD
- Added Disable Beginner Option
- Added Experimental Badge saving support
- Added Experimental score import/export
- Fixed where plugin may fail to register due to missing types in dev mode (invalid setup for dev, just enough to get around)
- Fixed where unable to login after first-play (SPADA, SINOBUZ, Rootage)
- Fixed where pacemaker isn't working as intended due to malformed ghost data on music.appoint response (~ DJ TROOPERS)
- Fixed where pacemaker isn't working as intented due to wrong condition check (HEROIC VERSE ~)
- Fixed where pacemaker sub-type isn't load correctly (HEROIC VERSE ~)
- Fixed where QPRO data doesn't get saved in WebUI
**v0.1.16**
- Added Initial support for EPOLIS
- Added music\_open on gameSystem.systemInfo response
- Added EXTRA FAVORITE support
- Fixed where lightning settings doesn't get saved on logout
- Fixed where Disable Music Preview, Disable HCN Color, VEFX Lock settings doesn't reflect
- Fixed where MISS COUNT has 0 as default (including score import)
- Fixed where MISS COUNT doesn't get updated when exscore is same
- Fixed where lightning model settings saved incorrectly
- Fixed where unable to import score if user has DP scores
- Fixed where unable to achieve dan if you failed once
- Fixed where unable to login (tricoro, CastHour, Rootage)
- Fixed where unable to specify rival in WebUI
- Fixed where music.arenaCPU isn't working as intended due to change of type (EPOLIS ~)
- Fixed where qpro head equip request handle as hand equip (@anzuwork)
- Added error message for invalid score database entries
- Reverted `v0.1.15` dev mode related code changes (now requires proper dev setup, refer parent README.md)
- WebUI is now display values of corresponding version
**v0.1.17**
- Added Initial support for Pinky Crush

View File

@ -0,0 +1,94 @@
{
"31": {
"0": {
"15": {
"music_id": [ 25090, 23068, 19004, 29045 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"16": {
"music_id": [ 23005, 27078, 22065, 27060 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"17": {
"music_id": [ 29007, 26108, 19002, 18004 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"18": {
"music_id": [ 25007, 18032, 16020, 12004 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
}
},
"1": {
"15": {
"music_id": [ 15032, 29033, 27092, 30020 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"16": {
"music_id": [ 10028, 26070, 28091, 23075 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"17": {
"music_id": [ 26012, 28002, 17017, 28005 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"18": {
"music_id": [ 28008, 15001, 19002, 9028 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
}
}
},
"32": {
"0": {
"15": {
"music_id": [ 19022, 30033, 27013, 29045 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"16": {
"music_id": [ 27034, 24023, 16009, 25085 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"17": {
"music_id": [ 26087, 19002, 29050, 30024 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"18": {
"music_id": [ 30052, 18032, 16020, 12004 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
}
},
"1": {
"15": {
"music_id": [ 12002, 31063, 23046, 30020 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"16": {
"music_id": [ 26106, 14021, 29052, 23075 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"17": {
"music_id": [ 29042, 26043, 17017, 28005 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
},
"18": {
"music_id": [ 25007, 29017, 19002, 9028 ],
"class_id": [ 3, 3, 3, 3 ],
"kind": 0
}
}
}
}

View File

@ -0,0 +1,82 @@
{
"26": {
"26002": { "kind": 0 },
"26006": { "kind": 0 },
"26022": { "kind": 0 },
"26045": { "kind": 0 }
},
"27": {
"27070": { "kind": 0 },
"27071": { "kind": 0 },
"27073": { "kind": 0 }
},
"28": {
"28001": { "kind": 0 },
"28036": { "kind": 0 },
"28038": { "kind": 0 },
"28072": { "kind": 0 },
"28074": { "kind": 0 }
},
"29": {
"29071": { "kind": 0 },
"29072": { "kind": 0 },
"29075": { "kind": 0 },
"29081": { "kind": 0 },
"29082": { "kind": 0 },
"29085": { "kind": 0 },
"29101": { "kind": 0 },
"29102": { "kind": 0 },
"29103": { "kind": 0 }
},
"30": {
"30038": { "kind": 0 },
"30082": { "kind": 0 },
"30083": { "kind": 0 },
"30084": { "kind": 0 },
"30085": { "kind": 0 },
"30101": { "kind": 0 },
"30102": { "kind": 0 },
"30104": { "kind": 0 },
"30105": { "kind": 0 }
},
"31": {
"31021": { "kind": 0 },
"31022": { "kind": 0 },
"31023": { "kind": 0 },
"31024": { "kind": 0 },
"31025": { "kind": 0 },
"31065": { "kind": 0 },
"31066": { "kind": 0 },
"31097": { "kind": 0 },
"31098": { "kind": 0 },
"31099": { "kind": 0 },
"31100": { "kind": 0 },
"31101": { "kind": 0 },
"31102": { "kind": 0 },
"31110": { "kind": 0 },
"31112": { "kind": 0 },
"31113": { "kind": 0 }
},
"32": {
"32022": { "kind": 0 },
"32049": { "kind": 0 },
"32078": { "kind": 0 },
"32079": { "kind": 0 },
"32080": { "kind": 0 },
"32081": { "kind": 0 },
"32082": { "kind": 0 },
"32083": { "kind": 0 },
"32084": { "kind": 0 },
"32085": { "kind": 0 },
"32096": { "kind": 0 },
"32097": { "kind": 0 },
"32098": { "kind": 0 },
"32019": { "kind": 0 },
"32101": { "kind": 0 },
"32102": { "kind": 0 },
"32103": { "kind": 0 },
"32104": { "kind": 0 },
"32110": { "kind": 0 },
"32111": { "kind": 0 }
}
}

View File

@ -0,0 +1,177 @@
import { IIDX_CPUS } from "../models/arena";
import { GetVersion } from "../util";
export const gssysteminfo: EPR = async (info, data, send) => {
const version = GetVersion(info);
if (version < 24) return send.success();
let result: any = {
arena_schedule: {
phase: K.ITEM("u8", U.GetConfig("ArenaPhase")),
start: K.ITEM("u32", 1605784800),
end: K.ITEM("u32", 4102326000)
},
arena_music_difficult: [],
maching_class_range: [],
arena_cpu_define: [],
}
// following datas are made up needs to figure out correct way to do it //
let music_open = JSON.parse(await IO.ReadFile("data/music_open.json", "utf-8"));
if (!_.isNil(music_open[version])) {
result = Object.assign(result, { music_open: [] });
Object.keys(music_open).forEach(v => {
Object.keys(music_open[v]).forEach(m => {
if (Number(v) > version) return;
result.music_open.push({
music_id: K.ITEM("s32", Number(m)),
kind: K.ITEM("s32", music_open[v][m].kind),
});
});
});
}
switch (version) {
case 32:
result.arena_schedule.phase = K.ITEM("u8", 3);
result.arena_schedult = Object.assign(result.arena_schedule, { season: K.ITEM("u8", 0) }); // arena season for online //
case 31:
result.arena_schedult = Object.assign(result.arena_schedule, { rule_type: K.ITEM("u8", 0) }); // arena rule for online //
result = Object.assign(result, { grade_course: [] });
// following datas are made up needs to figure out correct way to do it //
let grade = JSON.parse(await IO.ReadFile("data/grade.json", "utf-8"));
if (!_.isNil(grade[version])) {
Object.keys(grade[version]).forEach(s => {
Object.keys(grade[version][s]).forEach(c => {
result.grade_course.push({
play_style: K.ITEM("s32", Number(s)),
grade_id: K.ITEM("s32", Number(c)),
is_valid: K.ITEM("bool", true),
music_id_0: K.ITEM("s32", grade[version][s][c].music_id[0]),
class_id_0: K.ITEM("s32", grade[version][s][c].class_id[0]),
music_id_1: K.ITEM("s32", grade[version][s][c].music_id[1]),
class_id_1: K.ITEM("s32", grade[version][s][c].class_id[1]),
music_id_2: K.ITEM("s32", grade[version][s][c].music_id[2]),
class_id_2: K.ITEM("s32", grade[version][s][c].class_id[2]),
music_id_3: K.ITEM("s32", grade[version][s][c].music_id[3]),
class_id_3: K.ITEM("s32", grade[version][s][c].class_id[3]),
index: K.ITEM("s32", result.grade_course.length),
cube_num: K.ITEM("s32", 0),
kind: K.ITEM("s32", grade[version][s][c].kind),
});
});
});
}
default:
break;
}
// arena_music_difficult //
for (let s = 0; s < 2; ++s) {
for (let c = 0; c < 20; ++c) {
result.arena_music_difficult.push({
play_style: K.ITEM("s32", s),
arena_class: K.ITEM("s32", c),
low_difficult: K.ITEM("s32", 1),
high_difficult: K.ITEM("s32", 12),
is_leggendaria: K.ITEM("bool", 1),
force_music_list_id: K.ITEM("s32", 0),
});
result.maching_class_range.push({
play_style: K.ITEM("s32", s),
matching_class: K.ITEM("s32", c),
low_arena_class: K.ITEM("s32", 1),
high_arena_class: K.ITEM("s32", 20),
});
result.arena_cpu_define.push({
play_style: K.ITEM("s32", s),
arena_class: K.ITEM("s32", c),
grade_id: K.ITEM("s32", IIDX_CPUS[s][c][0]),
low_music_difficult: K.ITEM("s32", IIDX_CPUS[s][c][1]),
high_music_difficult: K.ITEM("s32", IIDX_CPUS[s][c][2]),
is_leggendaria: K.ITEM("bool", IIDX_CPUS[s][c][3]),
});
}
}
switch (version) {
case 29:
result = Object.assign(result, {
CommonBossPhase: K.ATTR({ val: String(3) }),
Event1InternalPhase: K.ATTR({ val: String(U.GetConfig("ch_event")) }),
ExtraBossEventPhase: K.ATTR({ val: String(U.GetConfig("ch_extraboss")) }),
isNewSongAnother12OpenFlg: K.ATTR({ val: String(Number(U.GetConfig("NewSongAnother12"))) }),
gradeOpenPhase: K.ATTR({ val: String(U.GetConfig("Grade")) }),
isEiseiOpenFlg: K.ATTR({ val: String(Number(U.GetConfig("Eisei"))) }),
WorldTourismOpenList: K.ATTR({ val: String(-1) }),
BPLBattleOpenPhase: K.ATTR({ val: String(2) }),
});
break;
case 30:
result = Object.assign(result, {
CommonBossPhase: K.ATTR({ val: String(3) }),
Event1InternalPhase: K.ATTR({ val: String(U.GetConfig("rs_event")) }),
ExtraBossEventPhase: K.ATTR({ val: String(U.GetConfig("rs_extraboss")) }),
isNewSongAnother12OpenFlg: K.ATTR({ val: String(Number(U.GetConfig("NewSongAnother12"))) }),
gradeOpenPhase: K.ATTR({ val: String(U.GetConfig("Grade")) }),
isEiseiOpenFlg: K.ATTR({ val: String(Number(U.GetConfig("Eisei"))) }),
WorldTourismOpenList: K.ATTR({ val: String(-1) }),
BPLBattleOpenPhase: K.ATTR({ val: String(2) }),
})
break;
case 31:
let totalMetron = 0;
let eventData = await DB.Find(null, {
collection: "event_1",
version: version,
event_data: "myepo_map",
});
if (!_.isNil(eventData)) {
eventData.forEach((res: any) => {
totalMetron += Number(res.metron_total_get);
});
}
Object.assign(result, {
CommonBossPhase: K.ATTR({ val: String(3) }),
Event1Value: K.ATTR({ val: String(U.GetConfig("ep_event")) }),
Event1Phase: K.ATTR({ val: String(U.GetConfig("ep_event1")) }),
Event2Phase: K.ATTR({ val: String(U.GetConfig("ep_event2")) }),
ExtraBossEventPhase: K.ATTR({ val: String(U.GetConfig("ep_extraboss")) }),
isNewSongAnother12OpenFlg: K.ATTR({ val: String(Number(U.GetConfig("NewSongAnother12"))) }),
isKiwamiOpenFlg: K.ATTR({ val: String(Number(U.GetConfig("Eisei"))) }),
WorldTourismOpenList: K.ATTR({ val: String(-1) }),
BPLBattleOpenPhase: K.ATTR({ val: String(2) }),
UnlockLeggendaria: K.ATTR({ val: String(1) }),
BPLSerialCodePhase: K.ATTR({ val: String(0) }),
Event1AllPlayerTotalGetMetron: K.ATTR({ val: String(totalMetron) }), // total amount of all users metron //
});
break;
case 32:
result = Object.assign(result, {
Event1Value: K.ATTR({ val: String(U.GetConfig("pc_event")) }), // TEST //
Event1Phase: K.ATTR({ val: String(U.GetConfig("pc_event1")) }), // TEST //
Event2Phase: K.ATTR({ val: String(U.GetConfig("pc_event2")) }), // TEST //
ExtraBossEventPhase: K.ATTR({ val: String(U.GetConfig("pc_extraboss")) }), // TEST //
isNewSongAnother12OpenFlg: K.ATTR({ val: String(Number(U.GetConfig("NewSongAnother12"))) }),
isKiwamiOpenFlg: K.ATTR({ val: String(Number(U.GetConfig("Eisei"))) }),
WorldTourismOpenList: K.ATTR({ val: String(-1) }),
OldBPLBattleOpenPhase: K.ATTR({ val: String(3) }),
});
break;
default:
break;
}
return send.object(result);
};

View File

@ -0,0 +1,208 @@
import { pcdata } from "../models/pcdata";
import { grade } from "../models/grade";
import { IDtoRef, GetVersion } from "../util";
import { eisei_grade } from "../models/lightning";
import { badge } from "../models/badge";
export const graderaised: EPR = async (info, data, send) => {
const version = GetVersion(info);
const iidxid = Number($(data).attr().iidxid);
const refid = await IDtoRef(iidxid);
const gid = Number($(data).attr().gid);
const gtype = Number($(data).attr().gtype);
let cflg = Number($(data).attr().cflg);
let achi = Number($(data).attr().achi);
let pcdata = await DB.FindOne<pcdata>(refid, { collection: "pcdata", version: version });
let grade = await DB.FindOne<grade>(refid, {
collection: "grade",
version: version,
style: gtype,
gradeId: gid,
});
if (version >= 23) cflg = Number($(data).attr().cstage);
const isTDJ = !_.isNil($(data).element("lightning_play_data")); // lightning model //
const hasEiseiData = (!_.isNil($(data).element("eisei_data")) || !_.isNil($(data).element("eisei_grade_data")) || !_.isNil($(data).element("kiwami_data")));
if (isTDJ && hasEiseiData) {
let eisei_clear_type: number;
let eisei_grade_id: number;
let eisei_grade_type: number;
let eisei_stage_num: number;
let eisei_option: number;
let eisei_past_achievement: number[];
let eisei_past_selected_course: number[];
let eisei_max_past_achievement: number[];
let eisei_max_past_selected_course: number[];
switch (version) {
case 27:
eisei_clear_type = Number($(data).attr("eisei_data").clear_type);
eisei_grade_id = Number($(data).attr("eisei_data").grade_id);
eisei_grade_type = Number($(data).attr("eisei_data").grade_type);
eisei_stage_num = Number($(data).attr("eisei_data").stage_num);
eisei_past_achievement = $(data).element("eisei_data").numbers("past_achievement");
eisei_max_past_achievement = $(data).element("eisei_data").numbers("max_past_achievement");
break;
case 30:
eisei_clear_type = Number($(data).element("eisei_data").attr().clear_type);
eisei_grade_id = Number($(data).element("eisei_data").attr().grade_id);
eisei_grade_type = Number($(data).element("eisei_data").attr().grade_type);
eisei_stage_num = Number($(data).element("eisei_data").attr().stage_num);
eisei_option = Number($(data).element("eisei_data").attr().option);
eisei_past_achievement = $(data).element("eisei_data").numbers("past_achievement");
eisei_past_selected_course = $(data).element("eisei_data").numbers("past_selected_course");
eisei_max_past_achievement = $(data).element("eisei_data").numbers("max_past_achievement");
eisei_max_past_selected_course = $(data).element("eisei_data").numbers("max_past_selected_course");
break;
case 31:
case 32:
eisei_clear_type = Number($(data).attr("kiwami_data").clear_type);
eisei_grade_id = Number($(data).attr("kiwami_data").grade_id);
eisei_grade_type = Number($(data).attr("kiwami_data").grade_type);
eisei_stage_num = Number($(data).attr("kiwami_data").stage_num);
eisei_option = Number($(data).attr("kiwami_data").option);
eisei_past_achievement = $(data).element("kiwami_data").numbers("past_achievement");
eisei_past_selected_course = $(data).element("kiwami_data").numbers("past_selected_course");
eisei_max_past_achievement = $(data).element("kiwami_data").numbers("max_past_achievement");
eisei_max_past_selected_course = $(data).element("kiwami_data").numbers("max_past_selected_course");
break;
default:
eisei_clear_type = Number($(data).attr("eisei_grade_data").clear_type);
eisei_grade_id = Number($(data).attr("eisei_grade_data").grade_id);
eisei_grade_type = Number($(data).attr("eisei_grade_data").grade_type);
eisei_stage_num = Number($(data).attr("eisei_grade_data").stage_num);
eisei_past_achievement = $(data).element("eisei_grade_data").numbers("past_achievement");
eisei_past_selected_course = $(data).element("eisei_grade_data").numbers("past_selected_course");
eisei_max_past_achievement = $(data).element("eisei_grade_data").numbers("max_past_achievement");
eisei_max_past_selected_course = $(data).element("eisei_grade_data").numbers("max_past_selected_course");
break;
}
await DB.Upsert<eisei_grade>(
refid,
{
collection: "eisei_grade",
version: version,
grade_type: eisei_grade_type,
grade_id: eisei_grade_id,
},
{
$set: {
clear_type: eisei_clear_type,
stage_num: eisei_stage_num,
option: eisei_option,
past_achievement: eisei_past_achievement,
past_selected_course: eisei_past_selected_course,
max_past_achievement: eisei_max_past_achievement,
max_past_selected_course: eisei_max_past_selected_course,
},
}
);
return send.object(
K.ATTR({
pnum: "1", // This isn't visible to user and seems leftover //
})
);
}
let updatePcdata = false;
let updateGrade = false;
if (_.isNil(pcdata)) return send.deny();
if (_.isNil(grade)) {
if (cflg == 4) {
if (gtype == 0) pcdata.sgid = Math.max(gid, pcdata.sgid);
else pcdata.dgid = Math.max(gid, pcdata.dgid);
updatePcdata = true;
}
updateGrade = true;
} else {
if (cflg >= grade.maxStage || achi >= grade.archive) {
cflg = Math.max(cflg, grade.maxStage);
achi = Math.max(achi, grade.archive);
updateGrade = true;
}
if (cflg == 4) {
if (gtype == 0) pcdata.sgid = Math.max(gid, pcdata.sgid);
else pcdata.dgid = Math.max(gid, pcdata.dgid);
updatePcdata = true;
}
}
if (updatePcdata) {
await DB.Upsert<pcdata>(
refid,
{
collection: "pcdata",
version: version,
},
{
$set: pcdata
}
);
}
if (updateGrade) {
await DB.Upsert<grade>(
refid,
{
collection: "grade",
version: version,
style: gtype,
gradeId: gid,
},
{
$set: {
maxStage: cflg,
archive: achi,
}
}
);
}
if (!_.isNil($(data).element("badge"))) {
await DB.Upsert<badge>(
refid,
{
collection: "badge",
version: version,
category_name: "grade",
flg_id: Number($(data).attr("badge").badge_flg_id),
},
{
$set: {
flg: Number($(data).attr("badge").badge_flg),
}
}
);
}
let gradeUser = await DB.Find<grade>(null, {
collection: "grade",
version: version,
style: gtype,
gradeId: gid,
maxStage: 4,
});
return send.object(
K.ATTR({
pnum: String(gradeUser.length),
})
);
};

File diff suppressed because it is too large Load Diff

5350
iidx@asphyxia/handlers/pc.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
import { expert, ranking } from "../models/ranking";
import { profile } from "../models/profile";
import { GetVersion, IDtoRef } from "../util";
export const rankingentry: EPR = async (info, data, send) => {
// pside //
const version = GetVersion(info);
const refid = await IDtoRef(Number($(data).attr().iidxid));
const coid = Number($(data).attr().coid);
const clid = Number($(data).attr().clid);
const opname = $(data).attr().opname;
const oppid = Number($(data).attr().oppid);
const pgnum = Number($(data).attr().pgnum);
const gnum = Number($(data).attr().gnum);
const opt = Number($(data).attr().opt);
const opt2 = Number($(data).attr().opt2);
const clr = Number($(data).attr().clr);
const exscore = (pgnum * 2 + gnum);
const cstage = Number($(data).attr().cstage);
const expert_data = await DB.FindOne<expert>(refid, {
collection: "expert",
version: version,
coid: coid,
});
let pgArray = Array<number>(6).fill(0); // PGREAT //
let gArray = Array<number>(6).fill(0); // GREAT //
let cArray = Array<number>(6).fill(0); // CLEAR FLAGS //
let optArray = Array<number>(6).fill(0); // USED OPTION (SP/DP) //
let opt2Array = Array<number>(6).fill(0); // USED OPTION (DP) //
let esArray = Array<number>(6).fill(0); // EXSCORE //
if (_.isNil(expert_data)) {
cArray[clid] = clr;
pgArray[clid] = pgnum;
gArray[clid] = gnum;
optArray[clid] = opt;
opt2Array[clid] = opt2;
esArray[clid] = exscore;
}
else {
cArray = expert_data.cArray;
pgArray = expert_data.pgArray;
gArray = expert_data.gArray;
optArray = expert_data.optArray;
opt2Array = expert_data.opt2Array;
esArray = expert_data.esArray;
const pExscore = esArray[clid];
if (exscore > pExscore) {
pgArray[clid] = pgnum;
gArray[clid] = gnum;
optArray[clid] = opt;
opt2Array[clid] = opt2;
esArray[clid] = exscore;
}
cArray[clid] = Math.max(cArray[clid], clr);
}
await DB.Upsert<expert>(
refid,
{
collection: "expert",
version: version,
coid: coid,
},
{
$set: {
cArray,
pgArray,
gArray,
optArray,
opt2Array,
esArray,
}
}
);
const profile = await DB.FindOne<profile>(refid, {
collection: "profile",
});
const name = profile.name;
await DB.Upsert<ranking>(
{
collection: "ranking",
version: version,
coid: coid,
clid: clid,
},
{
$set: {
pgnum: pgnum,
gnum: gnum,
name: name,
opname: opname,
pid: oppid,
udate: 0,
exscore: exscore,
maxStage: cstage,
}
}
);
let expertUser = await DB.Find<ranking>({
collection: "ranking",
version: version,
coid: coid,
clid: clid,
});
expertUser.sort((a: ranking, b: ranking) => b.exscore - a.exscore);
let rankPos = expertUser.findIndex((a: ranking) => a.name == name);
return send.object(K.ATTR({
anum: String(expertUser.length),
jun: String(rankPos + 1),
}));
};
export const rankingoentry: EPR = async (info, data, send) => {
const version = GetVersion(info);
const refid = await IDtoRef(Number($(data).attr().iidxid));
const coid = Number($(data).attr().coid);
const clid = Number($(data).attr().clid);
const pgnum = Number($(data).attr().pgnum);
const gnum = Number($(data).attr().gnum);
const opt = Number($(data).attr().opt);
const opt2 = Number($(data).attr().opt2);
const clr = Number($(data).attr().clr);
const exscore = (pgnum * 2 + gnum);
// TODO:: figure out what this does //
return send.success();
};
export const rankinggetranker: EPR = async (info, data, send) => {
const version = GetVersion(info);
const ranking = await DB.Find<ranking>({
collection: "ranking",
version: version,
coid: Number($(data).attr().coid),
clid: Number($(data).attr().clid),
});
let result = {
ranker: [],
}
if (_.isNil(ranking)) return send.success();
ranking.sort((a: ranking, b: ranking) => b.exscore - a.exscore);
ranking.forEach((res) => {
result.ranker.push(
K.ATTR({
gnum: String(res.gnum),
pgnum: String(res.pgnum),
name: res.name,
opname: res.opname,
pid: String(res.pid),
udate: String(res.udate),
})
);
});
return send.object(result);
};

View File

@ -0,0 +1,89 @@
import { convention_data, shop_data } from "../models/shop";
import { GetVersion } from "../util";
export const shopgetname: EPR = async (info, data, send) => {
const shop_data = await DB.FindOne<shop_data>({
collection: "shop_data",
});
if (_.isNil(shop_data)) {
await DB.Insert<shop_data>({
collection: "shop_data",
opname: "",
pid: 53,
cls_opt: 0,
});
return send.object(
K.ATTR({
opname: "",
pid: "53",
cls_opt: "0",
hr: "0",
mi: "0",
}),
{ encoding: "shift_jis" }
);
}
return send.object(
K.ATTR({
opname: shop_data.opname,
pid: String(shop_data.pid),
cls_opt: String(shop_data.cls_opt),
hr: "0",
mi: "0",
}),
{ encoding: "shift_jis" }
);
};
export const shopsavename: EPR = async (info, data, send) => {
// removed saving code as opname attribute being sent as shift_jis but KDataReader read as utf-8 //
return send.success();
};
export const shopgetconvention: EPR = async (info, data, send) => {
const version = GetVersion(info);
const convention_data = await DB.FindOne<convention_data>({
collection: "shop_convention",
version: version,
});
if (_.isNil(convention_data)) return send.deny();
return send.object(
K.ATTR({
music_0: String(convention_data.music_0),
music_1: String(convention_data.music_1),
music_2: String(convention_data.music_2),
music_3: String(convention_data.music_3),
},
{
valid: K.ITEM("bool", convention_data.valid),
})
);
};
export const shopsetconvention: EPR = async (info, data, send) => {
const version = GetVersion(info);
await DB.Upsert<convention_data>(
{
collection: "shop_convention",
version: version,
},
{
$set: {
music_0: $(data).number("music_0"),
music_1: $(data).number("music_1"),
music_2: $(data).number("music_2"),
music_3: $(data).number("music_3"),
valid: $(data).bool("valid"),
},
}
);
return send.success();
};

View File

@ -0,0 +1,429 @@
import { profile } from "../models/profile";
import { rival } from "../models/rival";
import { custom } from "../models/custom";
import { score, old_score } from "../models/score";
import { lightning_custom } from "../models/lightning";
export const updateRivalSettings = async (data) => {
let update_array = [];
if (!(_.isEmpty(data.sp_rival1))) {
let update_data = {
play_style: 1,
index: 0,
rival_refid: data.sp_rival1,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 1,
index: 0,
}
)
}
if (!(_.isEmpty(data.sp_rival2))) {
let update_data = {
play_style: 1,
index: 1,
rival_refid: data.sp_rival2,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 1,
index: 1,
}
)
}
if (!(_.isEmpty(data.sp_rival3))) {
let update_data = {
play_style: 1,
index: 2,
rival_refid: data.sp_rival3,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 1,
index: 2,
}
)
}
if (!(_.isEmpty(data.sp_rival4))) {
let update_data = {
play_style: 1,
index: 3,
rival_refid: data.sp_rival4,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 1,
index: 3,
}
)
}
if (!(_.isEmpty(data.sp_rival5))) {
let update_data = {
play_style: 1,
index: 4,
rival_refid: data.sp_rival5,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 1,
index: 4,
}
)
}
if (!(_.isEmpty(data.dp_rival1))) {
let update_data = {
play_style: 2,
index: 0,
rival_refid: data.dp_rival1,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 2,
index: 0,
}
)
}
if (!(_.isEmpty(data.dp_rival2))) {
let update_data = {
play_style: 2,
index: 1,
rival_refid: data.dp_rival2,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 2,
index: 1,
}
)
}
if (!(_.isEmpty(data.dp_rival3))) {
let update_data = {
play_style: 2,
index: 2,
rival_refid: data.dp_rival3,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 2,
index: 2,
}
)
}
if (!(_.isEmpty(data.dp_rival4))) {
let update_data = {
play_style: 2,
index: 3,
rival_refid: data.dp_rival4,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 2,
index: 3,
}
)
}
if (!(_.isEmpty(data.dp_rival5))) {
let update_data = {
play_style: 2,
index: 4,
rival_refid: data.dp_rival5,
};
update_array.push(update_data);
} else {
await DB.Remove<rival>(data.refid,
{
collection: "rival",
play_style: 2,
index: 4,
}
)
}
for (let i = 0; i < update_array.length; i++) {
await DB.Upsert<rival>(data.refid, {
collection: "rival",
play_style: update_array[i].play_style,
index: update_array[i].index,
}, {
$set: {
rival_refid: update_array[i].rival_refid,
}
}
)
}
};
export const updateCustomSettings = async (data) => {
const profile = await DB.FindOne<profile>(data.refid, {
collection: "profile",
});
let customize = {
frame: Number(data.frame),
turntable: Number(data.turntable),
note_burst: Number(data.note_burst),
menu_music: Number(data.menu_music),
lane_cover: Number(data.lane_cover),
category_vox: Number(data.category_vox),
note_skin: Number(data.note_skin),
full_combo_splash: Number(data.full_combo_splash),
disable_musicpreview: StoB(data.disable_musicpreview),
note_beam: Number(data.note_beam),
judge_font: Number(data.judge_font),
pacemaker_cover: Number(data.pacemaker_cover),
vefx_lock: StoB(data.vefx_lock),
effect: Number(data.effect),
bomb_size: Number(data.bomb_size),
disable_hcn_color: StoB(data.disable_hcn_color),
first_note_preview: Number(data.first_note_preview),
rank_folder: StoB(data.rank_folder),
clear_folder: StoB(data.clear_folder),
diff_folder: StoB(data.diff_folder),
alpha_folder: StoB(data.alpha_folder),
rival_folder: StoB(data.rival_folder),
rival_battle_folder: StoB(data.rival_battle_folder),
rival_info: StoB(data.rival_info),
hide_playcount: StoB(data.hide_playcount),
disable_graph_cutin: StoB(data.disable_graph_cutin),
classic_hispeed: StoB(data.classic_hispeed),
rival_played_folder: StoB(data.rival_played_folder),
hide_iidxid: StoB(data.hide_iidxid),
disable_beginner_option: StoB(data.disable_beginner_option),
qpro_head: Number(data.qpro_head),
qpro_hair: Number(data.qpro_hair),
qpro_face: Number(data.qpro_face),
qpro_hand: Number(data.qpro_hand),
qpro_body: Number(data.qpro_body),
qpro_back: Number(data.qpro_back),
}
await DB.Upsert<custom>(data.refid, {
collection: "custom",
version: Number(data.version)
},
{
$set: customize
});
if (!_.isEmpty(data.name) && data.name != profile.name) {
// TODO:: check name is in valid format //
await DB.Upsert<profile>(data.refid, {
collection: "profile",
}, {
$set: {
name: data.name
}
});
}
if (data.version > 27) {
await DB.Upsert<lightning_custom>(data.refid, {
collection: "lightning_custom",
version: Number(data.version)
},
{
$set: {
premium_skin: Number(data.lm_skin),
premium_bg: Number(data.lm_bg),
}
});
}
};
export const importScoreData = async (data, send: WebUISend) => {
if (_.isEmpty(data.data)) {
console.error("[Score Importer] Supplied data is empty");
return send.error(400, "Empty data");
}
let content = null;
let version = 0;
let count = 0;
try {
content = JSON.parse(data.data);
version = content.version;
count = content.count;
}
catch {
console.error("[Score Importer] Invaild data has been supplied");
return send.error(400, "Invalid data");
}
switch (version) {
case 1:
let sd_ver1: old_score[] = content.data;
for (let a = 0; a < count; a++) {
let result = {
pgArray: Array<number>(10).fill(0),
gArray: Array<number>(10).fill(0),
mArray: Array<number>(10).fill(-1),
cArray: Array<number>(10).fill(0),
rArray: Array<number>(10).fill(-1),
esArray: Array<number>(10).fill(0),
optArray: Array<number>(10).fill(0),
opt2Array: Array<number>(10).fill(0),
}
if (!_.isNil(sd_ver1[a].spmArray)) {
for (let b = 0; b < 5; b++) {
result.cArray[b] = sd_ver1[a].spmArray[2 + b];
result.esArray[b] = sd_ver1[a].spmArray[7 + b];
if (sd_ver1[a].spmArray[12 + b] != -1) result.mArray[b] = sd_ver1[a].spmArray[12 + b];
}
}
if (!_.isNil(sd_ver1[a].dpmArray)) {
for (let b = 5; b < 10; b++) {
result.cArray[b] = sd_ver1[a].dpmArray[2 + (b - 5)];
result.esArray[b] = sd_ver1[a].dpmArray[7 + (b - 5)];
if (sd_ver1[a].dpmArray[12 + (b - 5)] != -1) result.mArray[b] = sd_ver1[a].dpmArray[12 + (b - 5)];
}
}
if (!_.isNil(sd_ver1[a].optArray)) {
result.optArray = sd_ver1[a].optArray;
}
if (!_.isNil(sd_ver1[a].opt2Array)) {
result.opt2Array = sd_ver1[a].opt2Array;
}
for (let b = 0; b < 10; b++) {
if (_.isNil(sd_ver1[a][b])) continue;
result[b] = sd_ver1[a][b];
if (!_.isNil(sd_ver1[a][b + 10])) {
result[b + 10] = sd_ver1[a][b + 10];
}
}
await DB.Upsert<score>(data.refid,
{
collection: "score",
mid: sd_ver1[a].music_id
},
{
$set: {
...result
}
}
);
}
break;
case 2:
let sd_ver2: score[] = content.data;
for (let a = 0; a < count; a++) {
let result = {
pgArray: sd_ver2[a].pgArray,
gArray: sd_ver2[a].gArray,
mArray: sd_ver2[a].mArray,
cArray: sd_ver2[a].cArray,
rArray: sd_ver2[a].rArray,
esArray: sd_ver2[a].esArray,
optArray: sd_ver2[a].optArray,
opt2Array: sd_ver2[a].opt2Array,
};
for (let b = 0; b < 10; b++) {
if (_.isNil(sd_ver2[a][b])) continue;
result[b] = sd_ver2[a][b];
if (!_.isNil(sd_ver2[a][b + 10])) {
result[b + 10] = sd_ver2[a][b + 10];
}
}
await DB.Upsert<score>(data.refid,
{
collection: "score",
mid: sd_ver2[a].mid
},
{
$set: {
...result,
}
}
);
}
break;
default:
console.error("[Score Importer] Unregistered score data version");
return send.error(400, "Invalid data version");
}
}
export const exportScoreData = async (data, send: WebUISend) => {
const score = await DB.Find<score>(data.refid, {
collection: "score"
});
if (score == null) return send.error(400, "No data");
let result = {
version: 2,
count: score.length,
data: {
...score,
}
}
send.json(result);
}
function StoB(value: string) {
return value == "on" ? true : false;
};

598
iidx@asphyxia/index.ts Normal file
View File

@ -0,0 +1,598 @@
import { pccommon, pcreg, pcget, pcgetname, pctakeover, pcvisit, pcsave, pcoldget, pcgetlanegacha, pcdrawlanegacha, pcshopregister } from "./handlers/pc";
import { shopgetname, shopsavename, shopgetconvention, shopsetconvention } from "./handlers/shop";
import { musicreg, musicgetrank, musicappoint, musicarenacpu, musiccrate, musicbreg, musicgetralive } from "./handlers/music";
import { graderaised } from "./handlers/grade";
import { gssysteminfo } from "./handlers/gamesystem";
import { updateRivalSettings, updateCustomSettings, importScoreData, exportScoreData } from "./handlers/webui";
import { GetVersion } from "./util";
import { rankingentry, rankinggetranker, rankingoentry } from "./handlers/ranking";
export function register() {
if (CORE_VERSION_MAJOR <= 1 && CORE_VERSION_MINOR < 31) {
console.error("The current version of Asphyxia Core is not supported. Requires version '1.31' or later.");
return;
}
R.Contributor("duel0213");
R.GameCode("GLD");
R.GameCode("HDD");
R.GameCode("I00");
R.GameCode("JDJ");
R.GameCode("JDZ");
R.GameCode("KDZ");
R.GameCode("LDJ");
// common //
R.Config("BeatPhase", {
name: "Beat #",
desc: "1 / 2 / 3 / FREE", // This can be event phase on old versions //
type: "integer",
default: 3, // BEAT FREE //
});
// ~ Resort Anthem (common) / /
R.Config("cmd_gmbl", {
name: "G.JUDGE",
desc: "Enable G.JUDGE Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
R.Config("cmd_gmbla", {
name: "G.JUDGE-A",
desc: "Enable G.JUDGE-A Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
R.Config("cmd_regl", {
name: "REGUL-SPEED",
desc: "Enable REGUL-SPEED Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
R.Config("cmd_rndp", {
name: "RANDOM+",
desc: "Enable RANDOM+ Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
R.Config("cmd_hrnd", {
name: "H-RANDOM",
desc: "Enable H-RANDOM Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
R.Config("cmd_alls", {
name: "ALL-SCRATCH",
desc: "Enable ALL-SCRATCH Command (~ Resort Anthem)",
type: "boolean",
default: true,
});
// SPADA ~ (common) //
R.Config("NewSongAnother12", {
name: "New Song Another",
desc: "Enables ANOTHER difficulty of current version's new songs that has Level 12",
type: "boolean",
default: true,
});
// PENDUAL ~ (common) //
R.Config("ExpertPhase", {
name: "Expert Phase",
type: "integer",
default: 2,
});
R.Config("ExpertRandomPhase", {
name: "Expert Random Phase",
type: "integer",
default: 2,
});
// HEROIC VERSE ~ (common) //
R.Config("ArenaPhase", {
name: "ARENA Phase",
type: "integer",
default: 2, // ADVERTISE //
});
// BISTROVER ~ (common) //
R.Config("MovieUpload", {
name: "Movie Upload URL",
type: "string",
desc: "API address for play video uploading feature (JSON)",
default: "http://localhost/"
});
R.Config("Eisei", {
name: "Eisei Grade Courses",
desc: "Enable EISEI/KIWAMI Grade Courses",
type: "boolean",
default: true,
});
// CastHour ~ RESIDENT (common) //
R.Config("Grade", {
name: "Grade Open Phase",
desc: "RED / KAIDEN",
type: "integer",
default: 2,
})
// SIRIUS //
R.Config("sr_league", {
name: "League Phase (SR)",
type: "integer",
default: 0,
});
// Resort Anthem //
R.Config("ra_league", {
name: "League Phase (RA)",
type: "integer",
default: 0,
});
R.Config("ra_story", {
name: "Story Phase (RA)",
type: "integer",
default: 0,
});
R.Config("ra_event", {
name: "Tour Phase (RA)",
type: "integer",
default: 3,
});
R.Config("ra_lincle", {
name: "Lincle LINK Phase (RA)",
type: "integer",
default: 1,
});
// Lincle //
R.Config("lc_lincle", {
name: "Lincle LINK Phase (LC)",
type: "integer",
default: 2,
});
R.Config("lc_boss", {
name: "Lincle Kingdom Phase",
type: "integer",
default: 2,
});
// tricoro //
R.Config("tr_limit", {
name: "Limit Burst Phase (TR)",
type: "integer",
default: 24, // TODO:: verify //
});
R.Config("tr_boss", {
name: "Event Phase (TR)",
desc: "RED / BLUE / YELLOW",
type: "integer",
default: 3,
});
R.Config("tr_red", {
name: "RED Phase",
desc: "LEGEND CROSS Phase",
type: "integer",
default: 3,
});
R.Config("tr_yellow", {
name: "YELLOW Phase",
desc: "ぼくらの宇宙戦争 Phase",
type: "integer",
default: 3,
});
R.Config("tr_medal", {
name: "Medal Phase (TR)",
type: "integer",
default: 3,
});
R.Config("tr_cafe", {
name: "Café de Tran",
desc: "Enable Café de Tran Event (tricoro)",
type: "boolean",
default: true,
});
R.Config("tr_tripark", {
name: "Everyone's SPACEWAR!!",
desc: "Enable クプロ・ミミニャミ・パステルくんのみんなで宇宙戦争!! Event (tricoro)",
type: "boolean",
default: true,
});
// SPADA //
R.Config("sp_limit", {
name: "Limit Burst Phase (SP)",
type: "integer",
default: 24,
});
R.Config("sp_boss", {
name: "Event Phase (SP)",
desc: "Spada†leggendaria Phase",
type: "integer",
default: 3,
});
R.Config("sp_boss1", {
name: "Qprogue Phase (SP)",
type: "integer",
default: 4,
});
R.Config("sp_cafe", {
name: "Café de Tran",
desc: "Enable Café de Tran Event (SPADA)",
type: "boolean",
default: true,
});
R.Config("sp_tripark", {
name: "Everyone's SPACEWAR!!",
desc: "Enable クプロ・ミミニャミ・パステルくんのみんなで宇宙戦争!! Event (SPADA)",
type: "boolean",
default: true,
});
R.Config("sp_triparkskip", {
name: "Everyone's SPACEWAR!! Skip",
desc: "Skips クプロ・ミミニャミ・パステルくんのみんなで宇宙戦争!! Event Scenes",
type: "integer",
default: 2,
});
R.Config("sp_superstar", {
name: "SUPER STAR -MITSURU-",
desc: "SUPER STAR 満 -MITSURU- 完全復活祭 Phase",
type: "integer",
default: 2,
});
// PENDUAL //
R.Config("pd_preplay", {
name: "SUPER FUTURE 2323 Phase",
type: "integer",
default: 2,
});
R.Config("pd_tohoremix", {
name: "BEMANI X TOHO",
desc: "BEMANI×TOHO REITAISAI 2015 project Phase",
type: "integer",
default: 2,
});
R.Config("pd_limit", {
name: "Chrono Chaser Phase",
type: "integer",
default: 9,
});
R.Config("pd_boss", {
name: "Event Phase (PD)",
desc: "Chrono Seeker / Qpronicle Chord / PENDUAL TALISMAN",
type: "integer",
default: 3,
});
R.Config("pd_chronodiver", {
name: "Chrono Seeker",
type: "integer",
default: 3,
});
R.Config("pd_qproniclechord", {
name: "Qpronicle Chord",
type: "integer",
default: 2,
});
R.Config("pd_cccollabo", {
name: "Coca-Cola×BEMANI",
desc: "Coca-Cola×BEMANI 店舗限定ロケテスト Phase",
type: "integer",
default: 3,
});
R.Config("pd_timephase", {
name: "Time Phase",
type: "integer",
desc: "Default / Present / Future",
default: 0,
});
// copula //
R.Config("cp_boss", {
name: "Event Phase (CP)",
desc: "開通!とことこライン / Mystery Line",
type: "integer",
default: 2,
});
R.Config("cp_event1", {
name: "開通!とことこライン",
desc: "開通!とことこライン Phase",
type: "integer",
default: 1,
});
R.Config("cp_event2", {
name: "Mystery Line",
desc: "Mystery Line Phase",
type: "integer",
default: 2,
});
R.Config("cp_extraboss",
{
name: "Extra Boss Phase (CP)",
desc: "Extra Boss Phase",
type: "integer",
default: 30,
});
R.Config("cp_bemanisummer", {
name: "BEMANI Summer 2016",
desc: "NEW Generation 夏の流星フェスタ2016 Phase",
type: "integer",
default: 2,
});
// SINOBUZ //
R.Config("sb_boss", {
name: "Event Phase (SB)",
desc: "攻城シノバズ伝 / 忍々七鍵伝",
type: "integer",
default: 2,
});
R.Config("sb_event1", {
name: "攻城シノバズ伝",
desc: "攻城シノバズ伝 Phase",
type: "integer",
default: 2,
});
R.Config("sb_event2", {
name: "忍々七鍵伝",
desc: "忍々七鍵伝 Phase",
type: "integer",
default: 1,
});
R.Config("sb_extraboss",
{
name: "BUZRA ARTS",
desc: "BUZRA ARTS Phase",
type: "integer",
default: 35,
});
// CANNON BALLERS //
R.Config("cb_boss", {
name: "Event Phase (SB)",
desc: "激走!キャノンレーサー",
type: "integer",
default: 1,
});
R.Config("cb_event1", {
name: "激走!キャノンレーサー",
desc: "激走!キャノンレーサー Phase",
type: "integer",
default: 3,
});
R.Config("cb_extraboss",
{
name: "IIDX AIR RACE",
desc: "IIDX AIR RACE Phase",
type: "integer",
default: 35,
});
// Rootage //
R.Config("rt_boss", {
name: "Event Phase (RT)",
desc: "蜃気楼の図書館 / DELABITY LABORATORY",
type: "integer",
default: 2,
});
R.Config("rt_event1", {
name: "蜃気楼の図書館",
desc: "蜃気楼の図書館 Phase",
type: "integer",
default: 3,
});
R.Config("rt_event2", {
name: "DELABITY LABORATORY",
desc: "DELABITY LABORATORY Phase",
type: "integer",
default: 2,
});
R.Config("rt_extraboss",
{
name: "ARC SCORE",
desc: "ARC SCORE Phase",
type: "integer",
default: 3,
});
// HEROIC VERSE //
R.Config("hv_boss", {
name: "Event Phase (HV)",
desc: "HEROIC WORKOUT!!",
type: "integer",
default: 1,
});
R.Config("hv_event", {
name: "HEROIC WORKOUT!!",
desc: "HEROIC WORKOUT!! Phase",
type: "integer",
default: 4,
});
R.Config("hv_extraboss",
{
name: "SHADOW REBELLION",
desc: "SHADOW REBELLION Phase",
type: "integer",
default: 1,
});
// BISTROVER //
R.Config("bo_boss", {
name: "Event Phase (BO)",
desc: "召しませBISTROVER",
type: "integer",
default: 1,
});
R.Config("bo_extraboss", {
name: "BISTRO LANDING",
desc: "BISTRO LANDING Phase",
type: "integer",
default: 1,
});
R.Config("bo_event", {
name: "召しませBISTROVER",
desc: "召しませBISTROVER Phase",
type: "integer",
default: 1,
});
// CastHour //
R.Config("ch_event", {
name: "CastHour Space",
desc: "CastHour Space Phase",
type: "integer",
default: 5,
});
R.Config("ch_extraboss", {
name: "Extra Boss Phase (CH)",
type: "integer",
default: 3,
});
// RESIDENT //
R.Config("rs_event", {
name: "RESIDENT PARTY",
desc: "RESIDENT PARTY Phase",
type: "integer",
default: 5,
});
R.Config("rs_extraboss", {
name: "Extra Boss Phase (RS)",
type: "integer",
default: 3,
});
// EPOLIS //
R.Config("ep_event", {
name: "Event Phase (EP)",
desc: "MY POLIS DESIGNER / EPOLIS RESTORATION",
type: "integer",
default: 2,
});
R.Config("ep_event1", {
name: "MY POLIS DESIGNER",
desc: "MY POLIS DESIGNER Phase",
type: "integer",
default: 3,
});
R.Config("ep_event2", {
name: "EPOLIS RESTORATION",
desc: "EPOLIS RESTORATION Phase",
type: "integer",
default: 3,
});
R.Config("ep_extraboss", {
name: "EPOLIS SINGULARITY",
desc: "EPOLIS SINGULARITY Phase",
type: "integer",
default: 3,
});
// Pinky Crush //
R.Config("pc_event", {
name: "Event Phase (PC)",
desc: "ピンキージャンプアップ! / ピンキーアンダーグラウンド",
type: "integer",
default: 2,
});
R.Config("pc_event1", {
name: "ピンキージャンプアップ!",
desc: "ピンキージャンプアップ! Phase",
type: "integer",
default: 3,
});
R.Config("pc_event2", {
name: "ピンキーアンダーグラウンド",
desc: "ピンキーアンダーグラウンド Phase",
type: "integer",
default: 3,
});
R.Config("pc_extraboss", {
name: "Extra Boss Phase (PC)",
type: "integer",
default: 3,
});
// TODO:: Make a list of customize items //
R.WebUIEvent("iidxGetProfile", async (data, send: WebUISend) => {
const pcdata = await DB.FindOne(data.refid, {
collection: "pcdata",
version: Number(data.version),
});
return send.json({
pcdata,
});
});
R.WebUIEvent("iidxGetSetting", async (data, send: WebUISend) => {
const custom = await DB.FindOne(data.refid, {
collection: "custom",
version: Number(data.version),
});
const lm_custom = await DB.FindOne(data.refid, {
collection: "lightning_custom",
version: Number(data.version),
});
return send.json({
custom,
lm_custom,
});
});
R.WebUIEvent("iidxUpdateRival", updateRivalSettings);
R.WebUIEvent("iidxUpdateCustom", updateCustomSettings);
R.WebUIEvent("iidxImportScoreData", importScoreData);
R.WebUIEvent("iidxExportScoreData", exportScoreData);
const MultiRoute = (method: string, handler: EPR | boolean) => {
R.Route(`${method}`, handler);
R.Route(`IIDX21${method}`, handler);
R.Route(`IIDX22${method}`, handler);
R.Route(`IIDX23${method}`, handler);
R.Route(`IIDX24${method}`, handler);
R.Route(`IIDX25${method}`, handler);
R.Route(`IIDX26${method}`, handler);
R.Route(`IIDX27${method}`, handler);
R.Route(`IIDX28${method}`, handler);
R.Route(`IIDX29${method}`, handler);
R.Route(`IIDX30${method}`, handler);
R.Route(`IIDX31${method}`, handler);
R.Route(`IIDX32${method}`, handler);
};
MultiRoute("pc.common", pccommon);
MultiRoute("pc.reg", pcreg);
MultiRoute("pc.get", pcget);
MultiRoute("pc.getname", pcgetname);
MultiRoute("pc.oldget", pcoldget);
MultiRoute("pc.takeover", pctakeover);
MultiRoute("pc.visit", pcvisit);
MultiRoute("pc.save", pcsave);
MultiRoute("pc.shopregister", pcshopregister);
MultiRoute("pc.getLaneGachaTicket", pcgetlanegacha);
MultiRoute("pc.drawLaneGacha", pcdrawlanegacha);
MultiRoute("pc.consumeLaneGachaTicket", true);
MultiRoute("shop.getname", shopgetname);
MultiRoute("shop.savename", shopsavename);
MultiRoute("shop.getconvention", shopgetconvention);
MultiRoute("shop.setconvention", shopsetconvention);
MultiRoute("music.crate", musiccrate);
MultiRoute("music.getrank", musicgetrank);
MultiRoute("music.getralive", musicgetralive);
MultiRoute("music.appoint", musicappoint);
MultiRoute("music.reg", musicreg);
MultiRoute("music.breg", musicbreg);
MultiRoute("music.arenaCPU", musicarenacpu);
MultiRoute("grade.raised", graderaised);
MultiRoute("ranking.entry", rankingentry);
MultiRoute("ranking.oentry", rankingoentry);
MultiRoute("ranking.getranker", rankinggetranker);
MultiRoute("gameSystem.systemInfo", gssysteminfo);
R.Unhandled((req: EamuseInfo, data: any, send: EamuseSend) => {
console.warn(`Unhandled Request : [${GetVersion(req)}], ${req.module}.${req.method}, ${JSON.stringify(data)}`);
return send.success();
});
}

View File

@ -0,0 +1,50 @@
export interface activity {
collection: "activity";
version: number;
date: number;
play_style: number;
music_num: number;
play_time: number;
keyboard_num: number;
scratch_num: number;
clear_update_num: number[];
score_update_num: number[];
}
export interface activity_mybest {
collection: "activity_mybest";
version: number;
play_style: number;
play_side: number;
music_id: number;
note_id: number;
target_graph: number;
target_score: number;
pacemaker: number;
best_clear: number;
best_score: number;
best_misscount: number;
now_clear: number;
now_score: number;
now_misscount: number;
now_pgreat: number;
now_great: number;
now_good: number;
now_bad: number;
now_poor: number;
now_combo: number;
now_fast: number;
now_slow: number;
option: number;
option_2: number;
ghost_gauge_data: string;
gauge_type: number;
result_type: number;
is_special_result: number;
update_date: number;
}

View File

@ -0,0 +1,46 @@
export const IIDX_CPUS = [
[
[6, 4, 5, 0],
[7, 5, 6, 0],
[8, 6, 6, 0],
[9, 6, 7, 0],
[10, 7, 7, 0],
[10, 7, 8, 0],
[11, 8, 8, 0],
[11, 8, 9, 0],
[12, 9, 9, 0],
[12, 9, 10, 0],
[13, 9, 10, 0],
[13, 10, 10, 0],
[14, 10, 11, 0],
[14, 10, 11, 1],
[15, 11, 11, 1],
[15, 11, 12, 1],
[16, 11, 12, 1],
[16, 11, 12, 1],
[17, 12, 12, 1],
[18, 12, 12, 1],
],
[
[6, 3, 5, 0],
[7, 3, 5, 0],
[8, 4, 5, 0],
[8, 4, 5, 0],
[9, 5, 6, 0],
[9, 5, 6, 0],
[10, 6, 6, 0],
[10, 6, 7, 0],
[11, 7, 7, 0],
[11, 7, 8, 0],
[12, 8, 8, 0],
[12, 8, 9, 0],
[13, 9, 9, 0],
[13, 9, 10, 0],
[14, 9, 10, 0],
[15, 10, 10, 0],
[15, 10, 11, 0],
[16, 11, 11, 1],
[17, 11, 12, 1],
[18, 12, 12, 1],
],
];

View File

@ -0,0 +1,8 @@
export interface badge {
collection: "badge";
version: number;
category_name: string;
flg_id: number;
flg: number;
}

View File

@ -0,0 +1,117 @@
export interface custom {
collection: "custom";
version: number;
// skin //
frame: number;
turntable: number;
note_burst: number;
menu_music: number;
lane_cover: number;
category_vox: number;
note_skin: number;
full_combo_splash: number;
disable_musicpreview: boolean;
note_beam: number;
judge_font: number;
pacemaker_cover: number;
vefx_lock: boolean;
effect: number;
bomb_size: number;
disable_hcn_color: boolean;
first_note_preview: number;
skin_customize_flg: number[];
note_size: number; // epolis //
lift_cover: number;
note_beam_size: number;
// appendsettings
rank_folder: boolean;
clear_folder: boolean;
diff_folder: boolean;
alpha_folder: boolean;
rival_folder: boolean;
rival_battle_folder: boolean;
rival_info: boolean;
hide_playcount: boolean;
disable_graph_cutin: boolean;
classic_hispeed: boolean;
rival_played_folder: boolean;
hide_iidxid: boolean;
disable_beginner_option: boolean;
// qpro //
qpro_head: number;
qpro_hair: number;
qpro_face: number;
qpro_hand: number;
qpro_body: number;
qpro_back: number; // epolis //
// qpro_secret (heroic verse) //
qpro_secret_head: string[];
qpro_secret_hair: string[];
qpro_secret_face: string[];
qpro_secret_hand: string[];
qpro_secret_body: string[];
qpro_secret_back: string[]; // epolis //
}
export const default_custom = {
frame: 0,
turntable: 0,
note_burst: 0,
menu_music: 0,
lane_cover: 0,
category_vox: 0,
note_skin: 0,
full_combo_splash: 0,
disable_musicpreview: false,
note_beam: 0,
judge_font: 0,
pacemaker_cover: 0,
vefx_lock: false,
effect: 0,
bomb_size: 0,
disable_hcn_color: false,
first_note_preview: 0,
skin_customize_flg: Array<number>(3).fill(-1),
note_size: 0,
lift_cover: 0,
note_beam_size: 0,
rank_folder: true,
clear_folder: true,
diff_folder: true,
alpha_folder: true,
rival_folder: true,
rival_battle_folder: true,
rival_info: true,
hide_playcount: false,
disable_graph_cutin: false,
classic_hispeed: false,
rival_played_folder: true,
hide_iidxid: false,
disable_beginner_option: false,
qpro_head: 0,
qpro_hair: 0,
qpro_face: 0,
qpro_hand: 0,
qpro_body: 0,
qpro_back: 0,
qpro_secret_head: Array<string>(7).fill("-1"),
qpro_secret_hair: Array<string>(7).fill("-1"),
qpro_secret_face: Array<string>(7).fill("-1"),
qpro_secret_hand: Array<string>(7).fill("-1"),
qpro_secret_body: Array<string>(7).fill("-1"),
qpro_secret_back: Array<string>(7).fill("-1"),
}

View File

@ -0,0 +1,14 @@
export interface blueboss {
level: number;
gauge: number;
item: number;
item_flg: number;
row0: number;
row1: number;
column0: number;
column1: number;
general: number;
first_flg: number;
sector: number;
durability: string;
}

View File

@ -0,0 +1,10 @@
export interface extra_boss {
collection: "extra_boss";
version: number;
phase: number;
extra: number;
extra_b: number;
onemore: number;
onemore_b: number;
}

Some files were not shown because too many files have changed in this diff Show More