From f72f723b1772f06d44d07864b8cbe414ebb27d95 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Tue, 15 Dec 2020 20:03:50 +0100 Subject: [PATCH 01/16] Set 23/24/25 to latest phase --- popn@asphyxia/README.md | 21 +++++----- popn@asphyxia/handler/common.ts | 72 ++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 23 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index 8abe1d8..0ffdcf5 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -1,21 +1,24 @@ # Pop'n Music -Plugin Version: **v1.0.0** +Plugin Version: **v1.1.0** -Supported Versions -------------------- +## Supported Versions - pop'n music 23 Eclale - pop'n music 24 Usagi to Neko to Shōnen no Yume - pop'n music 25 peace -Changelog -========= -1.0.0 ------ +## Changelog +#### 1.1.0 +Update phase data : All versions are on latest phase. + +#### 1.0.0 Initial Release. -How to import data from non-core Asphyxia -========================================= +## TODO +* Allows to select phase +* Add/Fix save of phase datas + +## How to import data from non-core Asphyxia To import data, you have to : * Create your popn profile in Asphyxia-core. You just have to insert your card in the game and follow the process until coming to the select mode select screen. Once here, quit the game. * Create a backup of your savedata.db file (in case something goes wrong). diff --git a/popn@asphyxia/handler/common.ts b/popn@asphyxia/handler/common.ts index 9cd2305..a2ead0a 100644 --- a/popn@asphyxia/handler/common.ts +++ b/popn@asphyxia/handler/common.ts @@ -1,39 +1,83 @@ import { getVersion } from './utils'; -const PHASE23 = [ +interface Phase { + id: number; + p: number; +} + +const PHASE23 : Phase[] = [ { id: 0, p: 16 }, { id: 1, p: 3 }, { id: 2, p: 1 }, { id: 3, p: 2 }, { id: 4, p: 1 }, - { id: 5, p: 1 }, + { id: 5, p: 2 }, { id: 6, p: 1 }, { id: 7, p: 4 }, { id: 8, p: 3 }, { id: 9, p: 4 }, { id: 10, p: 4 }, { id: 11, p: 1 }, + { id: 12, p: 1 }, { id: 13, p: 4 }, ]; -const PHASE24 = [ - { id: 3, p: 1 }, - // { id: 5, p: 1 }, +const PHASE24 : Phase[] = [ + { id: 0, p: 11 }, + { id: 1, p: 2 }, + { id: 2, p: 2 }, + { id: 3, p: 4 }, + { id: 4, p: 1 }, + { id: 5, p: 1 }, { id: 6, p: 1 }, - { id: 16, p: 1 }, - { id: 17, p: 1 }, - { id: 18, p: 1 }, - { id: 19, p: 1 }, - { id: 20, p: 1 }, + { id: 7, p: 1 }, + { id: 8, p: 2 }, + { id: 9, p: 2 }, + { id: 10, p: 15 }, + { id: 11, p: 1 }, + { id: 12, p: 2 }, + { id: 13, p: 1 }, ]; -export const getInfo = (req) => { +const PHASE25 : Phase[] = [ + { id: 0, p: 23 }, + { id: 1, p: 4 }, + { id: 2, p: 2 }, + { id: 3, p: 4 }, + { id: 4, p: 1 }, + { id: 5, p: 1 }, + { id: 6, p: 1 }, + { id: 7, p: 1 }, + { id: 8, p: 2 }, + { id: 9, p: 2 }, + { id: 10, p: 30 }, + { id: 11, p: 1 }, + { id: 12, p: 2 }, + { id: 13, p: 1 }, + { id: 14, p: 39 }, + { id: 15, p: 2 }, + { id: 16, p: 3 }, + { id: 17, p: 8 }, + { id: 18, p: 1 }, + { id: 19, p: 1 }, + { id: 20, p: 13 }, + { id: 21, p: 20 }, + { id: 22, p: 2 }, + { id: 23, p: 1 }, + { id: 24, p: 1 }, +]; + +export const getInfo = (req: EamuseInfo) => { const version = getVersion(req); if (version == 'v23') { return getInfo23(); } else if (version == 'v24') { - return getInfo24(); + if(req.model == 'M39:J:A:A:2020092800') { + return getInfo24(PHASE25); + } else { + return getInfo24(PHASE24); + } } } @@ -71,13 +115,13 @@ const getInfo23 = () => { return result; }; -const getInfo24 = () => { +const getInfo24 = (phaseData : Phase[]) => { const result: any = { phase: [], goods: [], }; - for (const phase of PHASE24) { + for (const phase of phaseData) { result.phase.push({ event_id: K.ITEM('s16', phase.id), phase: K.ITEM('s16', phase.p), From 7007c6781d3394604c923a3d564623bbdf4ed13f Mon Sep 17 00:00:00 2001 From: cracrayol Date: Tue, 15 Dec 2020 22:24:55 +0100 Subject: [PATCH 02/16] Update Readme --- popn@asphyxia/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index 0ffdcf5..f0e605d 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -14,10 +14,6 @@ Update phase data : All versions are on latest phase. #### 1.0.0 Initial Release. -## TODO -* Allows to select phase -* Add/Fix save of phase datas - ## How to import data from non-core Asphyxia To import data, you have to : * Create your popn profile in Asphyxia-core. You just have to insert your card in the game and follow the process until coming to the select mode select screen. Once here, quit the game. From 5a4db13a88f082ac1fbdd466e402051ece24d010 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Sat, 19 Dec 2020 02:22:17 +0100 Subject: [PATCH 03/16] Add change name, enable/disable popn 25 event Disable NET Taisen --- popn@asphyxia/README.md | 5 +++++ popn@asphyxia/handler/common.ts | 10 ++++++---- popn@asphyxia/index.ts | 10 ++++++++++ popn@asphyxia/webui/profile_page.pug | 29 +++++++++++++++++++++++++--- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index f0e605d..eb34870 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -8,6 +8,11 @@ Plugin Version: **v1.1.0** - pop'n music 25 peace ## Changelog +#### 1.2.0 +* You can change your profile name +* You can enable/disable the pop'n 25 event archive event +* Net Taisen disabled on 24/25 (code not implemented) + #### 1.1.0 Update phase data : All versions are on latest phase. diff --git a/popn@asphyxia/handler/common.ts b/popn@asphyxia/handler/common.ts index a2ead0a..61068a5 100644 --- a/popn@asphyxia/handler/common.ts +++ b/popn@asphyxia/handler/common.ts @@ -28,7 +28,7 @@ const PHASE24 : Phase[] = [ { id: 2, p: 2 }, { id: 3, p: 4 }, { id: 4, p: 1 }, - { id: 5, p: 1 }, +// { id: 5, p: 1 }, // Disable Net Taisen { id: 6, p: 1 }, { id: 7, p: 1 }, { id: 8, p: 2 }, @@ -45,7 +45,7 @@ const PHASE25 : Phase[] = [ { id: 2, p: 2 }, { id: 3, p: 4 }, { id: 4, p: 1 }, - { id: 5, p: 1 }, +// { id: 5, p: 1 }, // Disable Net Taisen { id: 6, p: 1 }, { id: 7, p: 1 }, { id: 8, p: 2 }, @@ -61,7 +61,7 @@ const PHASE25 : Phase[] = [ { id: 18, p: 1 }, { id: 19, p: 1 }, { id: 20, p: 13 }, - { id: 21, p: 20 }, +// { id: 21, p: 20 }, // pop'n event archive { id: 22, p: 2 }, { id: 23, p: 1 }, { id: 24, p: 1 }, @@ -74,7 +74,9 @@ export const getInfo = (req: EamuseInfo) => { return getInfo23(); } else if (version == 'v24') { if(req.model == 'M39:J:A:A:2020092800') { - return getInfo24(PHASE25); + let phase = [...PHASE25]; + phase.push({ id: 21, p: U.GetConfig("enable_25_event") ? 20 : 0 }) + return getInfo24(phase); } else { return getInfo24(PHASE24); } diff --git a/popn@asphyxia/index.ts b/popn@asphyxia/index.ts index c5eb5bb..2b405a3 100644 --- a/popn@asphyxia/index.ts +++ b/popn@asphyxia/index.ts @@ -4,8 +4,18 @@ import { importPnmData } from "./handler/webui"; export function register() { R.GameCode('M39'); + + R.Config("enable_25_event", { + name: "PNM25 event", + desc: "Enable the pop'n event archive", + type: "boolean", + default: true, + }); R.WebUIEvent('importPnmData', importPnmData); + R.WebUIEvent('updatePnmPlayerInfo', async (data: any) => { + await DB.Update(data.refid, { collection: 'profile' }, { $set: { name: data.name } }); + }); const PlayerRoute = (method: string, handler: EPR | boolean) => { R.Route(`player24.${method}`, handler); diff --git a/popn@asphyxia/webui/profile_page.pug b/popn@asphyxia/webui/profile_page.pug index 49043ff..4190081 100644 --- a/popn@asphyxia/webui/profile_page.pug +++ b/popn@asphyxia/webui/profile_page.pug @@ -1,7 +1,28 @@ +//DATA// + profile: DB.FindOne(refid, { collection: 'profile' }) + div div.notification.is-success.is-hidden#import-success .field - label.label.is-small Data imported + label.label.is-small Data imported + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-account-edit + | User Detail + .card-content + form(method="post" action="/emit/updatePnmPlayerInfo") + input(type="hidden" id="refid" name="refid" value=refid) + .field + label.label Name + .control + input.input(type="text" name="name" maxlength="6", value=profile.name) + .field + button.button.is-primary(type="submit") + span.icon + i.mdi.mdi-check + span Submit .card .card-header p.card-header-title @@ -9,7 +30,6 @@ div i.mdi.mdi-account-edit | Import data .card-content - input(type="hidden" id="refid" name="refid" value=refid) .field label.label Import data from previous Asphyxia (non-core) .field @@ -19,6 +39,9 @@ div .field label.help.is-danger /!\ Please backup your savedata.db before importing data /!\ .field - button.button.is-primary#import-click Import + button.button.is-primary#import-click + span.icon + i.mdi.mdi-file-import-outline + span Import script(src="static/js/profile_page.js") \ No newline at end of file From a31def74ad7b63ca966cb0f31ad6685a83736c04 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Sat, 19 Dec 2020 02:22:56 +0100 Subject: [PATCH 04/16] Update readme --- popn@asphyxia/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index eb34870..35ca291 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -1,6 +1,6 @@ # Pop'n Music -Plugin Version: **v1.1.0** +Plugin Version: **v1.2.0** ## Supported Versions - pop'n music 23 Eclale From 8144150de92b0d5dbab56a4ff2ade8e9b215d13b Mon Sep 17 00:00:00 2001 From: dannylin0711 Date: Sat, 6 Mar 2021 00:02:37 +0800 Subject: [PATCH 05/16] Update for Blaster Pass Time is broken. --- sdvx@asphyxia/handlers/common.ts | 2 ++ sdvx@asphyxia/handlers/profiles.ts | 3 ++- sdvx@asphyxia/templates/load.pug | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/sdvx@asphyxia/handlers/common.ts b/sdvx@asphyxia/handlers/common.ts index 2bfa4a9..da45335 100644 --- a/sdvx@asphyxia/handlers/common.ts +++ b/sdvx@asphyxia/handlers/common.ts @@ -26,11 +26,13 @@ export const common: EPR = async (info, data, send) => { if (U.GetConfig('unlock_all_songs')) { for (let i = 1; i < 1700; ++i) { for (let j = 0; j < 5; ++j) { + songs.push({ music_id: K.ITEM('s32', i), music_type: K.ITEM('u8', j), limited: K.ITEM('u8', 3), }); + } } } diff --git a/sdvx@asphyxia/handlers/profiles.ts b/sdvx@asphyxia/handlers/profiles.ts index 0951e73..2330693 100644 --- a/sdvx@asphyxia/handlers/profiles.ts +++ b/sdvx@asphyxia/handlers/profiles.ts @@ -253,7 +253,7 @@ export const load: EPR = async (info, data, send) => { const courses = await DB.Find(refid, { collection: 'course' }); const items = await DB.Find(refid, { collection: 'item' }); const params = await DB.Find(refid, { collection: 'param' }); - + const currentTime = Date.now(); const mixes = version == 5 ? await getAutomationMixes(params) : []; send.pugFile('templates/load.pug', { @@ -263,6 +263,7 @@ export const load: EPR = async (info, data, send) => { : items, params, skill, + currentTime, mixes, automation: version == 5 ? SDVX_AUTOMATION_SONGS : [], code: IDToCode(profile.id), diff --git a/sdvx@asphyxia/templates/load.pug b/sdvx@asphyxia/templates/load.pug index d6e9ea8..a3dd726 100644 --- a/sdvx@asphyxia/templates/load.pug +++ b/sdvx@asphyxia/templates/load.pug @@ -35,6 +35,8 @@ game packet_booster(__type="s32") 1 if version != 5 block_booster(__type="s32") 1 + blaster_pass_enable(__type="bool") 1 + blaster_pass_limit_date(__type="u64") #{currentTime} eaappli relation(__type="s8") 1 From be159cef7703b80cd8238adf4fd49fcabddd7645 Mon Sep 17 00:00:00 2001 From: dannylin0711 Date: Fri, 12 Mar 2021 20:22:42 +0800 Subject: [PATCH 06/16] Fix Blaster Pass time, Fix VW appeal cards not appearing --- sdvx@asphyxia/data/vvw.ts | 1 + sdvx@asphyxia/handlers/profiles.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sdvx@asphyxia/data/vvw.ts b/sdvx@asphyxia/data/vvw.ts index 5b0762b..2a104a6 100644 --- a/sdvx@asphyxia/data/vvw.ts +++ b/sdvx@asphyxia/data/vvw.ts @@ -13,6 +13,7 @@ export const EVENT5 = [ 'FACTORY\t10', 'CONTINUATION', 'APPEAL_CARD_GEN_NEW_PRICE', + 'APPEAL_CARD_UNLOCK\t0,30170914,0,30171014,0,30171116,0,30180201,0,30180607,0,30181206,0,30200326,0,30200611', 'FAVORITE_APPEALCARD_MAX\t100', 'FAVORITE_MUSIC_MAX\t500', 'EVENTDATE_APRILFOOL', diff --git a/sdvx@asphyxia/handlers/profiles.ts b/sdvx@asphyxia/handlers/profiles.ts index 2330693..217fbb2 100644 --- a/sdvx@asphyxia/handlers/profiles.ts +++ b/sdvx@asphyxia/handlers/profiles.ts @@ -253,7 +253,14 @@ export const load: EPR = async (info, data, send) => { const courses = await DB.Find(refid, { collection: 'course' }); const items = await DB.Find(refid, { collection: 'item' }); const params = await DB.Find(refid, { collection: 'param' }); - const currentTime = Date.now(); + let time = new Date(); + let tempHour = time.getHours(); + let tempDate = time.getDate(); + tempHour += 12; + tempDate += 1; + time.setDate(tempDate); + time.setHours(tempHour); + const currentTime = time.getTime(); const mixes = version == 5 ? await getAutomationMixes(params) : []; send.pugFile('templates/load.pug', { From ad677b4c2b0684ded99385eff1e71fdeea3b2632 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Fri, 9 Apr 2021 23:08:37 +0200 Subject: [PATCH 07/16] 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 --- popn@asphyxia/README.md | 28 +- popn@asphyxia/handler/common.ts | 324 ------------ popn@asphyxia/handler/eclale.ts | 492 ++++++++++++++++++ popn@asphyxia/handler/fantasia.ts | 276 ++++++++++ popn@asphyxia/handler/lapistoria.ts | 441 ++++++++++++++++ popn@asphyxia/handler/player.ts | 519 ------------------- popn@asphyxia/handler/sunny.ts | 321 ++++++++++++ popn@asphyxia/handler/tunestreet.ts | 273 ++++++++++ popn@asphyxia/handler/usaneko.ts | 719 +++++++++++++++++++++++++++ popn@asphyxia/handler/utils.ts | 222 ++++++++- popn@asphyxia/handler/webui.ts | 3 +- popn@asphyxia/index.ts | 64 ++- popn@asphyxia/models/achievements.ts | 93 ++++ popn@asphyxia/models/common.ts | 42 ++ popn@asphyxia/models/profile.ts | 40 -- popn@asphyxia/models/scores.ts | 13 - 16 files changed, 2937 insertions(+), 933 deletions(-) delete mode 100644 popn@asphyxia/handler/common.ts create mode 100644 popn@asphyxia/handler/eclale.ts create mode 100644 popn@asphyxia/handler/fantasia.ts create mode 100644 popn@asphyxia/handler/lapistoria.ts delete mode 100644 popn@asphyxia/handler/player.ts create mode 100644 popn@asphyxia/handler/sunny.ts create mode 100644 popn@asphyxia/handler/tunestreet.ts create mode 100644 popn@asphyxia/handler/usaneko.ts create mode 100644 popn@asphyxia/models/achievements.ts create mode 100644 popn@asphyxia/models/common.ts delete mode 100644 popn@asphyxia/models/profile.ts delete mode 100644 popn@asphyxia/models/scores.ts diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index 35ca291..735fd01 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -1,13 +1,25 @@ # Pop'n Music -Plugin Version: **v1.2.0** +Plugin Version: **v2.0.0** ## Supported Versions -- pop'n music 23 Eclale -- pop'n music 24 Usagi to Neko to Shōnen no Yume -- pop'n music 25 peace +- pop'n music 19 Tune Street +- pop'n music 20 fantasia +- pop'n music Sunny Park +- pop'n music Lapistoria +- pop'n music éclale +- pop'n music Usagi to Neko to Shōnen no Yume +- pop'n music peace ## Changelog + +### 2.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 + #### 1.2.0 * You can change your profile name * You can enable/disable the pop'n 25 event archive event @@ -25,4 +37,10 @@ To import data, you have to : * Create a backup of your savedata.db file (in case something goes wrong). * In the web UI of Asphyxia, go to POPN -> Profile and click detail on your profile * Put the content of your non-core asphyxia popn music files in the text fields (pop.json and popn_scores.json) and click Import. -* Data is imported. Run the game, insert your card and your scores are available. \ No newline at end of file +* Data is imported. Run the game, insert your card and your scores are available. + +## Known limitations +* Tune Street : It will not report your profile name in-game +* Tune Street : No Town Mode +* No rival support implemented +* Some stats are not implemented (like daily stats, most played music) \ No newline at end of file diff --git a/popn@asphyxia/handler/common.ts b/popn@asphyxia/handler/common.ts deleted file mode 100644 index 61068a5..0000000 --- a/popn@asphyxia/handler/common.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { getVersion } from './utils'; - -interface Phase { - id: number; - p: number; -} - -const PHASE23 : Phase[] = [ - { id: 0, p: 16 }, - { id: 1, p: 3 }, - { id: 2, p: 1 }, - { id: 3, p: 2 }, - { id: 4, p: 1 }, - { id: 5, p: 2 }, - { id: 6, p: 1 }, - { id: 7, p: 4 }, - { id: 8, p: 3 }, - { id: 9, p: 4 }, - { id: 10, p: 4 }, - { id: 11, p: 1 }, - { id: 12, p: 1 }, - { id: 13, p: 4 }, -]; - -const PHASE24 : Phase[] = [ - { id: 0, p: 11 }, - { id: 1, p: 2 }, - { id: 2, p: 2 }, - { id: 3, p: 4 }, - { id: 4, p: 1 }, -// { id: 5, p: 1 }, // Disable Net Taisen - { id: 6, p: 1 }, - { id: 7, p: 1 }, - { id: 8, p: 2 }, - { id: 9, p: 2 }, - { id: 10, p: 15 }, - { id: 11, p: 1 }, - { id: 12, p: 2 }, - { id: 13, p: 1 }, -]; - -const PHASE25 : Phase[] = [ - { id: 0, p: 23 }, - { id: 1, p: 4 }, - { id: 2, p: 2 }, - { id: 3, p: 4 }, - { id: 4, p: 1 }, -// { id: 5, p: 1 }, // Disable Net Taisen - { id: 6, p: 1 }, - { id: 7, p: 1 }, - { id: 8, p: 2 }, - { id: 9, p: 2 }, - { id: 10, p: 30 }, - { id: 11, p: 1 }, - { id: 12, p: 2 }, - { id: 13, p: 1 }, - { id: 14, p: 39 }, - { id: 15, p: 2 }, - { id: 16, p: 3 }, - { id: 17, p: 8 }, - { id: 18, p: 1 }, - { id: 19, p: 1 }, - { id: 20, p: 13 }, -// { id: 21, p: 20 }, // pop'n event archive - { id: 22, p: 2 }, - { id: 23, p: 1 }, - { id: 24, p: 1 }, -]; - -export const getInfo = (req: EamuseInfo) => { - const version = getVersion(req); - - if (version == 'v23') { - return getInfo23(); - } else if (version == 'v24') { - if(req.model == 'M39:J:A:A:2020092800') { - let phase = [...PHASE25]; - phase.push({ id: 21, p: U.GetConfig("enable_25_event") ? 20 : 0 }) - return getInfo24(phase); - } else { - return getInfo24(PHASE24); - } - } -} - -const getInfo23 = () => { - const result: any = { - phase: [], - area: [], - goods: [], - }; - - for (const phase of PHASE23) { - result.phase.push({ - event_id: K.ITEM('s16', phase.id), - phase: K.ITEM('s16', phase.p), - }); - } - - for (let i = 1; i <= 100; ++i) { - result.area.push({ - area_id: K.ITEM('s16', i), - end_date: K.ITEM('u64', BigInt(0)), - medal_id: K.ITEM('s16', i), - is_limit: K.ITEM('bool', 0), - }); - } - - for (let i = 1; i <= 420; ++i) { - result.goods.push({ - goods_id: K.ITEM('s16', i), - price: K.ITEM('s32', i <= 80 ? 60 : 100), - goods_type: K.ITEM('s16', 0), - }); - } - - return result; -}; - -const getInfo24 = (phaseData : Phase[]) => { - const result: any = { - phase: [], - goods: [], - }; - - for (const phase of phaseData) { - result.phase.push({ - event_id: K.ITEM('s16', phase.id), - phase: K.ITEM('s16', phase.p), - }); - } - - for (let i = 3; i <= 96; ++i) { - result.goods.push({ - item_id: K.ITEM('s32', i), - item_type: K.ITEM('s16', 3), - price: K.ITEM('s32', 60), - goods_type: K.ITEM('s16', 0), - }); - } - - return result; -}; - -export const M39_EXTRA_DATA: { - [ver: string]: { - [field: string]: { - path: string; - type: string; - default: any; - isArray?: true; - }; - }; -} = { - v23: { - tutorial: { type: 's8', path: 'account', default: 0 }, - area_id: { type: 's16', path: 'account', default: 0 }, - lumina: { type: 's16', path: 'account', default: 300 }, - medal_set: { type: 's16', path: 'account', default: [0, 0, 0, 0], isArray: true }, - read_news: { type: 's16', path: 'account', default: 0 }, - staff: { type: 's8', path: 'account', default: 0 }, - item_type: { type: 's16', path: 'account', default: 0 }, - item_id: { type: 's16', path: 'account', default: 0 }, - is_conv: { type: 's8', path: 'account', default: 0 }, - active_fr_num: { type: 'u8', path: 'account', default: 0 }, - nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, - favorite_chara: { type: 's16', path: 'account', default: Array(20).fill(-1), isArray: true }, - special_area: { type: 's16', path: 'account', default: Array(8).fill(0), isArray: true }, - chocolate_charalist: { - type: 's16', - path: 'account', - default: Array(5).fill(-1), - isArray: true, - }, - teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(0), isArray: true }, - license_data: { type: 's16', path: 'account', default: [-1, -1], isArray: true }, - welcom_pack: { type: 'bool', path: 'account', default: 1 }, - meteor_flg: { type: 'bool', path: 'account', default: 1 }, - - hispeed: { type: 's16', path: 'option', default: 0 }, - popkun: { type: 'u8', path: 'option', default: 0 }, - hidden: { type: 'bool', path: 'option', default: 0 }, - hidden_rate: { type: 's16', path: 'option', default: 0 }, - sudden: { type: 'bool', path: 'option', default: 0 }, - sudden_rate: { type: 's16', path: 'option', default: 0 }, - randmir: { type: 's8', path: 'option', default: 0 }, - gauge_type: { type: 's8', path: 'option', default: 0 }, - ojama_0: { type: 'u8', path: 'option', default: 0 }, - ojama_1: { type: 'u8', path: 'option', default: 0 }, - forever_0: { type: 'bool', path: 'option', default: 0 }, - forever_1: { type: 'bool', path: 'option', default: 0 }, - full_setting: { type: 'bool', path: 'option', default: 0 }, - judge: { type: 'u8', path: 'option', default: 0 }, - - ep: { type: 'u16', path: 'info', default: 0 }, - - effect_left: { type: 'u16', path: 'customize', default: 0 }, - effect_center: { type: 'u16', path: 'customize', default: 0 }, - effect_right: { type: 'u16', path: 'customize', default: 0 }, - hukidashi: { type: 'u16', path: 'customize', default: 0 }, - comment_1: { type: 'u16', path: 'customize', default: 0 }, - comment_2: { type: 'u16', path: 'customize', default: 0 }, - - mode: { type: 'u8', path: 'config', default: 0 }, - chara: { type: 's16', path: 'config', default: -1 }, - music: { type: 's16', path: 'config', default: -1 }, - sheet: { type: 'u8', path: 'config', default: 0 }, - category: { type: 's8', path: 'config', default: -1 }, - sub_category: { type: 's8', path: 'config', default: -1 }, - chara_category: { type: 's8', path: 'config', default: -1 }, - course_id: { type: 's16', path: 'config', default: 0 }, - course_folder: { type: 's8', path: 'config', default: 0 }, - ms_banner_disp: { type: 's8', path: 'config', default: 0 }, - ms_down_info: { type: 's8', path: 'config', default: 0 }, - ms_side_info: { type: 's8', path: 'config', default: 0 }, - ms_raise_type: { type: 's8', path: 'config', default: 0 }, - ms_rnd_type: { type: 's8', path: 'config', default: 0 }, - - enemy_medal: { type: 's16', path: 'event', default: 0 }, - hp: { type: 's16', path: 'event', default: 0 }, - - valid: { type: 's8', path: 'custom_cate', default: 0 }, - lv_min: { type: 's8', path: 'custom_cate', default: -1 }, - lv_max: { type: 's8', path: 'custom_cate', default: -1 }, - medal_min: { type: 's8', path: 'custom_cate', default: -1 }, - medal_max: { type: 's8', path: 'custom_cate', default: -1 }, - friend_no: { type: 's8', path: 'custom_cate', default: -1 }, - score_flg: { type: 's8', path: 'custom_cate', default: -1 }, - },v24: { - enemy_medal: { type: 's16', path: 'event', default: 0 }, - hp: { type: 's16', path: 'event', default: 0 }, - - tutorial: { type: 's16', path: 'account', default: -1 }, - area_id: { type: 's16', path: 'account', default: 51 }, - lumina: { type: 's16', path: 'account', default: 0 }, - medal_set: { type: 's16', path: 'account', default: [0, 0], isArray: true }, - read_news: { type: 's16', path: 'account', default: 0 }, - staff: { type: 's8', path: 'account', default: 0 }, - is_conv: { type: 's8', path: 'account', default: 0 }, - item_type: { type: 's16', path: 'account', default: 0 }, - item_id: { type: 's16', path: 'account', default: 0 }, - license_data: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, - active_fr_num: { type: 'u8', path: 'account', default: 0 }, - nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, - favorite_chara: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, - special_area: { type: 's16', path: 'account', default: Array(8).fill(-1), isArray: true }, - chocolate_charalist: { - type: 's16', - path: 'account', - default: Array(5).fill(-1), - isArray: true, - }, - chocolate_sp_chara: { type: 's32', path: 'account', default: 0 }, - chocolate_pass_cnt: { type: 's32', path: 'account', default: 0 }, - chocolate_hon_cnt: { type: 's32', path: 'account', default: 0 }, - chocolate_giri_cnt: { type: 's32', path: 'account', default: 0 }, - chocolate_kokyu_cnt: { type: 's32', path: 'account', default: 0 }, - teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, - welcom_pack: { type: 'bool', path: 'account', default: 0 }, - meteor_flg: { type: 'bool', path: 'account', default: 0 }, - use_navi: { type: 's16', path: 'account', default: 0 }, - ranking_node: { type: 's32', path: 'account', default: 0 }, - chara_ranking_kind_id: { type: 's32', path: 'account', default: 0 }, - navi_evolution_flg: { type: 's8', path: 'account', default: 0 }, - ranking_news_last_no: { type: 's32', path: 'account', default: 0 }, - power_point: { type: 's32', path: 'account', default: 0 }, - player_point: { type: 's32', path: 'account', default: 0 }, - power_point_list: { type: 's32', path: 'account', default: [0], isArray: true }, - - mode: { type: 'u8', path: 'config', default: 0 }, - chara: { type: 's16', path: 'config', default: 0 }, - music: { type: 's16', path: 'config', default: 0 }, - sheet: { type: 'u8', path: 'config', default: 0 }, - category: { type: 's8', path: 'config', default: 0 }, - sub_category: { type: 's8', path: 'config', default: 0 }, - chara_category: { type: 's8', path: 'config', default: 0 }, // check - story_id: { type: 's16', path: 'config', default: 0 }, - ms_banner_disp: { type: 's8', path: 'config', default: 0 }, - ms_down_info: { type: 's8', path: 'config', default: 0 }, - ms_side_info: { type: 's8', path: 'config', default: 0 }, - ms_raise_type: { type: 's8', path: 'config', default: 0 }, - ms_rnd_type: { type: 's8', path: 'config', default: 0 }, - banner_sort: { type: 's8', path: 'config', default: 0 }, - course_id: { type: 's16', path: 'config', default: 0 }, - course_folder: { type: 's8', path: 'config', default: 0 }, - story_folder: { type: 's8', path: 'config', default: 0 }, - - hispeed: { type: 's16', path: 'option', default: 10 }, - popkun: { type: 'u8', path: 'option', default: 0 }, - hidden: { type: 'bool', path: 'option', default: 0 }, - hidden_rate: { type: 's16', path: 'option', default: -1 }, - sudden: { type: 'bool', path: 'option', default: 0 }, - sudden_rate: { type: 's16', path: 'option', default: -1 }, - randmir: { type: 's8', path: 'option', default: 0 }, - gauge_type: { type: 's8', path: 'option', default: 0 }, - ojama_0: { type: 'u8', path: 'option', default: 0 }, - ojama_1: { type: 'u8', path: 'option', default: 0 }, - forever_0: { type: 'bool', path: 'option', default: 0 }, - forever_1: { type: 'bool', path: 'option', default: 0 }, - full_setting: { type: 'bool', path: 'option', default: 0 }, - guide_se: { type: 's8', path: 'option', default: 0 }, - judge: { type: 'u8', path: 'option', default: 0 }, - slow: { type: 's16', path: 'option', default: 0 }, - fast: { type: 's16', path: 'option', default: 0 }, - - valid: { type: 's8', path: 'custom_cate', default: 0 }, - lv_min: { type: 's8', path: 'custom_cate', default: 0 }, - lv_max: { type: 's8', path: 'custom_cate', default: 0 }, - medal_min: { type: 's8', path: 'custom_cate', default: 0 }, - medal_max: { type: 's8', path: 'custom_cate', default: 0 }, - friend_no: { type: 's8', path: 'custom_cate', default: 0 }, - score_flg: { type: 's8', path: 'custom_cate', default: 0 }, - - ep: { type: 'u16', path: 'info', default: 0 }, - ap: { type: 'u16', path: 'info', default: 0 }, - - effect_left: { type: 'u16', path: 'customize', default: 0 }, - effect_center: { type: 'u16', path: 'customize', default: 0 }, - effect_right: { type: 'u16', path: 'customize', default: 0 }, - hukidashi: { type: 'u16', path: 'customize', default: 0 }, - comment_1: { type: 'u16', path: 'customize', default: 0 }, - comment_2: { type: 'u16', path: 'customize', default: 0 }, - }, -}; \ No newline at end of file diff --git a/popn@asphyxia/handler/eclale.ts b/popn@asphyxia/handler/eclale.ts new file mode 100644 index 0000000..deb9ab9 --- /dev/null +++ b/popn@asphyxia/handler/eclale.ts @@ -0,0 +1,492 @@ +import { AchievementsEclale } from "../models/achievements"; +import { ExtraData, Params, Phase } from "../models/common"; +import * as utils from "./utils"; + +export const setRoutes = () => { + R.Route(`info23.common`, getInfo); + R.Route(`player23.new`, newPlayer); + R.Route(`player23.read`, read); + R.Route(`player23.start`, start); + R.Route(`player23.buy`, buy); + R.Route(`player23.read_score`, readScore); + R.Route(`player23.write_music`, writeScore); + R.Route(`player23.write`, write); +} + +const getInfoCommon = (req: EamuseInfo) => { + const result: any = { + phase: [], + area: [], + goods: [], + }; + + // Phase + for (const elt of phase) { + result.phase.push({ + event_id: K.ITEM('s16', elt.id), + phase: K.ITEM('s16', elt.p), + }); + } + + // Area + for (let i = 1; i <= 100; ++i) { + result.area.push({ + area_id: K.ITEM('s16', i), + end_date: K.ITEM('u64', BigInt(0)), + medal_id: K.ITEM('s16', i), + is_limit: K.ITEM('bool', 0), + }); + } + + // Goods + for (let i = 1; i <= 420; ++i) { + let price = 150; + + if (i <= 80) { + price = 60; + } else if (i <= 120) { + price = 250; + } else if (i <= 142) { + price = 500; + } else if (i <= 300) { + price = 100; + } + + result.goods.push({ + goods_id: K.ITEM('s16', i), + price: K.ITEM('s32', price), + goods_type: K.ITEM('s16', 0), + }); + } + + // TODO : Course ranking + // TODO : Most popular characters + // TODO : Most popular music + + return result; +} + +const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + return send.object(getInfoCommon(req)); +} + +const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result = { + play_id: K.ITEM('s32', 1), + ...getInfoCommon(req), + }; + await send.object(result); +}; + +/** + * Create a new profile and send it. + */ +const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const name = $(data).str('name'); + + send.object(await getProfile(refid, name)); +}; + +/** + * Read a profile and send it. + */ +const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + send.object(await getProfile(refid)); +}; + +const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const type = $(data).number('type', -1); + const id = $(data).number('id', -1); + const param = $(data).number('param', 0); + const price = $(data).number('price', 0); + const lumina = $(data).number('lumina', 0); + + if (type < 0 || id < 0) { + return send.deny(); + } + + if (lumina >= price) { + const params = await utils.readParams(refid, version); + params.params.player_point = lumina - price; + await utils.writeParams(refid, version, params); + + const achievements = await utils.readAchievements(refid, version, defaultAchievements); + achievements.items[`${type}:${id}`] = param; + await utils.writeAchievements(refid, version, achievements); + } + send.success(); +}; + +const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const scoresData = await utils.readScores(refid, version); + const result: any = { + music: [], + }; + + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + result.music.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type]), + cnt: K.ITEM('s16', score.cnt), + }); + } + + send.object(result); +}; + +const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const music = $(data).number('music_num'); + const sheet = $(data).number('sheet_num'); + const clear_type = { + 1: 100, + 2: 200, + 3: 300, + 4: 400, + 5: 500, + 6: 600, + 7: 700, + 8: 800, + 9: 900, + 10: 1000, + 11: 1100, + }[$(data).number('clearmedal')]; + const score = $(data).number('score'); + + const key = `${music}:${sheet}`; + + const scoresData = await utils.readScores(refid, version, true); + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + + utils.writeScores(refid, version, scoresData); + + send.success(); +}; + +/** + * Get/create the profile based on refid + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + * @returns + */ +const getProfile = async (refid: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + let player: any = { + result: K.ITEM('s8', 0), + account: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + staff: K.ITEM('s8', 0), + item_type: K.ITEM('s16', 0), + item_id: K.ITEM('s16', 0), + is_conv: K.ITEM('s8', 0), + meteor_flg: K.ITEM('bool', true), + license_data: K.ARRAY('s16', Array(20).fill(-1)), + + // TODO: replace with real data + total_play_cnt: K.ITEM('s16', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + total_days: K.ITEM('s16', 366), + interval_day: K.ITEM('s16', 1), + my_best: K.ARRAY('s16', Array(10).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + active_fr_num: K.ITEM('u8', 0), + }, + netvs: { + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + dialog: [ + K.ITEM('str', 'dialog#0'), + K.ITEM('str', 'dialog#1'), + K.ITEM('str', 'dialog#2'), + K.ITEM('str', 'dialog#3'), + K.ITEM('str', 'dialog#4'), + K.ITEM('str', 'dialog#5'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + netvs_play_cnt: K.ITEM('u32', 0), + }, + custom_cate: { + valid: K.ITEM('s8', 0), + lv_min: K.ITEM('s8', -1), + lv_max: K.ITEM('s8', -1), + medal_min: K.ITEM('s8', -1), + medal_max: K.ITEM('s8', -1), + friend_no: K.ITEM('s8', -1), + score_flg: K.ITEM('s8', -1), + }, + + item: [], + chara_param: [], + medal: [], + }; + + const achievements = await utils.readAchievements(refid, version, defaultAchievements); + + const profileCharas = achievements.charas || {}; + for (const chara_id in profileCharas) { + player.chara_param.push({ + chara_id: K.ITEM('u16', parseInt(chara_id, 10)), + friendship: K.ITEM('u16', profileCharas[chara_id]), + }); + } + + const profileMedals = achievements.medals || {}; + for (const medal_id in profileMedals) { + const medal = profileMedals[medal_id]; + player.medal.push({ + medal_id: K.ITEM('s16', parseInt(medal_id, 10)), + level: K.ITEM('s16', medal.level), + exp: K.ITEM('s32', medal.exp), + set_count: K.ITEM('s32', medal.set_count), + get_count: K.ITEM('s32', medal.get_count), + }); + } + + const profileItems = achievements.items || {}; + for (const key in profileItems) { + const keyData = key.split(':'); + const type = parseInt(keyData[0], 10); + const id = parseInt(keyData[1], 10); + + const item: any = { + type: K.ITEM('u8', type), + id: K.ITEM('u16', id), + param: K.ITEM('u16', profileItems[key]), + is_new: K.ITEM('bool', 0), + }; + + player.item.push(item); + } + + // Add version specific datas + const params = await utils.readParams(refid, version); + utils.addExtraData(player, params, extraData); + + return player; +} + +const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const params = await utils.readParams(refid, version); + const achievements = await utils.readAchievements(refid, version, defaultAchievements); + + utils.getExtraData(data, params, extraData); + + // medals + let medals = _.get(data, 'medal', []); + if (!achievements.medals) { + achievements.medals = {}; + } + + if (!_.isArray(medals)) { + medals = [medals]; + } + + for (const medal of medals) { + const id = $(medal).number('medal_id'); + const level = $(medal).number('level'); + const exp = $(medal).number('exp'); + const set_count = $(medal).number('set_count'); + const get_count = $(medal).number('get_count'); + + achievements.medals[id] = { + level, + exp, + set_count, + get_count, + }; + } + + // items + let items = _.get(data, 'item', []); + if (!achievements.items) { + achievements.items = {}; + } + + if (!_.isArray(items)) { + items = [items]; + } + + for (const item of items) { + const type = $(item).number('type'); + const id = $(item).number('id'); + const param = $(item).number('param'); + + const key = `${type}:${id}`; + + achievements.items[key] = param; + } + + // charas + let charas = _.get(data, 'chara_param', []); + if (!achievements.charas) { + achievements.charas = {}; + } + + if (!_.isArray(charas)) { + charas = [charas]; + } + + for (const chara of charas) { + const id = $(chara).number('chara_id'); + const param = $(chara).number('friendship'); + + achievements.charas[id] = param; + } + + await utils.writeParams(refid, version, params); + await utils.writeAchievements(refid, version, achievements); + + send.success(); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const version: string = 'v23'; +const GAME_MAX_MUSIC_ID = 1550; + +const defaultAchievements: AchievementsEclale = { + collection: 'achievements', + version: 'v23', + medals: {}, + items: {}, + charas: {}, +} + +const phase: Phase[] = [ + { id: 0, p: 16 }, + { id: 1, p: 3 }, + { id: 2, p: 1 }, + { id: 3, p: 2 }, + { id: 4, p: 1 }, + { id: 5, p: 2 }, + { id: 6, p: 1 }, + { id: 7, p: 4 }, + { id: 8, p: 3 }, + { id: 9, p: 4 }, + { id: 10, p: 4 }, + { id: 11, p: 1 }, + { id: 12, p: 1 }, + { id: 13, p: 4 }, +]; +const extraData: ExtraData = { + tutorial: { type: 's8', path: 'account', default: 0 }, + area_id: { type: 's16', path: 'account', default: 0 }, + lumina: { type: 's16', path: 'account', default: 300 }, + read_news: { type: 's16', path: 'account', default: 0 }, + welcom_pack: { type: 'bool', path: 'account', default: 1 }, + medal_set: { type: 's16', path: 'account', default: Array(4).fill(0), isArray: true }, + nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, + favorite_chara: { type: 's16', path: 'account', default: Array(20).fill(-1), isArray: true }, + special_area: { type: 's16', path: 'account', default: Array(8).fill(0), isArray: true }, + chocolate_charalist: { type: 's16', path: 'account', default: Array(5).fill(-1), isArray: true }, + teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(0), isArray: true }, + + hispeed: { type: 's16', path: 'option', default: 0 }, + popkun: { type: 'u8', path: 'option', default: 0 }, + hidden: { type: 'bool', path: 'option', default: 0 }, + hidden_rate: { type: 's16', path: 'option', default: 0 }, + sudden: { type: 'bool', path: 'option', default: 0 }, + sudden_rate: { type: 's16', path: 'option', default: 0 }, + randmir: { type: 's8', path: 'option', default: 0 }, + gauge_type: { type: 's8', path: 'option', default: 0 }, + ojama_0: { type: 'u8', path: 'option', default: 0 }, + ojama_1: { type: 'u8', path: 'option', default: 0 }, + forever_0: { type: 'bool', path: 'option', default: 0 }, + forever_1: { type: 'bool', path: 'option', default: 0 }, + full_setting: { type: 'bool', path: 'option', default: 0 }, + judge: { type: 'u8', path: 'option', default: 0 }, + + ep: { type: 'u16', path: 'info', default: 0 }, + + effect_left: { type: 'u16', path: 'customize', default: 0 }, + effect_center: { type: 'u16', path: 'customize', default: 0 }, + effect_right: { type: 'u16', path: 'customize', default: 0 }, + hukidashi: { type: 'u16', path: 'customize', default: 0 }, + comment_1: { type: 'u16', path: 'customize', default: 0 }, + comment_2: { type: 'u16', path: 'customize', default: 0 }, + + mode: { type: 'u8', path: 'config', default: 0 }, + chara: { type: 's16', path: 'config', default: -1 }, + music: { type: 's16', path: 'config', default: -1 }, + sheet: { type: 'u8', path: 'config', default: 0 }, + category: { type: 's8', path: 'config', default: -1 }, + sub_category: { type: 's8', path: 'config', default: -1 }, + chara_category: { type: 's8', path: 'config', default: -1 }, + course_id: { type: 's16', path: 'config', default: 0 }, + course_folder: { type: 's8', path: 'config', default: 0 }, + ms_banner_disp: { type: 's8', path: 'config', default: 0 }, + ms_down_info: { type: 's8', path: 'config', default: 0 }, + ms_side_info: { type: 's8', path: 'config', default: 0 }, + ms_raise_type: { type: 's8', path: 'config', default: 0 }, + ms_rnd_type: { type: 's8', path: 'config', default: 0 }, + + enemy_medal: { type: 's16', path: 'event', default: 0 }, + hp: { type: 's16', path: 'event', default: 0 }, + + stamp_id: { type: 's16', path: 'stamp', default: 0 }, + cnt: { type: 's16', path: 'stamp', default: 0 }, +} \ No newline at end of file diff --git a/popn@asphyxia/handler/fantasia.ts b/popn@asphyxia/handler/fantasia.ts new file mode 100644 index 0000000..454252e --- /dev/null +++ b/popn@asphyxia/handler/fantasia.ts @@ -0,0 +1,276 @@ +import { ExtraData } from "../models/common"; +import * as utils from "./utils"; + +/** + * Return the current phases of the game. + */ +export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result = { + game_phase: K.ITEM('s32', 2), + ir_phase: K.ITEM('s32', 0), + event_phase: K.ITEM('s32', 5), + netvs_phase: K.ITEM('s32', 0), + card_phase: K.ITEM('s32', 6), + illust_phase: K.ITEM('s32', 2), + psp_phase: K.ITEM('s32', 5), + other_phase: K.ITEM('s32', 1), + jubeat_phase: K.ITEM('s32', 1), + public_phase: K.ITEM('s32', 3), + kac_phase: K.ITEM('s32', 2), + local_matching: K.ITEM('s32', 1), + n_matching_sec: K.ITEM('s32', 60), + l_matching_sec: K.ITEM('s32', 60), + is_check_cpu: K.ITEM('s32', 0), + week_no: K.ITEM('s32', 0), + ng_illust: K.ARRAY('s32', Array(10).fill(0)), + sel_ranking: K.ARRAY('s16', Array(10).fill(-1)), + up_ranking: K.ARRAY('s16', Array(10).fill(-1)), + }; + + return send.object(result); +}; + +/** + * Create a new profile and send it. + */ +export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const name = $(data).str('name'); + + send.object(await getProfile(refid, name)); +}; + +/** + * Read a profile and send it. + */ +export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + send.object(await getProfile(refid)); +}; + +/** + * Get/create the profile based on refid and format it + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + */ +export const getProfile = async (refid: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + // Get Score + let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); + let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); + + const scoresData = await utils.readScores(refid, version); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + const medal = { + 100: 1, + 200: 2, + 300: 3, + 400: 5, + 500: 5, + 600: 6, + 700: 7, + 800: 9, + 900: 10, + 1000: 11, + 1100: 15, + }[score.clear_type]; + clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); + + const hiscore_index = (music * 4) + sheet; + const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); + const hiscore_bit_pos = ((hiscore_index * 17) % 8); + const hiscore_value = score.score << hiscore_bit_pos; + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + } + + let player: any = { + base: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', '1234-5678'), + staff: K.ITEM('s8', 0), + is_conv: K.ITEM('s8', -1), + + // TODO: replace with real data + total_play_cnt: K.ITEM('s32', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + my_best: K.ARRAY('s16', Array(20).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1]), + active_fr_num: K.ITEM('u8', 0), + clear_medal: K.ARRAY('u16', clear_medal), + clear_medal_sub: K.ARRAY('u8', clear_medal_sub), + }, + player_card: { + // TODO: replace with real data + best_music: K.ARRAY('s16', [-1, -1, -1]), + }, + netvs: { + get_ojama: K.ARRAY('s32', [0, 0]), + rank_point: K.ITEM('s32', 0), + play_point: K.ITEM('s32', 0), + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + rank: K.ITEM('u8', 0), + dialog: [ + K.ITEM('str', 'dialog#0'), + K.ITEM('str', 'dialog#1'), + K.ITEM('str', 'dialog#2'), + K.ITEM('str', 'dialog#3'), + K.ITEM('str', 'dialog#4'), + K.ITEM('str', 'dialog#5'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + jewelry: K.ARRAY('s8', Array(15).fill(0)), + }, + hiscore: K.ITEM('bin', Buffer.from(hiscore_array)) + }; + + // Add version specific datas + const params = await utils.readParams(refid, version); + utils.addExtraData(player, params, extraData); + + return player; +} + +/** + * Unformat and write the end game data into DB + */ +export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + if (!refid) return send.deny(); + + const params = await utils.readParams(refid, version); + utils.getExtraData(data, params, extraData); + await utils.writeParams(refid, version, params); + + const scoresData = await utils.readScores(refid, version, true); + + for (const scoreData of $(data).elements('stage')) { + const music = scoreData.number('no', -1); + const sheet = { + 2: 0, + 0: 1, + 1: 2, + 3: 3, + }[scoreData.number('sheet')]; + const clear_type = { + 1: 100, + 2: 200, + 3: 300, + 5: 500, + 6: 600, + 7: 700, + 9: 800, + 10: 900, + 11: 1000, + 15: 1100, + }[(scoreData.number('n_data') >> (sheet * 4)) & 0x000F]; + const score = scoreData.number('score', 0); + + const key = `${music}:${sheet}`; + + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + } + + utils.writeScores(refid, version, scoresData); + + const profile = await utils.readProfile(refid); + + const result = { + pref: K.ITEM('s8', -1), + name: K.ITEM('str', profile.name) + } + + send.object(result); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const version: string = 'v20'; +const GAME_MAX_MUSIC_ID = 1150; + +const extraData: ExtraData = { + mode: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + button: { type: 's8', path: 'base', pathSrc: '', default: 0 }, + last_play_flag: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + medal_and_friend: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + sub_category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + chara: { type: 's16', path: 'base', pathSrc: '', default: -1 }, + chara_category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + collabo: { type: 'u8', path: 'base', pathSrc: '', default: 255 }, + sheet: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + tutorial: { type: 's8', path: 'base', pathSrc: '', default: 0 }, + music_open_pt: { type: 's32', path: 'base', pathSrc: '', default: 0 }, + option: { type: 's32', path: 'base', pathSrc: '', default: 0 }, + music: { type: 's16', path: 'base', pathSrc: '', default: -1 }, + ep: { type: 'u16', path: 'base', pathSrc: '', default: 0 }, + sp_color_flg: { type: 's32', path: 'base', pathSrc: '', default: [0, 0], isArray: true }, + read_news: { type: 's32', path: 'base', pathSrc: '', default: 0 }, + consecutive_days_coupon: { type: 's16', path: 'base', pathSrc: '', default: 0 }, + + title: { type: 'u8', path: 'player_card', pathSrc: '', default: [0, 1], isArray: true }, + frame: { type: 'u8', path: 'player_card', pathSrc: '', default: 0 }, + base: { type: 'u8', path: 'player_card', pathSrc: '', default: 0 }, + seal: { type: 'u8', path: 'player_card', pathSrc: '', default: [0, 0], isArray: true }, + get_title: { type: 's32', path: 'player_card', pathSrc: '', default: [0, 0, 0, 0], isArray: true }, + get_frame: { type: 's32', path: 'player_card', pathSrc: '', default: 0 }, + get_base: { type: 's32', path: 'player_card', pathSrc: '', default: 0 }, + get_seal: { type: 's32', path: 'player_card', pathSrc: '', default: [0, 0], isArray: true }, + + get_title_ex: { type: 's32', path: 'player_card_ex', default: 0 }, + get_frame_ex: { type: 's32', path: 'player_card_ex', default: 0 }, + get_base_ex: { type: 's32', path: 'player_card_ex', default: 0 }, + get_seal_ex: { type: 's32', path: 'player_card_ex', default: 0 }, + + sp: { type: 's32', path: 'sp_data', default: 0 }, + + reflec: { type: 's8', path: 'reflec_data', default: [0, 0], isArray: true }, + + genre: { type: 's8', path: 'navigate', default: 0 }, + image: { type: 's8', path: 'navigate', default: 0 }, + level: { type: 's8', path: 'navigate', default: 0 }, + ojama: { type: 's8', path: 'navigate', default: 0 }, + limit_num: { type: 's16', path: 'navigate', default: 0 }, + button__1: { type: 's8', path: 'navigate', default: 0 }, + life: { type: 's8', path: 'navigate', default: 0 }, + progress: { type: 's16', path: 'navigate', default: 0 }, +} \ No newline at end of file diff --git a/popn@asphyxia/handler/lapistoria.ts b/popn@asphyxia/handler/lapistoria.ts new file mode 100644 index 0000000..925e9f2 --- /dev/null +++ b/popn@asphyxia/handler/lapistoria.ts @@ -0,0 +1,441 @@ +import { AchievementsLapistoria } from "../models/achievements"; +import { ExtraData, Phase, Profile } from "../models/common"; +import * as utils from "./utils"; + +export const setRoutes = () => { + R.Route(`info22.common`, getInfo); + R.Route(`player22.new`, newPlayer); + R.Route(`player22.read`, read); + R.Route(`player22.write_music`, writeScore); + R.Route(`player22.write`, write); +} + +/** + * Return info22.common informations (phase, etc...) + */ +const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result: any = { + phase: [], + story: [], + }; + + for (const elt of phase) { + result.phase.push({ + event_id: K.ITEM('s16', elt.id), + phase: K.ITEM('s16', elt.p), + }); + } + + for (let i = 0; i < 173; ++i) { + result.story.push({ + story_id: K.ITEM('u32', i), + is_limited: K.ITEM('bool', false), + limit_date: K.ITEM('u64', BigInt(0)), + }); + } + + return send.object(result); +}; + +/** + * Create a new profile and send it. + */ +const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const name = $(data).str('name'); + + send.object(await getProfile(refid, name)); +}; + +/** + * Read a profile and send it. + */ +const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + send.object(await getProfile(refid)); +}; + +const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const music = $(data).number('music_num'); + const sheet = $(data).number('sheet_num'); + const clear_type = { + 1: 100, + 2: 200, + 3: 300, + 4: 400, + 5: 500, + 6: 600, + 7: 700, + 8: 800, + 9: 900, + 10: 1000, + 11: 1100, + }[$(data).number('clearmedal')]; + const score = $(data).number('score'); + + const key = `${music}:${sheet}`; + + const scoresData = await utils.readScores(refid, version, true); + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + + utils.writeScores(refid, version, scoresData); + + send.success(); +}; + +/** + * Get/create the profile based on refid + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + * @returns + */ +const getProfile = async (refid: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + let player: any = { + result: K.ITEM('s8', 0), + account: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + staff: K.ITEM('s8', 0), + is_conv: K.ITEM('s8', 0), + item_type: K.ITEM('s16', 0), + item_id: K.ITEM('s16', 0), + license_data: K.ARRAY('s16', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]), + + // TODO: replace with real data + total_play_cnt: K.ITEM('s16', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + total_days: K.ITEM('s16', 366), + interval_day: K.ITEM('s16', 1), + my_best: K.ARRAY('s16', Array(10).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + active_fr_num: K.ITEM('u8', 0), + }, + netvs: { + rank_point: K.ITEM('s32', 0), + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + rank: K.ITEM('u8', 0), + vs_rank_old: K.ITEM('u8', 0), + dialog: [ + K.ITEM('str', 'dialog#0'), + K.ITEM('str', 'dialog#1'), + K.ITEM('str', 'dialog#2'), + K.ITEM('str', 'dialog#3'), + K.ITEM('str', 'dialog#4'), + K.ITEM('str', 'dialog#5'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + netvs_play_cnt: K.ITEM('u32', 0), + }, + custom_cate: { + valid: K.ITEM('s8', 0), + lv_min: K.ITEM('s8', -1), + lv_max: K.ITEM('s8', -1), + medal_min: K.ITEM('s8', -1), + medal_max: K.ITEM('s8', -1), + friend_no: K.ITEM('s8', -1), + score_flg: K.ITEM('s8', -1), + }, + + music: [], + item: [], + achievement: [], + chara_param: [], + story: [], + }; + + // Add Score + const scoresData = await utils.readScores(refid, version); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + player.music.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + cnt: K.ITEM('s16', score.cnt), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type]), + old_score: K.ITEM('s32', 0), + old_clear_type: K.ITEM('u8', 0), + }); + } + + // Add achievements + const achievements = await utils.readAchievements(refid, version, defaultAchievements); + + const profileAchievements = achievements.achievements || { '0': 0 }; + for (const achievement in profileAchievements) { + player.achievement.push({ + type: K.ITEM('u8', parseInt(achievement, 10)), + count: K.ITEM('u32', profileAchievements[achievement]), + }); + } + + const profileCharas = achievements.charas || {}; + for (const chara_id in profileCharas) { + player.chara_param.push({ + chara_id: K.ITEM('u16', parseInt(chara_id, 10)), + friendship: K.ITEM('u16', profileCharas[chara_id]), + }); + } + + const profileStories = achievements.stories || {}; + for (const story_id in profileStories) { + const story = profileStories[story_id]; + player.story.push({ + story_id: K.ITEM('u32', parseInt(story_id, 10)), + chapter_id: K.ITEM('u32', story.chapter_id), + gauge_point: K.ITEM('u16', story.gauge_point), + is_cleared: K.ITEM('bool', story.is_cleared), + clear_chapter: K.ITEM('u32', story.clear_chapter), + }); + } + + const profileItems = achievements.items || {}; + for (const key in profileItems) { + const keyData = key.split(':'); + const type = parseInt(keyData[0], 10); + const id = parseInt(keyData[1], 10); + + const item: any = { + type: K.ITEM('u8', type), + id: K.ITEM('u16', id), + param: K.ITEM('u16', profileItems[key]), + is_new: K.ITEM('bool', 0), + }; + + player.item.push(item); + } + + // Add version specific datas + const params = await utils.readParams(refid, version); + utils.addExtraData(player, params, extraData); + + return player; +} + +const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const params = await utils.readParams(refid, version); + const achievements = await utils.readAchievements(refid, version, defaultAchievements); + + utils.getExtraData(data, params, extraData); + + // achievements + let achievement = _.get(data, 'achievement', []); + if (!achievements.achievements) { + achievements.achievements = { '0': 0 }; + } + + if (!_.isArray(achievement)) { + achievement = [achievement]; + } + + for (const elt of achievement) { + const id = $(elt).number('type'); + const cnt = $(elt).number('count'); + + achievements.achievements[id] = cnt; + } + + // medals + let stories = _.get(data, 'story', []); + if (!achievements.stories) { + achievements.stories = {}; + } + + if (!_.isArray(stories)) { + stories = [stories]; + } + + for (const story of stories) { + const id = $(story).number('story_id'); + const chapter_id = $(story).number('chapter_id'); + const gauge_point = $(story).number('gauge_point'); + const is_cleared = $(story).bool('is_cleared'); + const clear_chapter = $(story).number('clear_chapter'); + + achievements.stories[id] = { + chapter_id, + gauge_point, + is_cleared, + clear_chapter, + }; + } + + // items + let items = _.get(data, 'item', []); + if (!achievements.items) { + achievements.items = {}; + } + + if (!_.isArray(items)) { + items = [items]; + } + + for (const item of items) { + const type = $(item).number('type'); + const id = $(item).number('id'); + const param = $(item).number('param'); + + const key = `${type}:${id}`; + + achievements.items[key] = param; + } + + // charas + let charas = _.get(data, 'chara_param', []); + if (!achievements.charas) { + achievements.charas = {}; + } + + if (!_.isArray(charas)) { + charas = [charas]; + } + + for (const chara of charas) { + const id = $(chara).number('chara_id'); + const param = $(chara).number('friendship'); + + achievements.charas[id] = param; + } + + await utils.writeParams(refid, version, params); + await utils.writeAchievements(refid, version, achievements); + + send.success(); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const version: string = 'v22'; +const GAME_MAX_MUSIC_ID = 1422; + +const defaultAchievements: AchievementsLapistoria = { + collection: 'achievements', + version: 'v22', + achievements: {}, + stories: {}, + items: {}, + charas: {}, +} + +const phase: Phase[] = [ + { id: 0, p: 16 }, + { id: 1, p: 11 }, + { id: 2, p: 11 }, + { id: 3, p: 24 }, + { id: 4, p: 2 }, + { id: 5, p: 2 }, + { id: 6, p: 1 }, + { id: 7, p: 1 }, + { id: 8, p: 2 }, + { id: 9, p: 11 }, + { id: 10, p: 2 }, + { id: 11, p: 3 }, + { id: 12, p: 1 }, + { id: 13, p: 2 }, + { id: 14, p: 4 }, + { id: 15, p: 2 }, + { id: 16, p: 2 }, + { id: 17, p: 12 }, + { id: 18, p: 2 }, + { id: 19, p: 7 }, +]; + +const extraData: ExtraData = { + tutorial: { type: 's8', path: 'account', default: 0 }, + read_news: { type: 's16', path: 'account', default: 0 }, + + hispeed: { type: 's16', path: 'option', default: 0 }, + popkun: { type: 'u8', path: 'option', default: 0 }, + hidden: { type: 'bool', path: 'option', default: 0 }, + hidden_rate: { type: 's16', path: 'option', default: 0 }, + sudden: { type: 'bool', path: 'option', default: 0 }, + sudden_rate: { type: 's16', path: 'option', default: 0 }, + randmir: { type: 's8', path: 'option', default: 0 }, + gauge_type: { type: 's8', path: 'option', default: 0 }, + ojama_0: { type: 'u8', path: 'option', default: 0 }, + ojama_1: { type: 'u8', path: 'option', default: 0 }, + forever_0: { type: 'bool', path: 'option', default: 0 }, + forever_1: { type: 'bool', path: 'option', default: 0 }, + full_setting: { type: 'bool', path: 'option', default: 0 }, + + ep: { type: 'u16', path: 'info', default: 0 }, + ap: { type: 'u16', path: 'info', default: 0 }, + + effect: { type: 'u16', path: 'customize', default: 0 }, + hukidashi: { type: 'u16', path: 'customize', default: 0 }, + font: { type: 'u16', path: 'customize', default: 0 }, + comment_1: { type: 'u16', path: 'customize', default: 0 }, + comment_2: { type: 'u16', path: 'customize', default: 0 }, + + mode: { type: 'u8', path: 'config', default: 0 }, + chara: { type: 's16', path: 'config', default: -1 }, + music: { type: 's16', path: 'config', default: -1 }, + sheet: { type: 'u8', path: 'config', default: 0 }, + category: { type: 's8', path: 'config', default: 1 }, + sub_category: { type: 's8', path: 'config', default: -1 }, + chara_category: { type: 's8', path: 'config', default: -1 }, + story_id: { type: 's16', path: 'config', default: -1 }, + course_id: { type: 's16', path: 'config', default: -1 }, + course_folder: { type: 's8', path: 'config', default: -1 }, + story_folder: { type: 's8', path: 'config', default: -1 }, + ms_banner_disp: { type: 's8', path: 'config', default: 0 }, + ms_down_info: { type: 's8', path: 'config', default: 0 }, + ms_side_info: { type: 's8', path: 'config', default: 0 }, + ms_raise_type: { type: 's8', path: 'config', default: 0 }, + ms_rnd_type: { type: 's8', path: 'config', default: 0 }, +} \ No newline at end of file diff --git a/popn@asphyxia/handler/player.ts b/popn@asphyxia/handler/player.ts deleted file mode 100644 index a9949fb..0000000 --- a/popn@asphyxia/handler/player.ts +++ /dev/null @@ -1,519 +0,0 @@ -import { Profile } from '../models/profile'; -import { Scores } from '../models/scores'; -import { getInfo, M39_EXTRA_DATA } from './common'; -import { getVersion } from './utils'; - -const getPlayer = async (refid: string, version: string, name?: string) => { - const profile = await readProfile(refid); - - if (name && name.length > 0) { - profile.name = name; - await writeProfile(refid, profile); - } - - let player: any = { - result: K.ITEM('s8', 0), - account: { - name: K.ITEM('str', profile.name), - g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), - - // Fixed stats - total_play_cnt: K.ITEM('s16', 100), - today_play_cnt: K.ITEM('s16', 50), - consecutive_days: K.ITEM('s16', 365), - total_days: K.ITEM('s16', 366), - interval_day: K.ITEM('s16', 1), - - // TODO: do these - my_best: K.ARRAY('s16', Array(10).fill(-1)), - latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), - }, - - netvs: { - record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), - dialog: [ - K.ITEM('str', 'dialog#0'), - K.ITEM('str', 'dialog#1'), - K.ITEM('str', 'dialog#2'), - K.ITEM('str', 'dialog#3'), - K.ITEM('str', 'dialog#4'), - K.ITEM('str', 'dialog#5'), - ], - ojama_condition: K.ARRAY('s8', Array(74).fill(0)), - set_ojama: K.ARRAY('s8', [0, 0, 0]), - set_recommend: K.ARRAY('s8', [0, 0, 0]), - netvs_play_cnt: K.ITEM('u32', 0), - }, - - eaappli: { - relation: K.ITEM('s8', 0), - }, - - stamp: [], - item: [], - chara_param: [], - medal: [], - }; - - const profileStamps = profile.stamps[version] || { '0': 0 }; - - for (const stamp in profileStamps) { - player.stamp.push({ - stamp_id: K.ITEM('s16', parseInt(stamp, 10)), - cnt: K.ITEM('s16', profileStamps[stamp]), - }); - } - - const profileCharas = profile.charas[version] || {}; - - for (const chara_id in profileCharas) { - player.chara_param.push({ - chara_id: K.ITEM('u16', parseInt(chara_id, 10)), - friendship: K.ITEM('u16', profileCharas[chara_id]), - }); - } - - const profileMedals = profile.medals[version] || {}; - - for (const medal_id in profileMedals) { - const medal = profileMedals[medal_id]; - player.medal.push({ - medal_id: K.ITEM('s16', parseInt(medal_id, 10)), - level: K.ITEM('s16', medal.level), - exp: K.ITEM('s32', medal.exp), - set_count: K.ITEM('s32', medal.set_count), - get_count: K.ITEM('s32', medal.get_count), - }); - } - - const profileItems = profile.items[version] || {}; - - for (const key in profileItems) { - const keyData = key.split(':'); - const type = parseInt(keyData[0], 10); - const id = parseInt(keyData[1], 10); - - const item: any = { - type: K.ITEM('u8', type), - id: K.ITEM('u16', id), - param: K.ITEM('u16', profileItems[key]), - is_new: K.ITEM('bool', 0), - }; - - if (version != 'v23') { - item.get_time = K.ITEM('u64', BigInt(0)); - } - - player.item.push(item); - } - - // EXTRA DATA - if (M39_EXTRA_DATA[version]) { - for (const field in M39_EXTRA_DATA[version]) { - const fieldMetaData = M39_EXTRA_DATA[version][field]; - if (fieldMetaData.isArray) { - _.set( - player, - `${fieldMetaData.path}.${field}`, - K.ARRAY( - fieldMetaData.type as any, - _.get(profile, `extras.${version}.${field}`, fieldMetaData.default) - ) - ); - } else { - _.set( - player, - `${fieldMetaData.path}.${field}`, - K.ITEM( - fieldMetaData.type as any, - _.get(profile, `extras.${version}.${field}`, fieldMetaData.default) - ) - ); - } - } - } - - // Extra Fixed Data - if (version == 'v24') { - player = { - ...player, - navi_data: { - raisePoint: K.ARRAY('s32', [-1, -1, -1, -1, -1]), - navi_param: { - navi_id: K.ITEM('u16', 0), - friendship: K.ITEM('s32', 0), - }, - }, - - area: { - area_id: K.ITEM('u32', 0), - chapter_index: K.ITEM('u8', 0), - gauge_point: K.ITEM('u16', 0), - is_cleared: K.ITEM('bool', 0), - diary: K.ITEM('u32', 0), - }, - - mission: [ - { - mission_id: K.ITEM('u32', 170), - gauge_point: K.ITEM('u32', 0), - mission_comp: K.ITEM('u32', 0), - }, - { - mission_id: K.ITEM('u32', 157), - gauge_point: K.ITEM('u32', 0), - mission_comp: K.ITEM('u32', 0), - }, - { - mission_id: K.ITEM('u32', 47), - gauge_point: K.ITEM('u32', 0), - mission_comp: K.ITEM('u32', 0), - }, - ], - }; - } - - return player; -}; - -export const newPlayer: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const version = getVersion(req); - const name = $(data).str('name'); - - send.object(await getPlayer(refid, version, name)); -}; - -export const read: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const version = getVersion(req); - - send.object(await getPlayer(refid, version)); -}; - -export const readScore: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const scoresData = await readScores(refid); - const version = getVersion(req); - const result: any = { - music: [], - }; - - if(version == 'v24') { - for (const key in scoresData.scores) { - const keyData = key.split(':'); - const score = scoresData.scores[key]; - const music = parseInt(keyData[0], 10); - const sheet = parseInt(keyData[1], 10); - result.music.push({ - music_num: K.ITEM('s16', music), - sheet_num: K.ITEM('u8', sheet), - score: K.ITEM('s32', score.score), - clear_type: K.ITEM('u8', score.clear_type || 0), - clear_rank: K.ITEM('u8', score.clear_rank || 0), - cnt: K.ITEM('s16', score.cnt), - }); - } - } else if(version == 'v23') { - for (const key in scoresData.scores) { - const keyData = key.split(':'); - const score = scoresData.scores[key]; - const music = parseInt(keyData[0], 10); - const sheet = parseInt(keyData[1], 10); - result.music.push({ - music_num: K.ITEM('s16', music), - sheet_num: K.ITEM('u8', sheet), - score: K.ITEM('s32', score.score), - clear_type: K.ITEM('u8', score.clearmedal || 0), - cnt: K.ITEM('s16', score.cnt), - old_score: K.ITEM('s32', 0), - old_clear_type: K.ITEM('u8', 0), - }); - } - } - - send.object(result); -}; - -export const writeMusic: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const music = $(data).number('music_num', -1); - const sheet = $(data).number('sheet_num', -1); - const clear_type = $(data).number('clear_type'); - const clear_rank = $(data).number('clear_rank'); - const clearmedal = $(data).number('clearmedal'); - const score = $(data).number('score', 0); - - if (music < 0 || sheet < 0) { - return send.deny(); - } - - const key = `${music}:${sheet}`; - - const scoresData = await readScores(refid); - if (!scoresData.scores[key]) { - scoresData.scores[key] = { - score, - cnt: 1, - }; - } else { - scoresData.scores[key] = { - score: Math.max(score, scoresData.scores[key].score), - cnt: scoresData.scores[key].cnt + 1, - }; - } - - if (clear_type) { - scoresData.scores[key].clear_type = Math.max(clear_type, scoresData.scores[key].clear_type || 0); - } - - if (clear_rank) { - scoresData.scores[key].clear_rank = Math.max(clear_rank, scoresData.scores[key].clear_rank || 0); - } - - if (clearmedal) { - scoresData.scores[key].clearmedal = Math.max(clearmedal, scoresData.scores[key].clearmedal || 0); - } - - writeScores(refid, scoresData); - - const version = getVersion(req); - if (version == 'v24') { - - const p = await readProfile(refid); - - const settings = [ - 'hispeed', - 'popkun', - 'hidden', - 'hidden_rate', - 'sudden', - 'sudden_rate', - 'randmir', - 'ojama_0', - 'ojama_1', - 'forever_0', - 'forever_1', - 'full_setting', - 'guide_se', - 'judge', - 'slow', - 'fast', - 'mode', - ]; - - for (const setting of settings) { - _.set( - p, - `extras.v24.${setting}`, - _.get(data, `${setting}.@content.0`, _.get(p, `extras.v24.${setting}`, 0)) - ); - } - - _.set(p, `extras.v24.tutorial`, 32767); - - const chara = $(data).number('chara_num'); - if (chara) { - _.set(p, 'extras.v24.chara', chara); - } - - const music = $(data).number('music_num'); - if (music) { - _.set(p, 'extras.v24.music', music); - } - - const sheet = $(data).number('sheet_num'); - if (sheet) { - _.set(p, 'extras.v24.sheet', sheet); - } - - writeProfile(refid, p); - } - - send.success(); -}; - -export const write: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const version = getVersion(req); - const profile = await readProfile(refid); - - const writeData: Partial = {}; - - if (M39_EXTRA_DATA[version]) { - const extraFields = M39_EXTRA_DATA[version]; - for (const field in extraFields) { - const fieldMetaData = extraFields[field]; - let value = _.get(data, `${fieldMetaData.path}.${field}.@content`); - if ( value == 'undefined' && value == null ) { - continue; - } - - if (_.isArray(value) && value.length == 1) { - value = value[0]; - } - - _.set(writeData, `extras.${version}.${field}`, value); - } - } - - const newProfile:Profile = { - ...profile, - ...writeData, - }; - - // stamps - let stamps = _.get(data, 'stamp', []); - if (!newProfile.stamps[version]) { - newProfile.stamps[version] = { '0': 0 }; - } - - if (!_.isArray(stamps)) { - stamps = [stamps]; - } - - for (const stamp of stamps) { - const id = $(stamp).number('stamp_id'); - const cnt = $(stamp).number('cnt'); - - newProfile.stamps[version][id] = cnt; - } - - // medals - let medals = _.get(data, 'medal', []); - if (!newProfile.medals[version]) { - newProfile.medals[version] = {}; - } - - if (!_.isArray(medals)) { - medals = [medals]; - } - - for (const medal of medals) { - const id = $(medal).number('medal_id'); - const level = $(medal).number('level'); - const exp = $(medal).number('exp'); - const set_count = $(medal).number('set_count'); - const get_count = $(medal).number('get_count'); - - newProfile.medals[version][id] = { - level, - exp, - set_count, - get_count, - }; - } - - // items - let items = _.get(data, 'item', []); - if (!newProfile.items[version]) { - newProfile.items[version] = {}; - } - - if (!_.isArray(items)) { - items = [items]; - } - - for (const item of items) { - const type = $(item).number('type'); - const id = $(item).number('id'); - const param = $(item).number('param'); - - const key = `${type}:${id}`; - - newProfile.items[version][key] = param; - } - - // charas - let charas = _.get(data, 'chara_param', []); - if (!newProfile.charas[version]) { - newProfile.charas[version] = {}; - } - - if (!_.isArray(charas)) { - charas = [charas]; - } - - for (const chara of charas) { - const id = $(chara).number('chara_id'); - const param = $(chara).number('friendship'); - - newProfile.charas[version][id] = param; - } - - await writeProfile(refid, newProfile); - send.success(); -}; - -export const start: EPR = async (req, data, send) => { - const result = { - play_id: K.ITEM('s32', 1), - ...getInfo(req), - }; - await send.object(result); -}; - -export const buy: EPR = async (req, data, send) => { - const refid = $(data).str('ref_id'); - if (!refid) return send.deny(); - - const type = $(data).number('type', -1); - const id = $(data).number('id', -1); - const param = $(data).number('param', 0); - const version = getVersion(req); - - if (type < 0 || id < 0) { - return send.deny(); - } - - const key = `${type}:${id}`; - - const profile = await readProfile(refid); - if (!profile.items[version]) { - profile.items[version] = {}; - } - profile.items[version][key] = param; - await writeProfile(refid, profile); - send.success(); -}; - -const defaultProfile:Profile = { - collection: 'profile', - - name: 'ゲスト', - - stamps: {}, - medals: {}, - items: {}, - charas: {}, - - extras: {}, -}; - -async function readProfile(refid: string): Promise { - const profile = await DB.FindOne(refid, { collection: 'profile'} ) - return profile || defaultProfile -} - -async function writeProfile(refid: string, profile: Profile) { - await DB.Upsert(refid, { collection: 'profile'}, profile) -} - -async function readScores(refid: string): Promise { - const score = await DB.FindOne(refid, { collection: 'scores'} ) - return score || { collection: 'scores', scores: {}} -} - -async function writeScores(refid: string, scores: Scores) { - await DB.Upsert(refid, { collection: 'scores'}, scores) -} \ No newline at end of file diff --git a/popn@asphyxia/handler/sunny.ts b/popn@asphyxia/handler/sunny.ts new file mode 100644 index 0000000..903290a --- /dev/null +++ b/popn@asphyxia/handler/sunny.ts @@ -0,0 +1,321 @@ +import { ExtraData } from "../models/common"; +import * as utils from "./utils"; + +/** + * Return the current phases of the game. + */ +export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result = { + ir_phase: K.ITEM('s32', 0), + music_open_phase: K.ITEM('s32', 8), + collabo_phase: K.ITEM('s32', 8), + personal_event_phase: K.ITEM('s32', 10), + shop_event_phase: K.ITEM('s32', 6), + netvs_phase: K.ITEM('s32', 0), + card_phase: K.ITEM('s32', 9), + other_phase: K.ITEM('s32', 9), + local_matching_enable: K.ITEM('s32', 1), + n_matching_sec: K.ITEM('s32', 60), + l_matching_sec: K.ITEM('s32', 60), + is_check_cpu: K.ITEM('s32', 0), + week_no: K.ITEM('s32', 0), + sel_ranking: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + up_ranking: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + }; + + return send.object(result); +}; + +/** + * Create a new profile and send it. + */ +export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const name = $(data).str('name'); + + send.object(await getProfile(refid, name)); +}; + +/** + * Read a profile and send it. + */ +export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + send.object(await getProfile(refid)); +}; + +/** + * Get/create the profile based on refid and format it + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + */ +export const getProfile = async (refid: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + // Get Score + let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); + let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); + + const scoresData = await utils.readScores(refid, version); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + const medal = { + 100: 1, + 200: 2, + 300: 3, + 400: 5, + 500: 5, + 600: 6, + 700: 7, + 800: 9, + 900: 10, + 1000: 11, + 1100: 15, + }[score.clear_type]; + clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); + + const hiscore_index = (music * 4) + sheet; + const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); + const hiscore_bit_pos = ((hiscore_index * 17) % 8); + const hiscore_value = score.score << hiscore_bit_pos; + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + } + + let player: any = { + base: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', '1234-5678'), + staff: K.ITEM('s8', 0), + is_conv: K.ITEM('s8', -1), + collabo: K.ITEM('u8', 255), + + // TODO: replace with real data + total_play_cnt: K.ITEM('s32', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + my_best: K.ARRAY('s16', Array(20).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1]), + active_fr_num: K.ITEM('u8', 0), + clear_medal: K.ARRAY('u16', clear_medal), + clear_medal_sub: K.ARRAY('u8', clear_medal_sub), + }, + netvs: { + rank_point: K.ITEM('s32', 0), + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + rank: K.ITEM('u8', 0), + vs_rank_old: K.ITEM('s8', 0), + dialog: [ + K.ITEM('str', 'dialog#0'), + K.ITEM('str', 'dialog#1'), + K.ITEM('str', 'dialog#2'), + K.ITEM('str', 'dialog#3'), + K.ITEM('str', 'dialog#4'), + K.ITEM('str', 'dialog#5'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + netvs_play_cnt: K.ITEM('u8', 0), + }, + hiscore: K.ITEM('bin', Buffer.from(hiscore_array)), + gakuen_data: { + music_list: K.ITEM('s32', -1), + }, + flying_saucer: { + music_list: K.ITEM('s32', -1), + tune_count: K.ITEM('s32', -1), + clear_norma: K.ITEM('u32', 0), + clear_norma_add: K.ITEM('u32', 0), + }, + triple_journey: { + music_list: K.ITEM('s32', -1), + boss_damage: K.ARRAY('s32', [65534, 65534, 65534, 65534]), + boss_stun: K.ARRAY('s32', [0, 0, 0, 0]), + magic_gauge: K.ITEM('s32', 0), + today_party: K.ITEM('s32', 0), + union_magic: K.ITEM('bool', false), + base_attack_rate: K.ITEM('float', 1.0), + iidx_play_num: K.ITEM('s32', 0), + reflec_play_num: K.ITEM('s32', 0), + voltex_play_num: K.ITEM('s32', 0), + iidx_play_flg: K.ITEM('bool', true), + reflec_play_flg: K.ITEM('bool', true), + voltex_play_flg: K.ITEM('bool', true), + }, + ios: { + continueRightAnswer: K.ITEM('s32', 30), + totalRightAnswer: K.ITEM('s32', 30), + }, + kac2013: { + music_num: K.ITEM('s8', 0), + music: K.ITEM('s16', 0), + sheet: K.ITEM('u8', 0), + }, + baseball_data: { + music_list: K.ITEM('s64', BigInt(-1)), + }, + floor_infection: [ + { + infection_id: K.ITEM('s32', 3), + music_list: K.ITEM('s32', -1), + }, + { + infection_id: K.ITEM('s32', 5), + music_list: K.ITEM('s32', -1), + }, + { + infection_id: K.ITEM('s32', 7), + music_list: K.ITEM('s32', -1), + } + ] + }; + + // Add version specific datas + const params = await utils.readParams(refid, version); + utils.addExtraData(player, params, extraData); + + return player; +} + +/** + * Unformat and write the end game data into DB + */ +export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + if (!refid) return send.deny(); + + const params = await utils.readParams(refid, version); + utils.getExtraData(data, params, extraData); + await utils.writeParams(refid, version, params); + + const scoresData = await utils.readScores(refid, version, true); + + for (const scoreData of $(data).elements('stage')) { + const music = scoreData.number('no', -1); + const sheet = scoreData.number('sheet'); + const clear_type = { + 1: 100, + 2: 200, + 3: 300, + 5: 500, + 6: 600, + 7: 700, + 9: 800, + 10: 900, + 11: 1000, + 15: 1100, + }[(scoreData.number('n_data') >> (sheet * 4)) & 0x000F]; + const score = scoreData.number('score', 0); + + const key = `${music}:${sheet}`; + + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + } + + utils.writeScores(refid, version, scoresData); + + const profile = await utils.readProfile(refid); + + const result = { + pref: K.ITEM('s8', -1), + name: K.ITEM('str', profile.name) + } + + send.object(result); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const version: string = 'v21'; +const GAME_MAX_MUSIC_ID = 1350; + +const extraData: ExtraData = { + mode: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + button: { type: 's8', path: 'base', pathSrc: '', default: 0 }, + last_play_flag: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + medal_and_friend: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + sub_category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + chara: { type: 's16', path: 'base', pathSrc: '', default: -1 }, + chara_category: { type: 's8', path: 'base', pathSrc: '', default: -1 }, + sheet: { type: 'u8', path: 'base', pathSrc: '', default: 0 }, + tutorial: { type: 's8', path: 'base', pathSrc: '', default: 0 }, + music_open_pt: { type: 's8', path: 'base', pathSrc: '', default: 0 }, + option: { type: 's32', path: 'base', pathSrc: '', default: 0 }, + music: { type: 's16', path: 'base', pathSrc: '', default: -1 }, + ep: { type: 'u16', path: 'base', pathSrc: '', default: 0 }, + sp_color_flg: { type: 's32', path: 'base', pathSrc: '', default: [0, 0], isArray: true }, + read_news: { type: 's32', path: 'base', pathSrc: '', default: 0 }, + consecutive_days_coupon: { type: 's16', path: 'base', pathSrc: '', default: 0 }, + gitadora_point: { type: 'u16', path: 'base', pathSrc: '', default: [2000, 2000, 2000], isArray: true }, + gitadora_select: { type: 'u8', path: 'base', pathSrc: '', default: 2 }, + + sp: { type: 's32', path: 'sp_data', default: 0 }, + + point: { type: 'u16', path: 'zoo', default: [0, 0, 0, 0, 0], isArray: true }, + music_list__2: { type: 's32', path: 'zoo', default: [0, 0], isArray: true }, + today_play_flag: { type: 's8', path: 'zoo', default: [0, 0, 0, 0], isArray: true }, + + hair: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 }, + face: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 }, + body: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 }, + effect: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 }, + object: { type: 'u8', path: 'avatar', pathSrc: '', default: 0 }, + comment: { type: 'u8', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_hair: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_face: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_body: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_effect: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_object: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0], isArray: true }, + get_comment_over: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0, 0], isArray: true }, + get_comment_under: { type: 's32', path: 'avatar', pathSrc: '', default: [0, 0, 0], isArray: true }, + + get_hair__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_face__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_body__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_effect__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_object__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_comment_over__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + get_comment_under__1: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_face: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_body: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_effect: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_object: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_comment_over: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, + new_comment_under: { type: 's32', path: 'avatar_add', default: [0, 0], isArray: true }, +} \ No newline at end of file diff --git a/popn@asphyxia/handler/tunestreet.ts b/popn@asphyxia/handler/tunestreet.ts new file mode 100644 index 0000000..5bfdf03 --- /dev/null +++ b/popn@asphyxia/handler/tunestreet.ts @@ -0,0 +1,273 @@ +import * as utils from "./utils"; + +export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result = K.ATTR({ game_phase: "2", psp_phase: "2" }); + + return send.object(result); +}; + +/** + * Create a new profile and send it. + */ +export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + if (!refid) return send.deny(); + + const name = $(data).attr()['name']; + + send.object(await getProfile(refid, name)); +}; + +/** + * Read a profile and send it. + */ +export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + if (!refid) return send.deny(); + + send.object(await getProfile(refid)); +}; + +/** + * Get/create the profile based on refid + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + * @returns + */ +export const getProfile = async (refid: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + const params = await utils.readParams(refid, version); + + let binary_profile = Array(2200).fill(0); + let name_binary = profile.name.substr(0, 12); + for (let i = 0; i < name_binary.length; i++) { + binary_profile[i] = name_binary.charAt(i); + } + + binary_profile[13] = { + 0: 0, + 1: 0, + 2: 1, + 3: 1, + 4: 4, + 5: 2, + }[_.get(params, `params.play_mode`, 0)] + + binary_profile[16] = _.get(params, `params.last_play_flag`, 0) & 0xFF + binary_profile[44] = _.get(params, `params.option`, 0) & 0xFF + binary_profile[45] = (_.get(params, `params.option`, 0) >> 8) & 0xFF + binary_profile[46] = (_.get(params, `params.option`, 0) >> 16) & 0xFF + binary_profile[47] = (_.get(params, `params.option`, 0) >> 24) & 0xFF + binary_profile[60] = _.get(params, `params.chara`, 0) & 0xFF + binary_profile[61] = (_.get(params, `params.chara`, 0) >> 8) & 0xFF + binary_profile[62] = _.get(params, `params.music`, 0) & 0xFF + binary_profile[63] = (_.get(params, `params.music`, 0) >> 8) & 0xFF + binary_profile[64] = _.get(params, `params.sheet`, 0) & 0xFF + binary_profile[65] = _.get(params, `params.category`, 0) & 0xFF + binary_profile[67] = _.get(params, `params.medal_and_friend`, 0) & 0xFF + + // Get Score + let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 7) * 17) + 7) / 8)).fill(0); + + const scoresData = await utils.readScores(refid, version); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if (sheet == 0) { + continue; + } + + //flag + const flags = __format_flags_for_score(sheet, score.clear_type); + const flags_index = music * 2 + binary_profile[108 + flags_index] = binary_profile[108 + flags_index] | (flags & 0xFF) + binary_profile[109 + flags_index] = binary_profile[109 + flags_index] | ((flags >> 8) & 0xFF) + + if (sheet == 7 || sheet == 8) { + continue; + } + + const hiscore_index = (music * 7) + { + 9: 0, + 4: 1, + 5: 2, + 6: 3, + 1: 4, + 2: 5, + 3: 6, + }[sheet]; + const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); + const hiscore_bit_pos = ((hiscore_index * 17) % 8); + const hiscore_value = score.score << hiscore_bit_pos; + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + } + + const player = { + b: K.ITEM('bin', Buffer.from(binary_profile)), + hiscore: K.ITEM('bin', Buffer.from(hiscore_array)), + town: K.ITEM('bin', Buffer.alloc(0)), + } + + return player; +} + +const __format_flags_for_score = (sheet: number, clear_type: number) => { + const playedflag = { + 9: 0x2000, + 4: 0x0800, + 5: 0x1000, + 6: 0x4000, + 1: 0x0800, + 2: 0x1000, + 3: 0x4000, + 7: 0, + 8: 0, + }[sheet] + const shift = { + 9: 4, + 4: 0, + 5: 2, + 6: 6, + 1: 0, + 2: 2, + 3: 6, + 7: 9, + 8: 8, + }[sheet] + const flags = { + 100: 0, + 200: 0, + 300: 0, + 400: 1, + 500: 1, + 600: 1, + 700: 1, + 800: 2, + 900: 2, + 1000: 2, + 1100: 3, + }[clear_type] + return (flags << shift) | playedflag +} + +export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + if (!refid) return send.deny(); + + const params = await utils.readParams(refid, version); + + params.params['play_mode'] = parseInt($(data).attr()['play_mode']); + params.params['chara'] = parseInt($(data).attr()['chara_num']); + params.params['option'] = parseInt($(data).attr()['option']); + params.params['last_play_flag'] = parseInt($(data).attr()['last_play_flag']); + params.params['medal_and_friend'] = parseInt($(data).attr()['medal_and_friend']); + params.params['music'] = parseInt($(data).attr()['music_num']); + params.params['sheet'] = parseInt($(data).attr()['sheet_num']); + params.params['category'] = parseInt($(data).attr()['category_num']); + + await utils.writeParams(refid, version, params); + + const scoresData = await utils.readScores(refid, version, true); + + for (const scoreData of $(data).elements('music')) { + const music = parseInt(scoreData.attr()['music_num']); + let sheet = parseInt(scoreData.attr()['sheet_num']); + const score = parseInt(scoreData.attr()['score']); + const data = parseInt(scoreData.attr()['data']); + + if (sheet == 4 || sheet == 5) { + continue; + } + + if(params.params['play_mode'] == 4) { + if ([2, 6, 7].indexOf(sheet) != -1) { + continue; + } + sheet = { + 0: 1, + 1: 2, + 3: 3, + }[sheet] + } else { + sheet = { + 0: 4, + 1: 5, + 2: 9, + 3: 6, + 6: 7, + 7: 8, + }[sheet] + } + + const shift = { + 9: 4, + 4: 0, + 5: 2, + 6: 6, + 1: 0, + 2: 2, + 3: 6, + 7: 9, + 8: 8, + }[sheet] + + let mask = 0x3; + if (sheet == 7 || sheet == 8) { + mask = 0x1; + } + + const flags = (data >> shift) & mask; + const clear_type = { + 0: 100, + 1: 500, + 2: 800, + 3: 1100, + }[flags] + + const key = `${music}:${sheet}`; + + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + } + + utils.writeScores(refid, version, scoresData); + + const profile = await utils.readProfile(refid); + + const result = { + pref: K.ITEM('s8', -1), + name: K.ITEM('str', profile.name) + } + + send.object(result); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const version: string = 'v19'; +const GAME_MAX_MUSIC_ID = 1045; \ No newline at end of file diff --git a/popn@asphyxia/handler/usaneko.ts b/popn@asphyxia/handler/usaneko.ts new file mode 100644 index 0000000..09e3994 --- /dev/null +++ b/popn@asphyxia/handler/usaneko.ts @@ -0,0 +1,719 @@ +import { AchievementsUsaneko } from "../models/achievements"; +import { ExtraData, Params, Phase } from "../models/common"; +import * as utils from "./utils"; + +export const setRoutes = () => { + R.Route(`info24.common`, getInfo); + R.Route(`player24.new`, newPlayer); + R.Route(`player24.read`, read); + R.Route(`player24.start`, start); + R.Route(`player24.buy`, buy); + R.Route(`player24.read_score`, readScore); + R.Route(`player24.write_music`, writeScore); + R.Route(`player24.write`, write); +} + +const getInfoCommon = (req: EamuseInfo) => { + const result: any = { + phase: [], + choco: [], + goods: [], + area: [], + }; + + // Phase + const date: number = parseInt(req.model.match(/:(\d*)$/)[1]); + let phaseData: Phase[] = PHASE[getVersion(req)]; + + for (const phase of phaseData) { + result.phase.push({ + event_id: K.ITEM('s16', phase.id), + phase: K.ITEM('s16', phase.p), + }); + } + + // Choco + for (let i = 1; i <= 5; ++i) { + result.choco.push({ + choco_id: K.ITEM('s16', i), + param: K.ITEM('s32', -1), + }); + } + + // Goods + for (let i = 1; i <= 98; ++i) { + let price = 200; + if (i < 15) { + price = 30; + } else if (i < 30) { + price = 40; + } else if (i < 45) { + price = 60; + } else if (i < 60) { + price = 80; + } + + result.goods.push({ + item_id: K.ITEM('s32', i), + item_type: K.ITEM('s16', 3), + price: K.ITEM('s32', price), + goods_type: K.ITEM('s16', 0), + }); + } + + // Area + for (let i = 1; i <= 16; ++i) { + result.area.push({ + area_id: K.ITEM('s16', i), + end_date: K.ITEM('u64', BigInt(0)), + medal_id: K.ITEM('s16', i), + is_limit: K.ITEM('bool', 0), + }); + } + + // TODO : Course ranking + // TODO : Most popular characters + // TODO : Most popular music + + return result; +} + +const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + return send.object(getInfoCommon(req)); +} + +const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const result = { + play_id: K.ITEM('s32', 1), + ...getInfoCommon(req), + }; + await send.object(result); +}; + +/** + * Create a new profile and send it. + */ +const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const name = $(data).str('name'); + + send.object(await getProfile(refid, getVersion(req), name)); +}; + +/** + * Read a profile and send it. + */ +const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + send.object(await getProfile(refid, getVersion(req))); +}; + +const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const type = $(data).number('type', -1); + const id = $(data).number('id', -1); + const param = $(data).number('param', 0); + const price = $(data).number('price', 0); + const lumina = $(data).number('lumina', 0); + + if (type < 0 || id < 0) { + return send.deny(); + } + + if (lumina >= price) { + const version = getVersion(req); + + const params = await utils.readParams(refid, version); + params.params.player_point = lumina - price; + await utils.writeParams(refid, version, params); + + const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + achievements.items[`${type}:${id}`] = param; + await utils.writeAchievements(refid, version, achievements); + } + send.success(); +}; + +const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + const version = getVersion(req); + if (!refid) return send.deny(); + + send.object({ music: await getScores(refid, version) }); +}; + +const getScores = async (refid: string, version: string) => { + const scoresData = await utils.readScores(refid, version); + const result = []; + + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID[version]) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + result.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type]), + clear_rank: K.ITEM('u8', getRank(score.score)), + cnt: K.ITEM('s16', score.cnt), + }); + } + + return result; +}; + +const getRank = (score: number): number => { + if (score < 50000) { + return 1 + } else if (score < 62000) { + return 2 + } else if (score < 72000) { + return 3 + } else if (score < 82000) { + return 4 + } else if (score < 90000) { + return 5 + } else if (score < 95000) { + return 6 + } else if (score < 98000) { + return 7 + } + return 8 +} + +const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const version = getVersion(req); + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const music = $(data).number('music_num'); + const sheet = $(data).number('sheet_num'); + const clear_type = { + 1: 100, + 2: 200, + 3: 300, + 4: 400, + 5: 500, + 6: 600, + 7: 700, + 8: 800, + 9: 900, + 10: 1000, + 11: 1100, + }[$(data).number('clear_type')]; + const score = $(data).number('score'); + + const key = `${music}:${sheet}`; + + const scoresData = await utils.readScores(refid, version, true); + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + clear_type + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + clear_type: Math.max(clear_type, scoresData.scores[key].clear_type || 0) + }; + } + + utils.writeScores(refid, version, scoresData); + + send.success(); +}; + +/** + * Get/create the profile based on refid + * @param refid the profile refid + * @param name if defined, create/update the profile with the given name + * @returns + */ +const getProfile = async (refid: string, version: string, name?: string) => { + const profile = await utils.readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await utils.writeProfile(refid, profile); + } + + const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + + let player: any = { + result: K.ITEM('s8', 0), + account: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + staff: K.ITEM('s8', 0), + item_type: K.ITEM('s16', 0), + item_id: K.ITEM('s16', 0), + is_conv: K.ITEM('s8', 0), + license_data: K.ARRAY('s16', Array(20).fill(-1)), + + // TODO: replace with real data + total_play_cnt: K.ITEM('s16', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + total_days: K.ITEM('s16', 366), + interval_day: K.ITEM('s16', 1), + + // TODO: replace with real data + my_best: K.ARRAY('s16', Array(10).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + active_fr_num: K.ITEM('u8', 0), + }, + netvs: { + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + dialog: [ + K.ITEM('str', 'dialog'), + K.ITEM('str', 'dialog'), + K.ITEM('str', 'dialog'), + K.ITEM('str', 'dialog'), + K.ITEM('str', 'dialog'), + K.ITEM('str', 'dialog'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + netvs_play_cnt: K.ITEM('u32', 0), + }, + eaappli: { + relation: K.ITEM('s8', -1), + }, + custom_cate: { + valid: K.ITEM('s8', 0), + lv_min: K.ITEM('s8', -1), + lv_max: K.ITEM('s8', -1), + medal_min: K.ITEM('s8', -1), + medal_max: K.ITEM('s8', -1), + friend_no: K.ITEM('s8', -1), + score_flg: K.ITEM('s8', -1), + }, + // TODO: Navi data ?? + navi_data: { + raisePoint: K.ARRAY('s32', [-1, -1, -1, -1, -1]), + navi_param: { + navi_id: K.ITEM('u16', 0), + friendship: K.ITEM('s32', 0), + }, + }, + // TODO: Daily missions + mission: [ + { + mission_id: K.ITEM('u32', 170), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + { + mission_id: K.ITEM('u32', 157), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + { + mission_id: K.ITEM('u32', 47), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + ], + music: await getScores(refid, version), + area: [], + course_data: [], + fes: [], + item: [], + chara_param: [], + stamp: [], + }; + + const profileCharas = achievements.charas || {}; + for (const chara_id in profileCharas) { + player.chara_param.push({ + chara_id: K.ITEM('u16', parseInt(chara_id, 10)), + friendship: K.ITEM('u16', profileCharas[chara_id]), + }); + } + + const profileStamps = achievements.stamps || { '0': 0 }; + for (const stamp_id in profileStamps) { + player.stamp.push({ + stamp_id: K.ITEM('s16', parseInt(stamp_id, 10)), + cnt: K.ITEM('s16', profileStamps[stamp_id]), + }); + } + + const profileAreas = achievements.areas || {}; + for (const area_id in profileAreas) { + const area = profileAreas[area_id]; + player.area.push({ + area_id: K.ITEM('u32', parseInt(area_id, 10)), + chapter_index: K.ITEM('u8', area.chapter_index), + gauge_point: K.ITEM('u16', area.gauge_point), + is_cleared: K.ITEM('bool', area.is_cleared), + diary: K.ITEM('u32', area.diary), + }); + } + + const profileCourses = achievements.courses || {}; + for (const course_id in profileCourses) { + const course = profileCourses[course_id]; + player.course_data.push({ + course_id: K.ITEM('s16', parseInt(course_id, 10)), + clear_type: K.ITEM('u8', course.clear_type), + clear_rank: K.ITEM('u8', course.clear_rank), + total_score: K.ITEM('s32', course.total_score), + update_count: K.ITEM('s32', course.update_count), + sheet_num: K.ITEM('u8', course.sheet_num), + }); + } + + const profileFes = achievements.fes || {}; + for (const fes_id in profileFes) { + const fesElt = profileFes[fes_id]; + player.fes.push({ + fes_id: K.ITEM('u32', parseInt(fes_id, 10)), + chapter_index: K.ITEM('u8', fesElt.chapter_index), + gauge_point: K.ITEM('u16', fesElt.gauge_point), + is_cleared: K.ITEM('bool', fesElt.is_cleared), + }); + } + + const profileItems = achievements.items || {}; + for (const key in profileItems) { + const keyData = key.split(':'); + const type = parseInt(keyData[0], 10); + const id = parseInt(keyData[1], 10); + + const item: any = { + type: K.ITEM('u8', type), + id: K.ITEM('u16', id), + param: K.ITEM('u16', profileItems[key]), + is_new: K.ITEM('bool', 0), + get_time: K.ITEM('u64', BigInt(0)), + }; + + player.item.push(item); + } + + // Add version specific datas + let params = await utils.readParams(refid, version); + utils.addExtraData(player, params, EXTRA_DATA); + + return player; +} + +const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const version = getVersion(req); + + const params = await utils.readParams(refid, version); + const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + + utils.getExtraData(data, params, EXTRA_DATA); + + // areas + let areas = _.get(data, 'area', []); + if (!achievements.areas) { + achievements.areas = {}; + } + + if (!_.isArray(areas)) { + areas = [areas]; + } + + for (const area of areas) { + const id = $(area).number('area_id'); + const chapter_index = $(area).number('chapter_index'); + const gauge_point = $(area).number('gauge_point'); + const is_cleared = $(area).bool('is_cleared'); + const diary = $(area).number('diary'); + + achievements.areas[id] = { + chapter_index, + gauge_point, + is_cleared, + diary, + }; + } + + // courses + let courses = _.get(data, 'course_data', []); + if (!achievements.courses) { + achievements.courses = {}; + } + + if (!_.isArray(courses)) { + courses = [courses]; + } + + for (const course of courses) { + const id = $(course).number('course_id'); + const clear_type = $(course).number('clear_type'); + const clear_rank = $(course).number('clear_rank'); + const total_score = $(course).number('total_score'); + const update_count = $(course).number('update_count'); + const sheet_num = $(course).number('sheet_num'); + + achievements.courses[id] = { + clear_type, + clear_rank, + total_score, + update_count, + sheet_num, + }; + } + + // fes + let fes = _.get(data, 'fes', []); + if (!achievements.fes) { + achievements.fes = {}; + } + + if (!_.isArray(fes)) { + fes = [fes]; + } + + for (const fesElt of fes) { + const id = $(fesElt).number('fes_id'); + const chapter_index = $(fesElt).number('chapter_index'); + const gauge_point = $(fesElt).number('gauge_point'); + const is_cleared = $(fesElt).bool('is_cleared'); + + achievements.fes[id] = { + chapter_index, + gauge_point, + is_cleared, + }; + } + + // items + let items = _.get(data, 'item', []); + if (!achievements.items) { + achievements.items = {}; + } + + if (!_.isArray(items)) { + items = [items]; + } + + for (const item of items) { + const type = $(item).number('type'); + const id = $(item).number('id'); + const param = $(item).number('param'); + + const key = `${type}:${id}`; + + achievements.items[key] = param; + } + + // charas + let charas = _.get(data, 'chara_param', []); + if (!achievements.charas) { + achievements.charas = {}; + } + + if (!_.isArray(charas)) { + charas = [charas]; + } + + for (const chara of charas) { + const id = $(chara).number('chara_id'); + const param = $(chara).number('friendship'); + + achievements.charas[id] = param; + } + + // stamps + let stamps = _.get(data, 'stamp', []); + if (!achievements.stamps) { + achievements.stamps = { '0': 0 }; + } + + if (!_.isArray(stamps)) { + stamps = [stamps]; + } + + for (const stamp of stamps) { + const id = $(stamp).number('stamp_id'); + const cnt = $(stamp).number('cnt'); + + achievements.stamps[id] = cnt; + } + + await utils.writeParams(refid, version, params); + await utils.writeAchievements(refid, version, achievements); + + send.success(); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +const getVersion = (req: EamuseInfo): string => { + const date: number = parseInt(req.model.match(/:(\d*)$/)[1]); + if (date >= 2018101700) { + return 'v25'; + } else { + return 'v24'; + } +} + +const GAME_MAX_MUSIC_ID = { + v24: 1704, + v25: 1877 +} + +const defaultAchievements: AchievementsUsaneko = { + collection: 'achievements', + version: null, + areas: {}, + courses: {}, + fes: {}, + items: {}, + charas: {}, + stamps: {}, +} + +const PHASE = { + v24: [ + { id: 0, p: 11 }, // Default song phase availability (0-11) + { id: 1, p: 2 }, + { id: 2, p: 2 }, + { id: 3, p: 4 }, + { id: 4, p: 1 }, + { id: 5, p: 0 }, // Enable Net Taisen (0-1) + { id: 6, p: 1 }, // Enable NAVI-kun shunkyoku toujou, allows song 1608 to be unlocked (0-1) + { id: 7, p: 1 }, + { id: 8, p: 2 }, + { id: 9, p: 0 }, // Daily Mission (0-2) + { id: 10, p: 15 }, // NAVI-kun Song phase availability (0-15) + { id: 11, p: 1 }, + { id: 12, p: 2 }, + { id: 13, p: 1 }, // Enable Pop'n Peace preview song (0-1) + ], + v25: [ + { id: 0, p: 23 }, + { id: 1, p: 4 }, + { id: 2, p: 2 }, + { id: 3, p: 4 }, + { id: 4, p: 1 }, + { id: 5, p: 0 }, // Enable Net Taisen (0-1) + { id: 6, p: 1 }, + { id: 7, p: 1 }, + { id: 8, p: 2 }, + { id: 9, p: 0 }, // Daily Mission (0-2) + { id: 10, p: 30 }, + { id: 11, p: 1 }, + { id: 12, p: 2 }, + { id: 13, p: 1 }, + { id: 14, p: 39 }, + { id: 15, p: 2 }, + { id: 16, p: 3 }, + { id: 17, p: 8 }, + { id: 18, p: 1 }, + { id: 19, p: 1 }, + { id: 20, p: 13 }, + { id: 21, p: 20 }, // pop'n event archive + { id: 22, p: 2 }, + { id: 23, p: 1 }, + { id: 24, p: 1 }, + ] +} + +const EXTRA_DATA: ExtraData = { + + play_id: { type: 's32', path: 'account', default: 0 }, + start_type: { type: 's8', path: 'account', default: 0 }, + tutorial: { type: 's16', path: 'account', default: -1 }, + area_id: { type: 's16', path: 'account', default: 51 }, + read_news: { type: 's16', path: 'account', default: 0 }, + nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, + favorite_chara: { type: 's16', path: 'account', default: Array(20).fill(-1), isArray: true }, + special_area: { type: 's16', path: 'account', default: Array(8).fill(-1), isArray: true }, + chocolate_charalist: { type: 's16', path: 'account', default: Array(5).fill(-1), isArray: true }, + chocolate_sp_chara: { type: 's32', path: 'account', default: 0 }, + chocolate_pass_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_hon_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_giri_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_kokyu_cnt: { type: 's32', path: 'account', default: 0 }, + teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, + welcom_pack: { type: 'bool', path: 'account', default: 0 }, + use_navi: { type: 's16', path: 'account', default: 0 }, + ranking_node: { type: 's32', path: 'account', default: 0 }, + chara_ranking_kind_id: { type: 's32', path: 'account', default: 0 }, + navi_evolution_flg: { type: 's8', path: 'account', default: 0 }, + ranking_news_last_no: { type: 's32', path: 'account', default: 0 }, + power_point: { type: 's32', path: 'account', default: 0 }, + player_point: { type: 's32', path: 'account', default: 300 }, + power_point_list: { type: 's32', path: 'account', default: [0], isArray: true }, + + mode: { type: 'u8', path: 'config', default: 0 }, + chara: { type: 's16', path: 'config', default: 0 }, + music: { type: 's16', path: 'config', default: 0 }, + sheet: { type: 'u8', path: 'config', default: 0 }, + category: { type: 's8', path: 'config', default: 0 }, + sub_category: { type: 's8', path: 'config', default: 0 }, + chara_category: { type: 's8', path: 'config', default: 0 }, + ms_banner_disp: { type: 's8', path: 'config', default: 0 }, + ms_down_info: { type: 's8', path: 'config', default: 0 }, + ms_side_info: { type: 's8', path: 'config', default: 0 }, + ms_raise_type: { type: 's8', path: 'config', default: 0 }, + ms_rnd_type: { type: 's8', path: 'config', default: 0 }, + banner_sort: { type: 's8', path: 'config', default: 0 }, + course_id: { type: 's16', path: 'config', default: 0 }, + course_folder: { type: 's8', path: 'config', default: 0 }, + + hispeed: { type: 's16', path: 'option', default: 10 }, + popkun: { type: 'u8', path: 'option', default: 0 }, + hidden: { type: 'bool', path: 'option', default: 0 }, + hidden_rate: { type: 's16', path: 'option', default: -1 }, + sudden: { type: 'bool', path: 'option', default: 0 }, + sudden_rate: { type: 's16', path: 'option', default: -1 }, + randmir: { type: 's8', path: 'option', default: 0 }, + gauge_type: { type: 's8', path: 'option', default: 0 }, + ojama_0: { type: 'u8', path: 'option', default: 0 }, + ojama_1: { type: 'u8', path: 'option', default: 0 }, + forever_0: { type: 'bool', path: 'option', default: 0 }, + forever_1: { type: 'bool', path: 'option', default: 0 }, + full_setting: { type: 'bool', path: 'option', default: 0 }, + guide_se: { type: 's8', path: 'option', default: 0 }, + judge: { type: 'u8', path: 'option', default: 0 }, + + ep: { type: 'u16', path: 'info', default: 0 }, + + effect_left: { type: 'u16', path: 'customize', default: 0 }, + effect_center: { type: 'u16', path: 'customize', default: 0 }, + effect_right: { type: 'u16', path: 'customize', default: 0 }, + hukidashi: { type: 'u16', path: 'customize', default: 0 }, + comment_1: { type: 'u16', path: 'customize', default: 0 }, + comment_2: { type: 'u16', path: 'customize', default: 0 }, +} \ No newline at end of file diff --git a/popn@asphyxia/handler/utils.ts b/popn@asphyxia/handler/utils.ts index 46d083a..a742bdd 100644 --- a/popn@asphyxia/handler/utils.ts +++ b/popn@asphyxia/handler/utils.ts @@ -1,4 +1,218 @@ -export const getVersion = (info: EamuseInfo) => { - const moduleName: string = info.module; - return `v${moduleName.match(/[0-9]+/)[0]}`; -}; \ No newline at end of file +import { Achievements } from "../models/achievements"; +import { Profile, Scores, ExtraData, Params } from "../models/common"; + +const CURRENT_DATA_VERSION = 2; + +export const addExtraData = (player: any, params: Params, extraData: ExtraData) => { + for (const field in extraData) { + const fieldName = field.replace(/(__\d*)/, ''); + + const fieldMetaData = extraData[field]; + if (fieldMetaData.isArray) { + _.set( + player, + `${fieldMetaData.path}.${fieldName}`, + K.ARRAY( + fieldMetaData.type as any, + _.get(params, `params.${field}`, fieldMetaData.default) + ) + ); + } else { + _.set( + player, + `${fieldMetaData.path}.${fieldName}`, + K.ITEM( + fieldMetaData.type as any, + _.get(params, `params.${field}`, fieldMetaData.default) + ) + ); + } + } +} + +export const getExtraData = (data: any, params: Params, extraData: ExtraData) => { + for (const field in extraData) { + const fieldName = field.replace(/(__\d*)/, ''); + const fieldMetaData = extraData[field]; + + let path = fieldMetaData.path; + if (fieldMetaData.pathSrc != undefined) { + path = fieldMetaData.pathSrc; + } + if (path.length > 0) { + path += '.'; + } + + let value = _.get(data, path + fieldName + '.@content'); + if (value == 'undefined' && value == null) { + continue; + } + + if (_.isArray(value) && value.length == 1) { + value = value[0]; + } + + _.set(params, `params.${field}`, value); + } +} + +export const readProfile = async (refid: string): Promise => { + const profile = await DB.FindOne(refid, { collection: 'profile' }); + if (profile !== undefined && profile !== null && profile.dataVersion !== CURRENT_DATA_VERSION) { + return await doConvert(profile); + } + return profile || { collection: 'profile', name: 'ゲスト', dataVersion: CURRENT_DATA_VERSION }; +} + +export const writeProfile = async (refid: string, profile: Profile) => { + await DB.Upsert(refid, { collection: 'profile' }, profile); +} + +export const readParams = async (refid: string, version: string): Promise => { + const params = await DB.FindOne(refid, { collection: 'params', version }); + return params || { collection: 'params', version, params: {} }; +} + +export const writeParams = async (refid: string, version: string, params: Params) => { + await DB.Upsert(refid, { collection: 'params', version }, params); +} + +export const readScores = async (refid: string, version: string, forceVersion: boolean = false): Promise => { + if (forceVersion || !U.GetConfig("enable_score_sharing")) { + const score = await DB.FindOne(refid, { collection: 'scores', version }); + return score || { collection: 'scores', version, scores: {} }; + } else { + let retScore = { collection: 'scores', version, scores: {} }; + const scores = await DB.Find(refid, { collection: 'scores' }); + for (const score of scores) { + _.mergeWith(retScore.scores, score.scores, (objValue, srcValue) => { + if (objValue == undefined && srcValue != undefined) { + return srcValue; + } + return { + score: Math.max(objValue.score, srcValue.score), + cnt: objValue.cnt + srcValue.cnt, + clear_type: Math.max(objValue.clear_type, srcValue.clear_type) + } + }); + } + return retScore; + } +} + +export const writeScores = async (refid: string, version: string, scores: Scores) => { + await DB.Upsert(refid, { collection: 'scores', version }, scores); +} + +export const readAchievements = async (refid: string, version: string, defaultValue: Achievements): Promise => { + const achievements = await DB.FindOne(refid, { collection: 'achievements', version }); + return achievements || defaultValue; +} + +export const writeAchievements = async (refid: string, version: string, achievements: Achievements) => { + await DB.Upsert(refid, { collection: 'achievements', version }, achievements); +} + +const doConvert = async (profile: ProfileDoc): Promise> => { + let achievements = []; + + // charas + if (profile.charas !== undefined) { + for (let version in profile.charas) { + achievements[version] = { collection: 'achievements', version, charas: profile.charas[version] }; + } + } + + // stamps + if (profile.stamps !== undefined) { + for (let version in profile.stamps) { + if (achievements[version] === undefined) { + achievements[version] = { collection: 'achievements', version, stamps: profile.stamps[version] }; + } else { + achievements[version].stamps = profile.stamps[version] + } + } + } + + // medals + if (profile.medals !== undefined) { + for (let version in profile.medals) { + if (achievements[version] === undefined) { + achievements[version] = { collection: 'achievements', version, medals: profile.medals[version] }; + } else { + achievements[version].medals = profile.medals[version] + } + } + } + + // items + if (profile.items !== undefined) { + for (let version in profile.items) { + if (achievements[version] === undefined) { + achievements[version] = { collection: 'achievements', version, items: profile.items[version] }; + } else { + achievements[version].items = profile.items[version] + } + } + } + + // Write achievements + for (let version in achievements) { + const nbAchievements = await DB.Count(profile.__refid, { collection: 'achievements', version }); + if (nbAchievements == 0) { + await DB.Insert(profile.__refid, achievements[version]); + } + } + + // Write extras/params + if (profile.extras !== undefined) { + for (let version in profile.extras) { + const nbParams = await DB.Count(profile.__refid, { collection: 'params', version }); + if (nbParams == 0) { + let params: Params = { collection: 'params', version, params: profile.extras[version] }; + + // stamps + if (profile.stamps !== undefined && profile.stamps[version] !== undefined) { + const key = Object.keys(profile.stamps[version])[0]; + params.params.stamp_id = key; + params.params.cnt = profile.stamps[version][key]; + } + + await DB.Insert(profile.__refid, params); + } + } + } + + // Update profile + const newProfile = await (await DB.Upsert(profile.__refid, { collection: 'profile' }, { collection: 'profile', name: profile.name, dataVersion: 2 })).docs[0]; + + // Update scores + let scoresData: Scores = { collection: 'scores', version: 'v25', scores: {} }; + const oldScores = await DB.Find(null, { collection: 'scores' }); + for (const oldScore of oldScores) { + for (const key in oldScore.scores) { + scoresData.scores[key] = { + score: oldScore.scores[key].score, + cnt: oldScore.scores[key].cnt, + clear_type: { + 0: 100, + 1: 100, + 2: 200, + 3: 300, + 4: 400, + 5: 500, + 6: 600, + 7: 700, + 8: 800, + 9: 900, + 10: 1000, + 11: 1100, + }[Math.max(oldScore.scores[key].clearmedal || 0, oldScore.scores[key].clear_type || 0)] + }; + } + await DB.Remove(oldScore.__refid, { collection: 'scores' }); + await DB.Insert(oldScore.__refid, scoresData); + } + + return newProfile; +} \ No newline at end of file diff --git a/popn@asphyxia/handler/webui.ts b/popn@asphyxia/handler/webui.ts index 139ccd1..5f93701 100644 --- a/popn@asphyxia/handler/webui.ts +++ b/popn@asphyxia/handler/webui.ts @@ -1,5 +1,4 @@ -import { Profile } from "../models/profile"; -import { Scores } from "../models/scores"; +import { Profile, Scores } from "../models/common"; export const importPnmData = async (data: { refid: string; diff --git a/popn@asphyxia/index.ts b/popn@asphyxia/index.ts index 2b405a3..12a6877 100644 --- a/popn@asphyxia/index.ts +++ b/popn@asphyxia/index.ts @@ -1,43 +1,55 @@ -import { getInfo } from "./handler/common"; -import { newPlayer, read, readScore, start, writeMusic, write, buy } from "./handler/player"; +import * as tunestreet from "./handler/tunestreet"; +import * as fantasia from "./handler/fantasia"; +import * as sunny from "./handler/sunny"; +import * as lapistoria from "./handler/lapistoria"; +import * as eclale from "./handler/eclale"; +import * as usaneko from "./handler/usaneko"; import { importPnmData } from "./handler/webui"; +const getVersion = (req: any) => { + switch (req.gameCode) { + case 'K39': + return tunestreet; + case 'L39': + return fantasia; + case 'M39': + return sunny; + } +} + export function register() { + R.GameCode('K39'); + R.GameCode('L39'); R.GameCode('M39'); - - R.Config("enable_25_event", { - name: "PNM25 event", - desc: "Enable the pop'n event archive", + + R.Config("enable_score_sharing", { + name: "Score sharing", + desc: "Enable sharing scores between versions", type: "boolean", default: true, }); R.WebUIEvent('importPnmData', importPnmData); + R.WebUIEvent('updatePnmPlayerInfo', async (data: any) => { await DB.Update(data.refid, { collection: 'profile' }, { $set: { name: data.name } }); }); - const PlayerRoute = (method: string, handler: EPR | boolean) => { - R.Route(`player24.${method}`, handler); - R.Route(`player23.${method}`, handler); - }; + // Route management for PnM <= 21 - const CommonRoute = (method: string, handler: EPR | boolean) => { - R.Route(`info24.${method}`, handler); - R.Route(`info23.${method}`, handler); - }; + R.Route(`game.get`, async (req, data, send) => getVersion(req).getInfo(req, data, send)); + R.Route(`playerdata.new`, async (req, data, send) => getVersion(req).newPlayer(req, data, send)); + R.Route(`playerdata.conversion`, async (req, data, send) => getVersion(req).newPlayer(req, data, send)); + R.Route(`playerdata.get`, async (req, data, send) => getVersion(req).read(req, data, send)); + R.Route(`playerdata.set`, async (req, data, send) => getVersion(req).write(req, data, send)); - // Common - CommonRoute('common', (req, data, send) => { - return send.object(getInfo(req)); + // For Pnm >= 22, each game set his own route + + lapistoria.setRoutes(); + eclale.setRoutes(); + usaneko.setRoutes(); + + R.Unhandled((req: EamuseInfo, data: any, send: EamuseSend) => { + return send.success(); }); - - // Player - PlayerRoute('new', newPlayer); - PlayerRoute('read', read); - PlayerRoute('read_score', readScore); - PlayerRoute('write_music', writeMusic); - PlayerRoute('write', write); - PlayerRoute('start', start); - PlayerRoute('buy', buy); } \ No newline at end of file diff --git a/popn@asphyxia/models/achievements.ts b/popn@asphyxia/models/achievements.ts new file mode 100644 index 0000000..5f508ec --- /dev/null +++ b/popn@asphyxia/models/achievements.ts @@ -0,0 +1,93 @@ +export interface Achievements { + collection: 'achievements', + version: string, +} + +export interface AchievementsLapistoria extends Achievements { + version: 'v22', + + achievements: { + [stamp_id: string]: number; + }; + + stories: { + [id: string]: { + chapter_id: number; + gauge_point: number; + is_cleared: boolean; + clear_chapter: number; + }; + }; + + items: { + [key: string]: number; + }; + + charas: { + [chara_id: string]: number; + }; +} + +export interface AchievementsEclale extends Achievements { + version: 'v23', + + medals: { + [id: string]: { + level: number; + exp: number; + set_count: number; + get_count: number; + }; + }; + + items: { + [key: string]: number; + }; + + charas: { + [chara_id: string]: number; + }; +} + +export interface AchievementsUsaneko extends Achievements { + version: 'v24' | 'v25', + + areas: { + [id: string]: { + chapter_index: number; + gauge_point: number; + is_cleared: boolean; + diary: number; + }; + }; + + courses: { + [id: string]: { + clear_type: number; + clear_rank: number; + total_score: number; + update_count: number; + sheet_num: number; + }; + }; + + fes: { + [id: string]: { + chapter_index: number; + gauge_point: number; + is_cleared: boolean; + }; + }; + + items: { + [key: string]: number; + }; + + charas: { + [chara_id: string]: number; + }; + + stamps: { + [stamp_id: string]: number; + }; +} \ No newline at end of file diff --git a/popn@asphyxia/models/common.ts b/popn@asphyxia/models/common.ts new file mode 100644 index 0000000..7bc4d54 --- /dev/null +++ b/popn@asphyxia/models/common.ts @@ -0,0 +1,42 @@ +export interface Phase { + id: number; + p: number; +} + +export interface ExtraData { + [field: string]: { + path: string; + pathSrc?: string; + type: string; + default: any; + isArray?: true; + }; +}; + +export interface Profile { + collection: 'profile', + name: string; + dataVersion: number; +} + +export interface Params { + collection: 'params', + version: string, + + params: { + [key: string]: any; + }; +} + +export interface Scores { + collection: 'scores', + version: string, + + scores: { + [key: string]: { + clear_type?: number; + score: number; + cnt: number; + }; + }; +} \ No newline at end of file diff --git a/popn@asphyxia/models/profile.ts b/popn@asphyxia/models/profile.ts deleted file mode 100644 index 2faf776..0000000 --- a/popn@asphyxia/models/profile.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface Profile { - collection: 'profile', - - name: string; - - stamps: { - [ver: string]: { - [stamp_id: string]: number; - }; - }; - - medals: { - [ver: string]: { - [id: string]: { - level: number; - exp: number; - set_count: number; - get_count: number; - }; - }; - }; - - items: { - [ver: string]: { - [key: string]: number; - }; - }; - - charas: { - [ver: string]: { - [chara_id: string]: number; - }; - }; - - extras: { - [ver: string]: { - [key: string]: any; - }; - }; -} \ No newline at end of file diff --git a/popn@asphyxia/models/scores.ts b/popn@asphyxia/models/scores.ts deleted file mode 100644 index 5472678..0000000 --- a/popn@asphyxia/models/scores.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Scores { - collection: 'scores', - - scores: { - [key: string]: { - clearmedal?: number; - clear_type?: number; - clear_rank?: number; - score: number; - cnt: number; - }; - }; -} \ No newline at end of file From bc93c8603e85b315a664286e8064fceef982d541 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Sat, 10 Apr 2021 13:47:18 +0200 Subject: [PATCH 08/16] Tune street: Fix profile name not displayed --- popn@asphyxia/README.md | 2 ++ popn@asphyxia/handler/tunestreet.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index 735fd01..bdbca7c 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -11,6 +11,8 @@ Plugin Version: **v2.0.0** - pop'n music Usagi to Neko to Shōnen no Yume - pop'n music peace +Important : require minimum Asphyxia Core **v1.31** + ## Changelog ### 2.0.0 diff --git a/popn@asphyxia/handler/tunestreet.ts b/popn@asphyxia/handler/tunestreet.ts index 5bfdf03..9d17dd9 100644 --- a/popn@asphyxia/handler/tunestreet.ts +++ b/popn@asphyxia/handler/tunestreet.ts @@ -45,9 +45,9 @@ export const getProfile = async (refid: string, name?: string) => { const params = await utils.readParams(refid, version); let binary_profile = Array(2200).fill(0); - let name_binary = profile.name.substr(0, 12); - for (let i = 0; i < name_binary.length; i++) { - binary_profile[i] = name_binary.charAt(i); + let name_binary = U.EncodeString(profile.name, 'shift_jis'); + for (let i = 0; i < name_binary.length || i < 12; i++) { + binary_profile[i] = name_binary[i]; } binary_profile[13] = { From abd452512933cb1fc736767859a1010c756a1a21 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Sun, 11 Apr 2021 15:35:56 +0200 Subject: [PATCH 09/16] Add most played songs --- popn@asphyxia/handler/eclale.ts | 23 ++++++++++++++++++++++- popn@asphyxia/handler/fantasia.ts | 22 +++++++++++++++++++--- popn@asphyxia/handler/lapistoria.ts | 16 +++++++++++++++- popn@asphyxia/handler/sunny.ts | 20 +++++++++++++++++--- popn@asphyxia/handler/tunestreet.ts | 25 +++++++++++++++++++++++-- popn@asphyxia/handler/usaneko.ts | 27 +++++++++++++++++++++++---- 6 files changed, 119 insertions(+), 14 deletions(-) diff --git a/popn@asphyxia/handler/eclale.ts b/popn@asphyxia/handler/eclale.ts index deb9ab9..6d071eb 100644 --- a/popn@asphyxia/handler/eclale.ts +++ b/popn@asphyxia/handler/eclale.ts @@ -229,6 +229,27 @@ const getProfile = async (refid: string, name?: string) => { await utils.writeProfile(refid, profile); } + let myBest = Array(10).fill(-1); + const scores = await utils.readScores(refid, version, true); + if(Object.entries(scores.scores).length > 0) { + const playCount = new Map(); + for(const key in scores.scores) { + const keyData = key.split(':'); + const music = parseInt(keyData[0], 10); + playCount.set(music, (playCount.get(music) || 0) + scores.scores[key].cnt); + } + + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if(i >= 10) { + break; + } + myBest[i] = value; + i++; + } + } + let player: any = { result: K.ITEM('s8', 0), account: { @@ -247,7 +268,7 @@ const getProfile = async (refid: string, name?: string) => { consecutive_days: K.ITEM('s16', 365), total_days: K.ITEM('s16', 366), interval_day: K.ITEM('s16', 1), - my_best: K.ARRAY('s16', Array(10).fill(-1)), + my_best: K.ARRAY('s16', myBest), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), active_fr_num: K.ITEM('u8', 0), }, diff --git a/popn@asphyxia/handler/fantasia.ts b/popn@asphyxia/handler/fantasia.ts index 454252e..eb8db7a 100644 --- a/popn@asphyxia/handler/fantasia.ts +++ b/popn@asphyxia/handler/fantasia.ts @@ -71,6 +71,7 @@ export const getProfile = async (refid: string, name?: string) => { let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; @@ -106,6 +107,19 @@ export const getProfile = async (refid: string, name?: string) => { hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); + } + + let myBest = Array(20).fill(-1); + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if (i >= 20) { + break; + } + myBest[i] = value; + i++; } let player: any = { @@ -114,16 +128,16 @@ export const getProfile = async (refid: string, name?: string) => { g_pm_id: K.ITEM('str', '1234-5678'), staff: K.ITEM('s8', 0), is_conv: K.ITEM('s8', -1), + my_best: K.ARRAY('s16', myBest), + clear_medal: K.ARRAY('u16', clear_medal), + clear_medal_sub: K.ARRAY('u8', clear_medal_sub), // TODO: replace with real data total_play_cnt: K.ITEM('s32', 100), today_play_cnt: K.ITEM('s16', 50), consecutive_days: K.ITEM('s16', 365), - my_best: K.ARRAY('s16', Array(20).fill(-1)), latest_music: K.ARRAY('s16', [-1, -1, -1]), active_fr_num: K.ITEM('u8', 0), - clear_medal: K.ARRAY('u16', clear_medal), - clear_medal_sub: K.ARRAY('u8', clear_medal_sub), }, player_card: { // TODO: replace with real data @@ -155,6 +169,8 @@ export const getProfile = async (refid: string, name?: string) => { const params = await utils.readParams(refid, version); utils.addExtraData(player, params, extraData); + player.player_card.best_music = myBest.slice(0, 3); + return player; } diff --git a/popn@asphyxia/handler/lapistoria.ts b/popn@asphyxia/handler/lapistoria.ts index 925e9f2..3828500 100644 --- a/popn@asphyxia/handler/lapistoria.ts +++ b/popn@asphyxia/handler/lapistoria.ts @@ -133,7 +133,6 @@ const getProfile = async (refid: string, name?: string) => { consecutive_days: K.ITEM('s16', 365), total_days: K.ITEM('s16', 366), interval_day: K.ITEM('s16', 1), - my_best: K.ARRAY('s16', Array(10).fill(-1)), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), active_fr_num: K.ITEM('u8', 0), }, @@ -174,6 +173,7 @@ const getProfile = async (refid: string, name?: string) => { // Add Score const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; @@ -208,8 +208,22 @@ const getProfile = async (refid: string, name?: string) => { old_score: K.ITEM('s32', 0), old_clear_type: K.ITEM('u8', 0), }); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); } + let myBest = Array(10).fill(-1); + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if (i >= 10) { + break; + } + myBest[i] = value; + i++; + } + player.account.my_best = K.ARRAY('s16', myBest); + // Add achievements const achievements = await utils.readAchievements(refid, version, defaultAchievements); diff --git a/popn@asphyxia/handler/sunny.ts b/popn@asphyxia/handler/sunny.ts index 903290a..f73c50c 100644 --- a/popn@asphyxia/handler/sunny.ts +++ b/popn@asphyxia/handler/sunny.ts @@ -67,6 +67,7 @@ export const getProfile = async (refid: string, name?: string) => { let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; @@ -102,6 +103,19 @@ export const getProfile = async (refid: string, name?: string) => { hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); + } + + let myBest = Array(20).fill(-1); + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if (i >= 20) { + break; + } + myBest[i] = value; + i++; } let player: any = { @@ -111,16 +125,16 @@ export const getProfile = async (refid: string, name?: string) => { staff: K.ITEM('s8', 0), is_conv: K.ITEM('s8', -1), collabo: K.ITEM('u8', 255), + my_best: K.ARRAY('s16', myBest), + clear_medal: K.ARRAY('u16', clear_medal), + clear_medal_sub: K.ARRAY('u8', clear_medal_sub), // TODO: replace with real data total_play_cnt: K.ITEM('s32', 100), today_play_cnt: K.ITEM('s16', 50), consecutive_days: K.ITEM('s16', 365), - my_best: K.ARRAY('s16', Array(20).fill(-1)), latest_music: K.ARRAY('s16', [-1, -1, -1]), active_fr_num: K.ITEM('u8', 0), - clear_medal: K.ARRAY('u16', clear_medal), - clear_medal_sub: K.ARRAY('u8', clear_medal_sub), }, netvs: { rank_point: K.ITEM('s32', 0), diff --git a/popn@asphyxia/handler/tunestreet.ts b/popn@asphyxia/handler/tunestreet.ts index 9d17dd9..efca0fd 100644 --- a/popn@asphyxia/handler/tunestreet.ts +++ b/popn@asphyxia/handler/tunestreet.ts @@ -76,6 +76,7 @@ export const getProfile = async (refid: string, name?: string) => { let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 7) * 17) + 7) / 8)).fill(0); const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; @@ -114,6 +115,26 @@ export const getProfile = async (refid: string, name?: string) => { hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); + } + + let myBest = Array(20).fill(-1); + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if (i >= 20) { + break; + } + myBest[i] = value; + i++; + } + + let profile_pos = 68 + for (const musicid of myBest) { + binary_profile[profile_pos] = musicid & 0xFF + binary_profile[profile_pos + 1] = (musicid >> 8) & 0xFF + profile_pos = profile_pos + 2 } const player = { @@ -169,7 +190,7 @@ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promi if (!refid) return send.deny(); const params = await utils.readParams(refid, version); - + params.params['play_mode'] = parseInt($(data).attr()['play_mode']); params.params['chara'] = parseInt($(data).attr()['chara_num']); params.params['option'] = parseInt($(data).attr()['option']); @@ -193,7 +214,7 @@ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promi continue; } - if(params.params['play_mode'] == 4) { + if (params.params['play_mode'] == 4) { if ([2, 6, 7].indexOf(sheet) != -1) { continue; } diff --git a/popn@asphyxia/handler/usaneko.ts b/popn@asphyxia/handler/usaneko.ts index 09e3994..783ec17 100644 --- a/popn@asphyxia/handler/usaneko.ts +++ b/popn@asphyxia/handler/usaneko.ts @@ -267,7 +267,26 @@ const getProfile = async (refid: string, version: string, name?: string) => { await utils.writeProfile(refid, profile); } - const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + let myBest = Array(10).fill(-1); + const scores = await utils.readScores(refid, version, true); + if(Object.entries(scores.scores).length > 0) { + const playCount = new Map(); + for(const key in scores.scores) { + const keyData = key.split(':'); + const music = parseInt(keyData[0], 10); + playCount.set(music, (playCount.get(music) || 0) + scores.scores[key].cnt); + } + + const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + let i = 0; + for (const value of sortedPlayCount.keys()) { + if(i >= 10) { + break; + } + myBest[i] = value; + i++; + } + } let player: any = { result: K.ITEM('s8', 0), @@ -279,6 +298,7 @@ const getProfile = async (refid: string, version: string, name?: string) => { item_id: K.ITEM('s16', 0), is_conv: K.ITEM('s8', 0), license_data: K.ARRAY('s16', Array(20).fill(-1)), + my_best: K.ARRAY('s16', myBest), // TODO: replace with real data total_play_cnt: K.ITEM('s16', 100), @@ -286,9 +306,6 @@ const getProfile = async (refid: string, version: string, name?: string) => { consecutive_days: K.ITEM('s16', 365), total_days: K.ITEM('s16', 366), interval_day: K.ITEM('s16', 1), - - // TODO: replace with real data - my_best: K.ARRAY('s16', Array(10).fill(-1)), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), active_fr_num: K.ITEM('u8', 0), }, @@ -354,6 +371,8 @@ const getProfile = async (refid: string, version: string, name?: string) => { stamp: [], }; + const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + const profileCharas = achievements.charas || {}; for (const chara_id in profileCharas) { player.chara_param.push({ From efb28ad9a02a6ab0a642cd3070396368650aadcd Mon Sep 17 00:00:00 2001 From: cracrayol Date: Mon, 12 Apr 2021 10:32:50 +0200 Subject: [PATCH 10/16] Fantasia: Fix player card best_music --- popn@asphyxia/handler/fantasia.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/popn@asphyxia/handler/fantasia.ts b/popn@asphyxia/handler/fantasia.ts index eb8db7a..c6a5a36 100644 --- a/popn@asphyxia/handler/fantasia.ts +++ b/popn@asphyxia/handler/fantasia.ts @@ -169,7 +169,7 @@ export const getProfile = async (refid: string, name?: string) => { const params = await utils.readParams(refid, version); utils.addExtraData(player, params, extraData); - player.player_card.best_music = myBest.slice(0, 3); + player.player_card.best_music = K.ARRAY('s16', myBest.slice(0, 3)); return player; } From c3119b6e4bcaeeee24d55c843ff7fbd740ed3194 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Thu, 15 Apr 2021 23:45:06 +0200 Subject: [PATCH 11/16] Add rivals support for Lapistoria-peace Fix stamp not properly initialized on usaneko --- popn@asphyxia/README.md | 6 +- popn@asphyxia/handler/eclale.ts | 91 ++++++++++++++++++++-------- popn@asphyxia/handler/lapistoria.ts | 68 ++++++++++++++++++++- popn@asphyxia/handler/usaneko.ts | 91 +++++++++++++++++++++------- popn@asphyxia/handler/utils.ts | 7 ++- popn@asphyxia/index.ts | 27 ++++++++- popn@asphyxia/models/common.ts | 5 ++ popn@asphyxia/webui/profile_page.pug | 39 ++++++++++++ 8 files changed, 280 insertions(+), 54 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index bdbca7c..da82e43 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -1,6 +1,6 @@ # Pop'n Music -Plugin Version: **v2.0.0** +Plugin Version: **v2.1.0** ## Supported Versions - pop'n music 19 Tune Street @@ -15,6 +15,10 @@ Important : require minimum Asphyxia Core **v1.31** ## Changelog +### 2.1.0 +* Add rivals support +* Various fixes + ### 2.0.0 * Big rewrite/reorganization of the code * Add support for Tune Street, fantasia, Sunny Park, Lapistoria diff --git a/popn@asphyxia/handler/eclale.ts b/popn@asphyxia/handler/eclale.ts index 6d071eb..9f68537 100644 --- a/popn@asphyxia/handler/eclale.ts +++ b/popn@asphyxia/handler/eclale.ts @@ -11,6 +11,7 @@ export const setRoutes = () => { R.Route(`player23.read_score`, readScore); R.Route(`player23.write_music`, writeScore); R.Route(`player23.write`, write); + R.Route(`player23.friend`, friend); } const getInfoCommon = (req: EamuseInfo) => { @@ -130,16 +131,31 @@ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< const refid = $(data).str('ref_id'); if (!refid) return send.deny(); + send.object({ music: await getScores(refid, version) }); +}; + +const getScores = async (refid: string, version: string, forFriend: boolean = false) => { const scoresData = await utils.readScores(refid, version); - const result: any = { - music: [], - }; + const result = []; for (const key in scoresData.scores) { const keyData = key.split(':'); const score = scoresData.scores[key]; const music = parseInt(keyData[0], 10); const sheet = parseInt(keyData[1], 10); + const clearType = { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type]; if (music > GAME_MAX_MUSIC_ID) { continue; @@ -148,28 +164,24 @@ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< continue; } - result.music.push({ - music_num: K.ITEM('s16', music), - sheet_num: K.ITEM('u8', sheet), - score: K.ITEM('s32', score.score), - clear_type: K.ITEM('u8', { - 100: 1, - 200: 2, - 300: 3, - 400: 4, - 500: 5, - 600: 6, - 700: 7, - 800: 8, - 900: 9, - 1000: 10, - 1100: 11, - }[score.clear_type]), - cnt: K.ITEM('s16', score.cnt), - }); + if(forFriend) { + result.push(K.ATTR({ + music_num: music.toString(), + sheet_num: sheet.toString(), + score: score.score.toString(), + clearmedal: clearType.toString() + })); + } else { + result.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', clearType), + cnt: K.ITEM('s16', score.cnt), + }); + } } - - send.object(result); + return result; }; const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { @@ -223,6 +235,7 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); + const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; @@ -261,6 +274,7 @@ const getProfile = async (refid: string, name?: string) => { is_conv: K.ITEM('s8', 0), meteor_flg: K.ITEM('bool', true), license_data: K.ARRAY('s16', Array(20).fill(-1)), + active_fr_num: K.ITEM('u8', rivals.rivals.length), // TODO: replace with real data total_play_cnt: K.ITEM('s16', 100), @@ -270,7 +284,6 @@ const getProfile = async (refid: string, name?: string) => { interval_day: K.ITEM('s16', 1), my_best: K.ARRAY('s16', myBest), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), - active_fr_num: K.ITEM('u8', 0), }, netvs: { record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), @@ -424,6 +437,34 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + const no = parseInt($(data).attr()['no'], -1); + + const rivals = await utils.readRivals(refid); + + if(no < 0 || no >= rivals.rivals.length) { + send.object({result : K.ITEM('s8', 2)}); + return; + } + + const profile = await utils.readProfile(rivals.rivals[no]); + const params = await utils.readParams(rivals.rivals[no], version); + + const friend = { + friend: { + no: K.ITEM('s16', no), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + name: K.ITEM('str', profile.name), + chara: K.ITEM('s16', params.params.chara || -1), + is_open: K.ITEM('s8', 1), + music : await getScores(rivals.rivals[no], version, true), + } + } + + send.object(friend); +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v23'; diff --git a/popn@asphyxia/handler/lapistoria.ts b/popn@asphyxia/handler/lapistoria.ts index 3828500..aa65e7d 100644 --- a/popn@asphyxia/handler/lapistoria.ts +++ b/popn@asphyxia/handler/lapistoria.ts @@ -8,6 +8,7 @@ export const setRoutes = () => { R.Route(`player22.read`, read); R.Route(`player22.write_music`, writeScore); R.Route(`player22.write`, write); + R.Route(`player22.friend`, friend); } /** @@ -110,6 +111,7 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); + const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; @@ -126,6 +128,7 @@ const getProfile = async (refid: string, name?: string) => { item_type: K.ITEM('s16', 0), item_id: K.ITEM('s16', 0), license_data: K.ARRAY('s16', [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]), + active_fr_num: K.ITEM('u8', rivals.rivals.length), // TODO: replace with real data total_play_cnt: K.ITEM('s16', 100), @@ -134,7 +137,6 @@ const getProfile = async (refid: string, name?: string) => { total_days: K.ITEM('s16', 366), interval_day: K.ITEM('s16', 1), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), - active_fr_num: K.ITEM('u8', 0), }, netvs: { rank_point: K.ITEM('s32', 0), @@ -372,6 +374,70 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + const no = parseInt($(data).attr()['no'], -1); + + const rivals = await utils.readRivals(refid); + + if(no < 0 || no >= rivals.rivals.length) { + send.object({result : K.ITEM('s8', 2)}); + return; + } + + const profile = await utils.readProfile(rivals.rivals[no]); + const params = await utils.readParams(rivals.rivals[no], version); + + // Add Score + const scoresData = await utils.readScores(rivals.rivals[no], version); + const scores = []; + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + scores.push(K.ATTR({ + music_num: music.toString(), + sheet_num: sheet.toString(), + score: score.score.toString(), + clearmedal: { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type].toString(), + })); + } + + const friend = { + friend: { + no: K.ITEM('s16', no), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + name: K.ITEM('str', profile.name), + chara: K.ITEM('s16', params.params.chara || -1), + is_open: K.ITEM('s8', 1), + music: scores, + } + } + + send.object(friend); +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v22'; diff --git a/popn@asphyxia/handler/usaneko.ts b/popn@asphyxia/handler/usaneko.ts index 783ec17..08e4dfe 100644 --- a/popn@asphyxia/handler/usaneko.ts +++ b/popn@asphyxia/handler/usaneko.ts @@ -11,6 +11,7 @@ export const setRoutes = () => { R.Route(`player24.read_score`, readScore); R.Route(`player24.write_music`, writeScore); R.Route(`player24.write`, write); + R.Route(`player24.friend`, friend); } const getInfoCommon = (req: EamuseInfo) => { @@ -148,7 +149,7 @@ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< send.object({ music: await getScores(refid, version) }); }; -const getScores = async (refid: string, version: string) => { +const getScores = async (refid: string, version: string, forFriend: boolean = false) => { const scoresData = await utils.readScores(refid, version); const result = []; @@ -157,6 +158,19 @@ const getScores = async (refid: string, version: string) => { const score = scoresData.scores[key]; const music = parseInt(keyData[0], 10); const sheet = parseInt(keyData[1], 10); + const clearType = { + 100: 1, + 200: 2, + 300: 3, + 400: 4, + 500: 5, + 600: 6, + 700: 7, + 800: 8, + 900: 9, + 1000: 10, + 1100: 11, + }[score.clear_type]; if (music > GAME_MAX_MUSIC_ID[version]) { continue; @@ -165,26 +179,24 @@ const getScores = async (refid: string, version: string) => { continue; } - result.push({ - music_num: K.ITEM('s16', music), - sheet_num: K.ITEM('u8', sheet), - score: K.ITEM('s32', score.score), - clear_type: K.ITEM('u8', { - 100: 1, - 200: 2, - 300: 3, - 400: 4, - 500: 5, - 600: 6, - 700: 7, - 800: 8, - 900: 9, - 1000: 10, - 1100: 11, - }[score.clear_type]), - clear_rank: K.ITEM('u8', getRank(score.score)), - cnt: K.ITEM('s16', score.cnt), - }); + if(forFriend) { + result.push(K.ATTR({ + music_num: music.toString(), + sheet_num: sheet.toString(), + score: score.score.toString(), + cleartype: clearType.toString(), + clearrank: getRank(score.score).toString() + })); + } else { + result.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', clearType), + clear_rank: K.ITEM('u8', getRank(score.score)), + cnt: K.ITEM('s16', score.cnt), + }); + } } return result; @@ -261,6 +273,7 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const getProfile = async (refid: string, version: string, name?: string) => { const profile = await utils.readProfile(refid); + const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; @@ -299,6 +312,7 @@ const getProfile = async (refid: string, version: string, name?: string) => { is_conv: K.ITEM('s8', 0), license_data: K.ARRAY('s16', Array(20).fill(-1)), my_best: K.ARRAY('s16', myBest), + active_fr_num: K.ITEM('u8', rivals.rivals.length), // TODO: replace with real data total_play_cnt: K.ITEM('s16', 100), @@ -307,7 +321,6 @@ const getProfile = async (refid: string, version: string, name?: string) => { total_days: K.ITEM('s16', 366), interval_day: K.ITEM('s16', 1), latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), - active_fr_num: K.ITEM('u8', 0), }, netvs: { record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), @@ -381,7 +394,10 @@ const getProfile = async (refid: string, version: string, name?: string) => { }); } - const profileStamps = achievements.stamps || { '0': 0 }; + let profileStamps = achievements.stamps; + if(Object.entries(profileStamps).length == 0) { + profileStamps = {"0": 0 }; + } for (const stamp_id in profileStamps) { player.stamp.push({ stamp_id: K.ITEM('s16', parseInt(stamp_id, 10)), @@ -595,6 +611,35 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + const no = parseInt($(data).attr()['no'], -1); + const version = getVersion(req); + + const rivals = await utils.readRivals(refid); + + if(no < 0 || no >= rivals.rivals.length) { + send.object({result : K.ITEM('s8', 2)}); + return; + } + + const profile = await utils.readProfile(rivals.rivals[no]); + const params = await utils.readParams(rivals.rivals[no], version); + + const friend = { + friend: { + no: K.ITEM('s16', no), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + name: K.ITEM('str', profile.name), + chara: K.ITEM('s16', params.params.chara || -1), + is_open: K.ITEM('s8', 1), + music : await getScores(rivals.rivals[no], version, true), + } + } + + send.object(friend); +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const getVersion = (req: EamuseInfo): string => { diff --git a/popn@asphyxia/handler/utils.ts b/popn@asphyxia/handler/utils.ts index a742bdd..53c7654 100644 --- a/popn@asphyxia/handler/utils.ts +++ b/popn@asphyxia/handler/utils.ts @@ -1,5 +1,5 @@ import { Achievements } from "../models/achievements"; -import { Profile, Scores, ExtraData, Params } from "../models/common"; +import { Profile, Scores, ExtraData, Params, Rivals } from "../models/common"; const CURRENT_DATA_VERSION = 2; @@ -68,6 +68,11 @@ export const writeProfile = async (refid: string, profile: Profile) => { await DB.Upsert(refid, { collection: 'profile' }, profile); } +export const readRivals = async (refid: string): Promise => { + const rivals = await DB.FindOne(refid, { collection: 'rivals' }); + return rivals || { collection: 'rivals', rivals: [] }; +} + export const readParams = async (refid: string, version: string): Promise => { const params = await DB.FindOne(refid, { collection: 'params', version }); return params || { collection: 'params', version, params: {} }; diff --git a/popn@asphyxia/index.ts b/popn@asphyxia/index.ts index 12a6877..5d3d639 100644 --- a/popn@asphyxia/index.ts +++ b/popn@asphyxia/index.ts @@ -5,6 +5,7 @@ import * as lapistoria from "./handler/lapistoria"; import * as eclale from "./handler/eclale"; import * as usaneko from "./handler/usaneko"; import { importPnmData } from "./handler/webui"; +import { Rivals } from "./models/common"; const getVersion = (req: any) => { switch (req.gameCode) { @@ -24,7 +25,7 @@ export function register() { R.Config("enable_score_sharing", { name: "Score sharing", - desc: "Enable sharing scores between versions", + desc: "Enable sharing scores between versions. This also affect rivals scores.", type: "boolean", default: true, }); @@ -35,8 +36,29 @@ export function register() { await DB.Update(data.refid, { collection: 'profile' }, { $set: { name: data.name } }); }); - // Route management for PnM <= 21 + // Rivals UI management + R.WebUIEvent('deleteRival', async (data: any) => { + const rivals = await DB.FindOne(data.refid, { collection: 'rivals' }) || {collection: 'rivals', rivals: []}; + const idx = rivals.rivals.indexOf(data.rivalid); + if(idx >= 0) { + rivals.rivals.splice(idx, 1); + await DB.Update(data.refid, { collection: 'rivals' }, rivals); + } + }); + R.WebUIEvent('addRival', async (data: any) => { + const refid = data.refid.trim(); + const profile = await DB.FindOne(refid, { collection: 'profile'}); + if(profile != undefined && profile != null) { + const rivals = await DB.FindOne(refid, { collection: 'rivals' }) || {collection: 'rivals', rivals: []}; + if(rivals.rivals.length < 4) { + rivals.rivals.push(data.rivalid); + await DB.Upsert(refid, { collection: 'rivals' }, rivals); + } + } + }); + + // Route management for PnM <= 21 R.Route(`game.get`, async (req, data, send) => getVersion(req).getInfo(req, data, send)); R.Route(`playerdata.new`, async (req, data, send) => getVersion(req).newPlayer(req, data, send)); R.Route(`playerdata.conversion`, async (req, data, send) => getVersion(req).newPlayer(req, data, send)); @@ -44,7 +66,6 @@ export function register() { R.Route(`playerdata.set`, async (req, data, send) => getVersion(req).write(req, data, send)); // For Pnm >= 22, each game set his own route - lapistoria.setRoutes(); eclale.setRoutes(); usaneko.setRoutes(); diff --git a/popn@asphyxia/models/common.ts b/popn@asphyxia/models/common.ts index 7bc4d54..b6160f1 100644 --- a/popn@asphyxia/models/common.ts +++ b/popn@asphyxia/models/common.ts @@ -28,6 +28,11 @@ export interface Params { }; } +export interface Rivals { + collection: 'rivals', + rivals: string[] +} + export interface Scores { collection: 'scores', version: string, diff --git a/popn@asphyxia/webui/profile_page.pug b/popn@asphyxia/webui/profile_page.pug index 4190081..3cecda4 100644 --- a/popn@asphyxia/webui/profile_page.pug +++ b/popn@asphyxia/webui/profile_page.pug @@ -1,5 +1,6 @@ //DATA// profile: DB.FindOne(refid, { collection: 'profile' }) + rivals: DB.FindOne(refid, { collection: 'rivals' }) div div.notification.is-success.is-hidden#import-success @@ -23,6 +24,44 @@ div span.icon i.mdi.mdi-check span Submit + .card + .card-header + p.card-header-title + span.icon + i.mdi.mdi-account-edit + | Rivals + .card-content + .columns.is-multiline + if rivals != null + - for (const rival of rivals.rivals) + form(method="post" action="/emit/deleteRival").column.is-4 + .box + input(type="hidden" id="refid" name="refid" value=refid) + input(type="hidden" id="rivalid" name="rivalid" value=rival) + .field + input.input(type="text" value=rival disabled="disabled") + .field + button.button.is-primary + span.icon + i.mdi.mdi-file-import-outline + span Delete + if rivals == null || rivals.rivals.length < 4 + form(method="post" action="/emit/addRival").column.is-4 + .box + input(type="hidden" id="refid" name="refid" value=refid) + .field + input.input(type="text" id="rivalid" name="rivalid" placeholder="Rival ID (ex. AAB56E7436549D83)") + .field + button.button.is-primary + span.icon + i.mdi.mdi-file-import-outline + span Add + div + label To add a rival, use the profile ID located on the POPN Profiles page. + div + label There is a limit of 4 rivals maximum (only the 2 firsts will be used for Sunny Park and lower). + div + label The score sharing option also affect scores get from rivals. .card .card-header p.card-header-title From af7128e3273e3f70b196b55451b4b9dec0234c1e Mon Sep 17 00:00:00 2001 From: cracrayol Date: Thu, 15 Apr 2021 23:47:00 +0200 Subject: [PATCH 12/16] Fix addRival refid check --- popn@asphyxia/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/popn@asphyxia/index.ts b/popn@asphyxia/index.ts index 5d3d639..d7fdbda 100644 --- a/popn@asphyxia/index.ts +++ b/popn@asphyxia/index.ts @@ -48,7 +48,7 @@ export function register() { R.WebUIEvent('addRival', async (data: any) => { const refid = data.refid.trim(); - const profile = await DB.FindOne(refid, { collection: 'profile'}); + const profile = await DB.FindOne(data.rivalid, { collection: 'profile'}); if(profile != undefined && profile != null) { const rivals = await DB.FindOne(refid, { collection: 'rivals' }) || {collection: 'rivals', rivals: []}; if(rivals.rivals.length < 4) { From 84113ca2e27d06619ddd841d791a7a7d01c566b2 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Sat, 17 Apr 2021 00:13:02 +0200 Subject: [PATCH 13/16] Add rivals support for fantasia and sunny park Add comments --- popn@asphyxia/README.md | 4 +- popn@asphyxia/handler/eclale.ts | 46 +++++--- popn@asphyxia/handler/fantasia.ts | 146 +++++++++++++++++--------- popn@asphyxia/handler/lapistoria.ts | 20 ++-- popn@asphyxia/handler/sunny.ts | 156 ++++++++++++++++++---------- popn@asphyxia/handler/tunestreet.ts | 18 +++- popn@asphyxia/handler/usaneko.ts | 55 +++++++--- popn@asphyxia/index.ts | 13 +-- 8 files changed, 309 insertions(+), 149 deletions(-) diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md index da82e43..68c2547 100644 --- a/popn@asphyxia/README.md +++ b/popn@asphyxia/README.md @@ -16,8 +16,8 @@ Important : require minimum Asphyxia Core **v1.31** ## Changelog ### 2.1.0 -* Add rivals support -* Various fixes +* Add rivals support (except for Tune Street) +* Some fixes ### 2.0.0 * Big rewrite/reorganization of the code diff --git a/popn@asphyxia/handler/eclale.ts b/popn@asphyxia/handler/eclale.ts index 9f68537..4eb23ef 100644 --- a/popn@asphyxia/handler/eclale.ts +++ b/popn@asphyxia/handler/eclale.ts @@ -14,6 +14,9 @@ export const setRoutes = () => { R.Route(`player23.friend`, friend); } +/** + * Return current state of the game (phase, good prices, etc...) + */ const getInfoCommon = (req: EamuseInfo) => { const result: any = { phase: [], @@ -80,7 +83,7 @@ const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise }; /** - * Create a new profile and send it. + * Handler for new profile */ const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -92,7 +95,7 @@ const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< }; /** - * Read a profile and send it. + * Handler for existing profile */ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -101,6 +104,9 @@ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.object(await getProfile(refid)); }; +/** + * Handler fo buying goods with lumina + */ const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -127,14 +133,22 @@ const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise = send.success(); }; +/** + * Handler for getting the user scores + */ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); - send.object({ music: await getScores(refid, version) }); + send.object({ music: await getScores(refid) }); }; -const getScores = async (refid: string, version: string, forFriend: boolean = false) => { +/** + * Read the user scores and format them (profile/friend) + * @param refid ID of the user + * @param forFriend If true, format the output for friend request. + */ +const getScores = async (refid: string, forFriend: boolean = false) => { const scoresData = await utils.readScores(refid, version); const result = []; @@ -164,7 +178,7 @@ const getScores = async (refid: string, version: string, forFriend: boolean = fa continue; } - if(forFriend) { + if (forFriend) { result.push(K.ATTR({ music_num: music.toString(), sheet_num: sheet.toString(), @@ -184,6 +198,9 @@ const getScores = async (refid: string, version: string, forFriend: boolean = fa return result; }; +/** + * Handler for saving the scores + */ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -231,7 +248,6 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise * Get/create the profile based on refid * @param refid the profile refid * @param name if defined, create/update the profile with the given name - * @returns */ const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); @@ -244,9 +260,9 @@ const getProfile = async (refid: string, name?: string) => { let myBest = Array(10).fill(-1); const scores = await utils.readScores(refid, version, true); - if(Object.entries(scores.scores).length > 0) { + if (Object.entries(scores.scores).length > 0) { const playCount = new Map(); - for(const key in scores.scores) { + for (const key in scores.scores) { const keyData = key.split(':'); const music = parseInt(keyData[0], 10); playCount.set(music, (playCount.get(music) || 0) + scores.scores[key].cnt); @@ -255,7 +271,7 @@ const getProfile = async (refid: string, name?: string) => { const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { - if(i >= 10) { + if (i >= 10) { break; } myBest[i] = value; @@ -360,6 +376,9 @@ const getProfile = async (refid: string, name?: string) => { return player; } +/** + * Handler for saving the profile + */ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -437,14 +456,17 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +/** + * Handler for sending rivals + */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; const no = parseInt($(data).attr()['no'], -1); const rivals = await utils.readRivals(refid); - if(no < 0 || no >= rivals.rivals.length) { - send.object({result : K.ITEM('s8', 2)}); + if (no < 0 || no >= rivals.rivals.length) { + send.object({ result: K.ITEM('s8', 2) }); return; } @@ -458,7 +480,7 @@ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const result = { @@ -31,7 +31,7 @@ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Pro }; /** - * Create a new profile and send it. + * Handler for new profile */ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -43,7 +43,7 @@ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): P }; /** - * Read a profile and send it. + * Handler for existing profile */ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -59,6 +59,7 @@ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promis */ export const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); + const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; @@ -66,53 +67,11 @@ export const getProfile = async (refid: string, name?: string) => { } // Get Score - let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); - let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + const scores = await getScores(refid); let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); - const scoresData = await utils.readScores(refid, version); - const playCount = new Map(); - for (const key in scoresData.scores) { - const keyData = key.split(':'); - const score = scoresData.scores[key]; - const music = parseInt(keyData[0], 10); - const sheet = parseInt(keyData[1], 10); - - if (music > GAME_MAX_MUSIC_ID) { - continue; - } - if ([0, 1, 2, 3].indexOf(sheet) == -1) { - continue; - } - - const medal = { - 100: 1, - 200: 2, - 300: 3, - 400: 5, - 500: 5, - 600: 6, - 700: 7, - 800: 9, - 900: 10, - 1000: 11, - 1100: 15, - }[score.clear_type]; - clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); - - const hiscore_index = (music * 4) + sheet; - const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); - const hiscore_bit_pos = ((hiscore_index * 17) % 8); - const hiscore_value = score.score << hiscore_bit_pos; - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); - - playCount.set(music, (playCount.get(music) || 0) + score.cnt); - } - let myBest = Array(20).fill(-1); - const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + const sortedPlayCount = new Map([...scores.playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { if (i >= 20) { @@ -129,15 +88,15 @@ export const getProfile = async (refid: string, name?: string) => { staff: K.ITEM('s8', 0), is_conv: K.ITEM('s8', -1), my_best: K.ARRAY('s16', myBest), - clear_medal: K.ARRAY('u16', clear_medal), + clear_medal: K.ARRAY('u16', scores.clear_medal), clear_medal_sub: K.ARRAY('u8', clear_medal_sub), + active_fr_num: K.ITEM('u8', rivals.rivals.length), // TODO: replace with real data total_play_cnt: K.ITEM('s32', 100), today_play_cnt: K.ITEM('s16', 50), consecutive_days: K.ITEM('s16', 365), latest_music: K.ARRAY('s16', [-1, -1, -1]), - active_fr_num: K.ITEM('u8', 0), }, player_card: { // TODO: replace with real data @@ -162,7 +121,7 @@ export const getProfile = async (refid: string, name?: string) => { set_recommend: K.ARRAY('s8', [0, 0, 0]), jewelry: K.ARRAY('s8', Array(15).fill(0)), }, - hiscore: K.ITEM('bin', Buffer.from(hiscore_array)) + hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array)) }; // Add version specific datas @@ -175,7 +134,7 @@ export const getProfile = async (refid: string, name?: string) => { } /** - * Unformat and write the end game data into DB + * Handler for saving profile ans scores */ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; @@ -238,6 +197,91 @@ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promi send.object(result); }; +/** + * Handler for sending rivals + */ +export const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + const rivals = await utils.readRivals(refid); + let result = { + friend: [] + } + + for (const rival of rivals.rivals.slice(0, 2)) { + const profile = await utils.readProfile(rival); + const params = await utils.readParams(rival, version); + + const scores = await getScores(refid); + + result.friend.push({ + open: K.ITEM('s8', 1), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + name: K.ITEM('str', profile.name), + chara: K.ITEM('s16', params.params.chara || -1), + clear_medal: K.ARRAY('u16', scores.clear_medal), + hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array)) + }); + } + + send.object(result); +} + +/** + * Read the user scores and format them + * @param refid ID of the user + */ +const getScores = async (refid: string) => { + let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); + let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + + const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + const medal = { + 100: 1, + 200: 2, + 300: 3, + 400: 5, + 500: 5, + 600: 6, + 700: 7, + 800: 9, + 900: 10, + 1000: 11, + 1100: 15, + }[score.clear_type]; + clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); + + const hiscore_index = (music * 4) + sheet; + const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); + const hiscore_bit_pos = ((hiscore_index * 17) % 8); + const hiscore_value = score.score << hiscore_bit_pos; + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); + } + + return { + hiscore_array, + clear_medal, + playCount + } +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v20'; diff --git a/popn@asphyxia/handler/lapistoria.ts b/popn@asphyxia/handler/lapistoria.ts index aa65e7d..c357a3a 100644 --- a/popn@asphyxia/handler/lapistoria.ts +++ b/popn@asphyxia/handler/lapistoria.ts @@ -12,7 +12,7 @@ export const setRoutes = () => { } /** - * Return info22.common informations (phase, etc...) + * Handler for getting current state of the game. */ const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const result: any = { @@ -39,7 +39,7 @@ const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -51,7 +51,7 @@ const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< }; /** - * Read a profile and send it. + * Handler for existing profile */ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -60,6 +60,9 @@ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.object(await getProfile(refid)); }; +/** + * Handler for saving the scores + */ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -107,7 +110,6 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise * Get/create the profile based on refid * @param refid the profile refid * @param name if defined, create/update the profile with the given name - * @returns */ const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); @@ -280,6 +282,9 @@ const getProfile = async (refid: string, name?: string) => { return player; } +/** + * Handler for saving the profile + */ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -374,14 +379,17 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +/** + * Handler for sending rivals + */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; const no = parseInt($(data).attr()['no'], -1); const rivals = await utils.readRivals(refid); - if(no < 0 || no >= rivals.rivals.length) { - send.object({result : K.ITEM('s8', 2)}); + if (no < 0 || no >= rivals.rivals.length) { + send.object({ result: K.ITEM('s8', 2) }); return; } diff --git a/popn@asphyxia/handler/sunny.ts b/popn@asphyxia/handler/sunny.ts index f73c50c..7dff458 100644 --- a/popn@asphyxia/handler/sunny.ts +++ b/popn@asphyxia/handler/sunny.ts @@ -2,7 +2,7 @@ import { ExtraData } from "../models/common"; import * as utils from "./utils"; /** - * Return the current phases of the game. + * Handler for getting the current state of the game (phase, good prices, etc...) */ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const result = { @@ -19,15 +19,15 @@ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Pro l_matching_sec: K.ITEM('s32', 60), is_check_cpu: K.ITEM('s32', 0), week_no: K.ITEM('s32', 0), - sel_ranking: K.ARRAY('s16', [-1, -1, -1, -1, -1]), - up_ranking: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + sel_ranking: K.ARRAY('s16', Array(10).fill(-1)), + up_ranking: K.ARRAY('s16', Array(10).fill(-1)), }; return send.object(result); }; /** - * Create a new profile and send it. + * Handler for new profile */ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -39,7 +39,7 @@ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): P }; /** - * Read a profile and send it. + * Handler for existing profile */ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -55,6 +55,7 @@ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promis */ export const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); + const rivals = await utils.readRivals(refid); if (name && name.length > 0) { profile.name = name; @@ -62,53 +63,11 @@ export const getProfile = async (refid: string, name?: string) => { } // Get Score - let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); - let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + const scores = await getScores(refid); let clear_medal_sub = Array(GAME_MAX_MUSIC_ID).fill(0); - const scoresData = await utils.readScores(refid, version); - const playCount = new Map(); - for (const key in scoresData.scores) { - const keyData = key.split(':'); - const score = scoresData.scores[key]; - const music = parseInt(keyData[0], 10); - const sheet = parseInt(keyData[1], 10); - - if (music > GAME_MAX_MUSIC_ID) { - continue; - } - if ([0, 1, 2, 3].indexOf(sheet) == -1) { - continue; - } - - const medal = { - 100: 1, - 200: 2, - 300: 3, - 400: 5, - 500: 5, - 600: 6, - 700: 7, - 800: 9, - 900: 10, - 1000: 11, - 1100: 15, - }[score.clear_type]; - clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); - - const hiscore_index = (music * 4) + sheet; - const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); - const hiscore_bit_pos = ((hiscore_index * 17) % 8); - const hiscore_value = score.score << hiscore_bit_pos; - hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); - hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); - hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); - - playCount.set(music, (playCount.get(music) || 0) + score.cnt); - } - let myBest = Array(20).fill(-1); - const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); + const sortedPlayCount = new Map([...scores.playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { if (i >= 20) { @@ -126,15 +85,15 @@ export const getProfile = async (refid: string, name?: string) => { is_conv: K.ITEM('s8', -1), collabo: K.ITEM('u8', 255), my_best: K.ARRAY('s16', myBest), - clear_medal: K.ARRAY('u16', clear_medal), + clear_medal: K.ARRAY('u16', scores.clear_medal), clear_medal_sub: K.ARRAY('u8', clear_medal_sub), + active_fr_num: K.ITEM('u8', rivals.rivals.length), // TODO: replace with real data total_play_cnt: K.ITEM('s32', 100), today_play_cnt: K.ITEM('s16', 50), consecutive_days: K.ITEM('s16', 365), latest_music: K.ARRAY('s16', [-1, -1, -1]), - active_fr_num: K.ITEM('u8', 0), }, netvs: { rank_point: K.ITEM('s32', 0), @@ -154,7 +113,7 @@ export const getProfile = async (refid: string, name?: string) => { set_recommend: K.ARRAY('s8', [0, 0, 0]), netvs_play_cnt: K.ITEM('u8', 0), }, - hiscore: K.ITEM('bin', Buffer.from(hiscore_array)), + hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array)), gakuen_data: { music_list: K.ITEM('s32', -1), }, @@ -215,7 +174,7 @@ export const getProfile = async (refid: string, name?: string) => { } /** - * Unformat and write the end game data into DB + * Handler for saving profile and scores */ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; @@ -273,6 +232,97 @@ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promi send.object(result); }; +/** + * Handler for sending rivals + */ +export const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + const refid = $(data).attr()['ref_id']; + const rivals = await utils.readRivals(refid); + let result = { + friend: [] + } + + for (const rival of rivals.rivals.slice(0, 2)) { + const profile = await utils.readProfile(rival); + const params = await utils.readParams(rival, version); + + const scores = await getScores(rival); + + result.friend.push({ + open: K.ITEM('s8', 1), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + name: K.ITEM('str', profile.name), + chara: K.ITEM('s16', params.params.chara || -1), + hair: K.ITEM('u8', params.params.hair || 0), + face: K.ITEM('u8', params.params.face || 0), + body: K.ITEM('u8', params.params.body || 0), + effect: K.ITEM('u8', params.params.effect || 0), + object: K.ITEM('u8', params.params.object || 0), + comment: K.ARRAY('u8', params.params.comment || [0, 0]), + clear_medal: K.ARRAY('u16', scores.clear_medal), + hiscore: K.ITEM('bin', Buffer.from(scores.hiscore_array)) + }); + } + + send.object(result); +} + +/** + * Read the user scores and format them + * @param refid ID of the user + */ +const getScores = async (refid: string) => { + let hiscore_array = Array(Math.floor((((GAME_MAX_MUSIC_ID * 4) * 17) + 7) / 8)).fill(0); + let clear_medal = Array(GAME_MAX_MUSIC_ID).fill(0); + + const scoresData = await utils.readScores(refid, version); + const playCount = new Map(); + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + + if (music > GAME_MAX_MUSIC_ID) { + continue; + } + if ([0, 1, 2, 3].indexOf(sheet) == -1) { + continue; + } + + const medal = { + 100: 1, + 200: 2, + 300: 3, + 400: 5, + 500: 5, + 600: 6, + 700: 7, + 800: 9, + 900: 10, + 1000: 11, + 1100: 15, + }[score.clear_type]; + clear_medal[music] = clear_medal[music] | (medal << (sheet * 4)); + + const hiscore_index = (music * 4) + sheet; + const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8); + const hiscore_bit_pos = ((hiscore_index * 17) % 8); + const hiscore_value = score.score << hiscore_bit_pos; + hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF); + hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF); + hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF); + + playCount.set(music, (playCount.get(music) || 0) + score.cnt); + } + + return { + hiscore_array, + clear_medal, + playCount + } +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v21'; diff --git a/popn@asphyxia/handler/tunestreet.ts b/popn@asphyxia/handler/tunestreet.ts index efca0fd..f63c937 100644 --- a/popn@asphyxia/handler/tunestreet.ts +++ b/popn@asphyxia/handler/tunestreet.ts @@ -1,5 +1,8 @@ import * as utils from "./utils"; +/** + * Handler for getting the current state of the game (phase, good prices, etc...) + */ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const result = K.ATTR({ game_phase: "2", psp_phase: "2" }); @@ -7,7 +10,7 @@ export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Pro }; /** - * Create a new profile and send it. + * Handler for new profile */ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; @@ -19,7 +22,7 @@ export const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): P }; /** - * Read a profile and send it. + * Handler for existing profile */ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; @@ -29,10 +32,9 @@ export const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promis }; /** - * Get/create the profile based on refid + * Get/create the profile and scores based on refid * @param refid the profile refid * @param name if defined, create/update the profile with the given name - * @returns */ export const getProfile = async (refid: string, name?: string) => { const profile = await utils.readProfile(refid); @@ -185,6 +187,9 @@ const __format_flags_for_score = (sheet: number, clear_type: number) => { return (flags << shift) | playedflag } +/** + * Handler for saving profile and scores + */ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; if (!refid) return send.deny(); @@ -288,6 +293,11 @@ export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promi send.object(result); }; +export const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { + // No rivals support for Tune street :( + send.deny(); +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const version: string = 'v19'; diff --git a/popn@asphyxia/handler/usaneko.ts b/popn@asphyxia/handler/usaneko.ts index 08e4dfe..9164289 100644 --- a/popn@asphyxia/handler/usaneko.ts +++ b/popn@asphyxia/handler/usaneko.ts @@ -14,6 +14,9 @@ export const setRoutes = () => { R.Route(`player24.friend`, friend); } +/** + * Return current state of the game (phase, good prices, etc...) + */ const getInfoCommon = (req: EamuseInfo) => { const result: any = { phase: [], @@ -92,7 +95,7 @@ const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise }; /** - * Create a new profile and send it. + * Handler for new profile */ const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -104,7 +107,7 @@ const newPlayer = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< }; /** - * Read a profile and send it. + * Handler for existing profile */ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); @@ -113,6 +116,9 @@ const read = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.object(await getProfile(refid, getVersion(req))); }; +/** + * Handler fo buying goods with lumina + */ const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -134,13 +140,16 @@ const buy = async (req: EamuseInfo, data: any, send: EamuseSend): Promise = params.params.player_point = lumina - price; await utils.writeParams(refid, version, params); - const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + const achievements = await utils.readAchievements(refid, version, { ...defaultAchievements, version }); achievements.items[`${type}:${id}`] = param; await utils.writeAchievements(refid, version, achievements); } send.success(); }; +/** + * Handler for getting the user scores + */ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); const version = getVersion(req); @@ -149,6 +158,11 @@ const readScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise< send.object({ music: await getScores(refid, version) }); }; +/** + * Read the user scores and format them (profile/friend) + * @param refid ID of the user + * @param forFriend If true, format the output for friend request. + */ const getScores = async (refid: string, version: string, forFriend: boolean = false) => { const scoresData = await utils.readScores(refid, version); const result = []; @@ -179,7 +193,7 @@ const getScores = async (refid: string, version: string, forFriend: boolean = fa continue; } - if(forFriend) { + if (forFriend) { result.push(K.ATTR({ music_num: music.toString(), sheet_num: sheet.toString(), @@ -202,6 +216,9 @@ const getScores = async (refid: string, version: string, forFriend: boolean = fa return result; }; +/** + * Return the rank based on the given score + */ const getRank = (score: number): number => { if (score < 50000) { return 1 @@ -221,6 +238,9 @@ const getRank = (score: number): number => { return 8 } +/** + * Handler for saving the scores + */ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const version = getVersion(req); const refid = $(data).str('ref_id'); @@ -269,7 +289,6 @@ const writeScore = async (req: EamuseInfo, data: any, send: EamuseSend): Promise * Get/create the profile based on refid * @param refid the profile refid * @param name if defined, create/update the profile with the given name - * @returns */ const getProfile = async (refid: string, version: string, name?: string) => { const profile = await utils.readProfile(refid); @@ -282,9 +301,9 @@ const getProfile = async (refid: string, version: string, name?: string) => { let myBest = Array(10).fill(-1); const scores = await utils.readScores(refid, version, true); - if(Object.entries(scores.scores).length > 0) { + if (Object.entries(scores.scores).length > 0) { const playCount = new Map(); - for(const key in scores.scores) { + for (const key in scores.scores) { const keyData = key.split(':'); const music = parseInt(keyData[0], 10); playCount.set(music, (playCount.get(music) || 0) + scores.scores[key].cnt); @@ -293,7 +312,7 @@ const getProfile = async (refid: string, version: string, name?: string) => { const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1])); let i = 0; for (const value of sortedPlayCount.keys()) { - if(i >= 10) { + if (i >= 10) { break; } myBest[i] = value; @@ -384,7 +403,7 @@ const getProfile = async (refid: string, version: string, name?: string) => { stamp: [], }; - const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + const achievements = await utils.readAchievements(refid, version, { ...defaultAchievements, version }); const profileCharas = achievements.charas || {}; for (const chara_id in profileCharas) { @@ -395,8 +414,8 @@ const getProfile = async (refid: string, version: string, name?: string) => { } let profileStamps = achievements.stamps; - if(Object.entries(profileStamps).length == 0) { - profileStamps = {"0": 0 }; + if (Object.entries(profileStamps).length == 0) { + profileStamps = { "0": 0 }; } for (const stamp_id in profileStamps) { player.stamp.push({ @@ -465,6 +484,9 @@ const getProfile = async (refid: string, version: string, name?: string) => { return player; } +/** + * Handler for saving the profile + */ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).str('ref_id'); if (!refid) return send.deny(); @@ -472,7 +494,7 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise const version = getVersion(req); const params = await utils.readParams(refid, version); - const achievements = await utils.readAchievements(refid, version, {...defaultAchievements, version}); + const achievements = await utils.readAchievements(refid, version, { ...defaultAchievements, version }); utils.getExtraData(data, params, EXTRA_DATA); @@ -611,6 +633,9 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise send.success(); }; +/** + * Handler for sending rivals + */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; const no = parseInt($(data).attr()['no'], -1); @@ -618,8 +643,8 @@ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise= rivals.rivals.length) { - send.object({result : K.ITEM('s8', 2)}); + if (no < 0 || no >= rivals.rivals.length) { + send.object({ result: K.ITEM('s8', 2) }); return; } @@ -633,7 +658,7 @@ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise { - const rivals = await DB.FindOne(data.refid, { collection: 'rivals' }) || {collection: 'rivals', rivals: []}; + const rivals = await DB.FindOne(data.refid, { collection: 'rivals' }) || { collection: 'rivals', rivals: [] }; const idx = rivals.rivals.indexOf(data.rivalid); - if(idx >= 0) { + if (idx >= 0) { rivals.rivals.splice(idx, 1); await DB.Update(data.refid, { collection: 'rivals' }, rivals); } @@ -48,10 +48,10 @@ export function register() { R.WebUIEvent('addRival', async (data: any) => { const refid = data.refid.trim(); - const profile = await DB.FindOne(data.rivalid, { collection: 'profile'}); - if(profile != undefined && profile != null) { - const rivals = await DB.FindOne(refid, { collection: 'rivals' }) || {collection: 'rivals', rivals: []}; - if(rivals.rivals.length < 4) { + const profile = await DB.FindOne(data.rivalid, { collection: 'profile' }); + if (profile != undefined && profile != null) { + const rivals = await DB.FindOne(refid, { collection: 'rivals' }) || { collection: 'rivals', rivals: [] }; + if (rivals.rivals.length < 4) { rivals.rivals.push(data.rivalid); await DB.Upsert(refid, { collection: 'rivals' }, rivals); } @@ -64,6 +64,7 @@ export function register() { R.Route(`playerdata.conversion`, async (req, data, send) => getVersion(req).newPlayer(req, data, send)); R.Route(`playerdata.get`, async (req, data, send) => getVersion(req).read(req, data, send)); R.Route(`playerdata.set`, async (req, data, send) => getVersion(req).write(req, data, send)); + R.Route(`playerdata.friend`, async (req, data, send) => getVersion(req).friend(req, data, send)); // For Pnm >= 22, each game set his own route lapistoria.setRoutes(); From b9257ce08a1c60d1014645d50070b726f0c71122 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Mon, 19 Apr 2021 23:09:55 +0200 Subject: [PATCH 14/16] Fix loading of rivals scores --- popn@asphyxia/handler/eclale.ts | 2 +- popn@asphyxia/handler/lapistoria.ts | 2 +- popn@asphyxia/handler/usaneko.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/popn@asphyxia/handler/eclale.ts b/popn@asphyxia/handler/eclale.ts index 4eb23ef..7540fed 100644 --- a/popn@asphyxia/handler/eclale.ts +++ b/popn@asphyxia/handler/eclale.ts @@ -461,7 +461,7 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; - const no = parseInt($(data).attr()['no'], -1); + const no = parseInt($(data).attr()['no'], 10); const rivals = await utils.readRivals(refid); diff --git a/popn@asphyxia/handler/lapistoria.ts b/popn@asphyxia/handler/lapistoria.ts index c357a3a..659a298 100644 --- a/popn@asphyxia/handler/lapistoria.ts +++ b/popn@asphyxia/handler/lapistoria.ts @@ -384,7 +384,7 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; - const no = parseInt($(data).attr()['no'], -1); + const no = parseInt($(data).attr()['no'], 10); const rivals = await utils.readRivals(refid); diff --git a/popn@asphyxia/handler/usaneko.ts b/popn@asphyxia/handler/usaneko.ts index 9164289..95a5a4e 100644 --- a/popn@asphyxia/handler/usaneko.ts +++ b/popn@asphyxia/handler/usaneko.ts @@ -638,7 +638,7 @@ const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise */ const friend = async (req: EamuseInfo, data: any, send: EamuseSend): Promise => { const refid = $(data).attr()['ref_id']; - const no = parseInt($(data).attr()['no'], -1); + const no = parseInt($(data).attr()['no'], 10); const version = getVersion(req); const rivals = await utils.readRivals(refid); From 0c925ca0e07fefd08b7f4732ce9cfcec9a1be939 Mon Sep 17 00:00:00 2001 From: Kirito Date: Tue, 20 Apr 2021 13:12:38 +0900 Subject: [PATCH 15/16] Implement eventlog.write --- .gitignore | 1 + sdvx@asphyxia/index.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5f51ed9..7439c72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Editor configs .vscode +.idea # External modules node_modules diff --git a/sdvx@asphyxia/index.ts b/sdvx@asphyxia/index.ts index 31a5dd1..a34cb75 100644 --- a/sdvx@asphyxia/index.ts +++ b/sdvx@asphyxia/index.ts @@ -61,7 +61,12 @@ export function register() { MultiRoute('entry_s', true); MultiRoute('entry_e', true); MultiRoute('exception', true); - R.Route('eventlog.write', true); + R.Route('eventlog.write', (_, __, send) => send.object({ + gamesession: K.ITEM('s64', 1n), + logsendflg: K.ITEM('s32', 0), + logerrlevel: K.ITEM('s32', 0), + evtidnosendflg: K.ITEM('s32', 0) + })); R.Unhandled(); } From d1eb39e9444bbbcfcc288cf2fa2f5222b4392a98 Mon Sep 17 00:00:00 2001 From: Kirito Date: Tue, 20 Apr 2021 22:46:02 +0900 Subject: [PATCH 16/16] Booth support --- sdvx@asphyxia/README.md | 1 + sdvx@asphyxia/handlers/common.ts | 10 +- sdvx@asphyxia/handlers/features.ts | 48 +++++-- sdvx@asphyxia/handlers/profiles.ts | 167 ++++++++++++++++++----- sdvx@asphyxia/index.ts | 13 +- sdvx@asphyxia/models/profile.ts | 5 + sdvx@asphyxia/templates/booth/common.pug | 16 +++ sdvx@asphyxia/templates/booth/load.pug | 23 ++++ sdvx@asphyxia/utils.ts | 11 +- 9 files changed, 246 insertions(+), 48 deletions(-) create mode 100644 sdvx@asphyxia/templates/booth/common.pug create mode 100644 sdvx@asphyxia/templates/booth/load.pug diff --git a/sdvx@asphyxia/README.md b/sdvx@asphyxia/README.md index ca4cb63..1c0c309 100644 --- a/sdvx@asphyxia/README.md +++ b/sdvx@asphyxia/README.md @@ -4,5 +4,6 @@ Plugin Version: **v1.1** Supported Versions: +- BOOTH - HEAVENLY HAVEN - VIVID WAVE diff --git a/sdvx@asphyxia/handlers/common.ts b/sdvx@asphyxia/handlers/common.ts index da45335..a5b4d0b 100644 --- a/sdvx@asphyxia/handlers/common.ts +++ b/sdvx@asphyxia/handlers/common.ts @@ -6,6 +6,12 @@ export const common: EPR = async (info, data, send) => { let courses = []; let extend = []; + const version = parseInt(info.model.split(":")[4]); + + if (version <= 2013052900) { + return send.pugFile('templates/booth/common.pug'); + } + switch (info.method) { case 'sv4_common': { events = EVENT4; @@ -26,13 +32,13 @@ export const common: EPR = async (info, data, send) => { if (U.GetConfig('unlock_all_songs')) { for (let i = 1; i < 1700; ++i) { for (let j = 0; j < 5; ++j) { - + songs.push({ music_id: K.ITEM('s32', i), music_type: K.ITEM('u8', j), limited: K.ITEM('u8', 3), }); - + } } } diff --git a/sdvx@asphyxia/handlers/features.ts b/sdvx@asphyxia/handlers/features.ts index 371f8e2..fe5abec 100644 --- a/sdvx@asphyxia/handlers/features.ts +++ b/sdvx@asphyxia/handlers/features.ts @@ -1,17 +1,46 @@ -import { Profile } from '../models/profile'; -import { MusicRecord } from '../models/music_record'; -import { IDToCode, GetCounter } from '../utils'; -import { Mix } from '../models/mix'; +import {Profile} from '../models/profile'; +import {MusicRecord} from '../models/music_record'; +import {getVersion, IDToCode, GetCounter} from '../utils'; +import {Mix} from '../models/mix'; export const hiscore: EPR = async (info, data, send) => { const records = await DB.Find(null, { collection: 'music' }); + const version = getVersion(info); + const profiles = _.groupBy( await DB.Find(null, { collection: 'profile' }), '__refid' ); - send.object({ + if (version === 1) { + return send.object({ + hiscore: K.ATTR({ type: "1" }, { + music: _.map( + _.groupBy(records, r => { + return `${r.mid}:${r.type}`; + }), + r => _.maxBy(r, 'score') + ).map(r => (K.ATTR({ id: String(r.mid) }, { + note: (() => { + const notes = []; + + for (let i = 1; i <= 3; i++) { + if (r.type !== i) continue; + notes.push(K.ATTR({ type: String(r.type) }, { + name: K.ITEM('str', profiles[r.__refid][0].name), + score: K.ITEM('u32', r.score) + })) + } + + return notes; + })() + }))), + }) + }) + } + + return send.object({ sc: { d: _.map( _.groupBy(records, r => { @@ -40,7 +69,7 @@ export const rival: EPR = async (info, data, send) => { await DB.Find(null, { collection: 'profile' }) ).filter(p => p.__refid != refid); - send.object({ + return send.object({ rival: await Promise.all( rivals.map(async (p, index) => { return { @@ -84,7 +113,7 @@ export const saveMix: EPR = async (info, data, send) => { jacket: mix.number('jacket_id'), }); - send.object({ + return send.object({ automation: { mix_id: K.ITEM('s32', id), mix_code: K.ITEM('str', doc.code), @@ -105,11 +134,10 @@ export const loadMix: EPR = async (info, data, send) => { const mix = await DB.FindOne({ collection: 'mix', code }); if (!mix) { - send.object({ result: K.ITEM('s32', 1) }); - return; + return send.object({ result: K.ITEM('s32', 1) }); } - send.object({ + return send.object({ automation: { mix_id: K.ITEM('s32', mix.id), mix_code: K.ITEM('str', mix.code), diff --git a/sdvx@asphyxia/handlers/profiles.ts b/sdvx@asphyxia/handlers/profiles.ts index 217fbb2..3e55e7d 100644 --- a/sdvx@asphyxia/handlers/profiles.ts +++ b/sdvx@asphyxia/handlers/profiles.ts @@ -1,18 +1,12 @@ -import { Skill } from '../models/skill'; -import { SDVX_AUTOMATION_SONGS } from '../data/vvw'; -import { Item } from '../models/item'; -import { Param } from '../models/param'; -import { MusicRecord } from '../models/music_record'; -import { CourseRecord } from '../models/course_record'; -import { Profile } from '../models/profile'; -import { IDToCode } from '../utils'; -import { Mix } from '../models/mix'; - -function getVersion(info: EamuseInfo) { - if (info.method.startsWith('sv4')) return 4; - if (info.method.startsWith('sv5')) return 5; - return 0; -} +import {Skill} from '../models/skill'; +import {SDVX_AUTOMATION_SONGS} from '../data/vvw'; +import {Item} from '../models/item'; +import {Param} from '../models/param'; +import {MusicRecord} from '../models/music_record'; +import {CourseRecord} from '../models/course_record'; +import {Profile} from '../models/profile'; +import {getVersion, IDToCode} from '../utils'; +import {Mix} from '../models/mix'; async function getAutomationMixes(params: Param[]) { const mixids = params @@ -30,12 +24,37 @@ function unlockNavigators(items: Partial[]) { } export const loadScore: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); const records = await DB.Find(refid, { collection: 'music' }); - send.object({ + const version = getVersion(info); + + if (version === 1) { + return send.object({ + music: records.map(r => (K.ATTR({ music_id: String(r.mid) }, { + type: (() => { + const records = []; + + for (let i = 1; i <= 3; i++) { + if (r.type != i) continue; + records.push(K.ATTR({ + type_id: String(i), + score: String(r.score), + clear_type: String(r.clear), + score_grade: String(r.grade), + cnt: "0" + })); + } + + return records; + })() + }))) + }); + } + + return send.object({ music: { info: records.map(r => ({ param: K.ARRAY('u32', [ @@ -62,9 +81,57 @@ export const loadScore: EPR = async (info, data, send) => { }; export const saveScore: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); + const version = getVersion(info); + + // Booth - Save score + if (version === 1) { + try { + const mid = parseInt($(data).attr().music_id); + const type = parseInt($(data).attr().music_type); + + if (_.isNil(mid) || _.isNil(type)) return send.deny(); + + const record = (await DB.FindOne(refid, { + collection: 'music', + mid, + type, + })) || { + collection: 'music', + mid, + type, + score: 0, + clear: 0, + grade: 0, + buttonRate: 0, + longRate: 0, + volRate: 0, + }; + + const score = $(data).attr().score ? parseInt($(data).attr().score) : 0; + const clear = $(data).attr().clear_type ? parseInt($(data).attr().clear_type) : 0; + const grade = $(data).attr().score_grade ? parseInt($(data).attr().score_grade) : 0; + if (score > record.score) { + record.score = score; + } + + record.clear = Math.max(clear, record.clear); + record.grade = Math.max(grade, record.grade); + + await DB.Upsert( + refid, + { collection: 'music', mid, type }, + record + ); + + return send.success(); + } catch { + return send.deny(); + } + } + const mid = $(data).number('music_id'); const type = $(data).number('music_type'); @@ -103,7 +170,7 @@ export const saveScore: EPR = async (info, data, send) => { record ); - send.success(); + return send.success(); }; export const saveCourse: EPR = async (info, data, send) => { @@ -134,16 +201,47 @@ export const saveCourse: EPR = async (info, data, send) => { } ); - send.success(); + return send.success(); }; export const save: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().refid); if (!refid) return send.deny(); const version = getVersion(info); if (version == 0) return send.deny(); + if (version === 1) { + try { + // Save Profile + await DB.Update( + refid, + { collection: 'profile' }, + { + $set: { + headphone: $(data).number('headphone'), + hiSpeed: $(data).number('hispeed'), + appeal: $(data).number('appeal_id'), + boothFrame: [$(data).number('frame0'), $(data).number('frame1'), $(data).number('frame2'), $(data).number('frame3'), $(data).number('frame4')], + musicID: parseInt($(data).attr("last").music_id), + musicType: parseInt($(data).attr("last").music_type), + sortType: parseInt($(data).attr("last").sort_type), + mUserCnt: $(data).number('m_user_cnt'), + }, + $inc: { + expPoint: $(data).number('gain_exp'), + packets: $(data).number('earned_gamecoin_packet'), + blocks: $(data).number('earned_gamecoin_block'), + }, + } + ); + + return send.success(); + } catch { + return send.deny(); + } + } + // Save Profile await DB.Update( refid, @@ -226,11 +324,11 @@ export const save: EPR = async (info, data, send) => { } ); - send.success(); + return send.success(); }; export const load: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().dataid); if (!refid) return send.deny(); const version = getVersion(info); @@ -241,8 +339,8 @@ export const load: EPR = async (info, data, send) => { }); if (!profile) { - send.object({ result: K.ITEM('u8', 1) }); - return; + if (version === 1) return send.object(K.ATTR({ none: "1" })); + return send.object({ result: K.ITEM('u8', 1) }); } let skill = (await DB.FindOne(refid, { @@ -263,7 +361,11 @@ export const load: EPR = async (info, data, send) => { const currentTime = time.getTime(); const mixes = version == 5 ? await getAutomationMixes(params) : []; - send.pugFile('templates/load.pug', { + if (version === 1) { + return send.pugFile('templates/booth/load.pug', { code: IDToCode(profile.id), ...profile }); + } + + return send.pugFile('templates/load.pug', { courses, items: U.GetConfig('unlock_all_navigators') ? unlockNavigators(items) @@ -279,10 +381,10 @@ export const load: EPR = async (info, data, send) => { }; export const create: EPR = async (info, data, send) => { - const refid = $(data).str('refid'); + const refid = $(data).str('refid', $(data).attr().refid); if (!refid) return send.deny(); - const name = $(data).str('name', 'GUEST'); + const name = $(data).str('name', $(data).attr().name ? $(data).attr().name : 'GUEST'); let id = _.random(0, 99999999); while (await DB.FindOne(null, { collecttion: 'profile', id })) { id = _.random(0, 99999999); @@ -314,10 +416,13 @@ export const create: EPR = async (info, data, send) => { musicID: 0, musicType: 0, sortType: 0, + expPoint: 0, + mUserCnt: 0, + boothFrame: [0, 0, 0, 0, 0] }; await DB.Upsert(refid, { collection: 'profile' }, profile); - send.object({ result: K.ITEM('u8', 0) }); + return send.object({ result: K.ITEM('u8', 0) }); }; export const buy: EPR = async (info, data, send) => { @@ -356,11 +461,11 @@ export const buy: EPR = async (info, data, send) => { ); } - send.object({ + return send.object({ gamecoin_packet: K.ITEM('u32', updated.docs[0].packets), gamecoin_block: K.ITEM('u32', updated.docs[0].blocks), }); } else { - send.success(); + return send.success(); } }; diff --git a/sdvx@asphyxia/index.ts b/sdvx@asphyxia/index.ts index a34cb75..0e23872 100644 --- a/sdvx@asphyxia/index.ts +++ b/sdvx@asphyxia/index.ts @@ -1,5 +1,5 @@ -import { common } from './handlers/common'; -import { hiscore, rival, saveMix, loadMix } from './handlers/features'; +import {common} from './handlers/common'; +import {hiscore, rival, saveMix, loadMix} from './handlers/features'; import { updateProfile, updateMix, @@ -29,6 +29,7 @@ export function register() { const MultiRoute = (method: string, handler: EPR | boolean) => { // Helper for register multiple versions. + R.Route(`game.${method}`, handler); R.Route(`game.sv4_${method}`, handler); R.Route(`game.sv5_${method}`, handler); }; @@ -53,8 +54,12 @@ export function register() { MultiRoute('load_ap', loadMix); // Lazy - MultiRoute('lounge', false); - MultiRoute('shop', true); + MultiRoute('lounge', (_, __, send) => send.object({ + interval: K.ITEM('u32', 30) + })); + MultiRoute('shop', (_, __, send) => send.object({ + nxt_time: K.ITEM('u32', 1000 * 5 * 60) + })); MultiRoute('save_e', true); MultiRoute('play_e', true); MultiRoute('play_s', true); diff --git a/sdvx@asphyxia/models/profile.ts b/sdvx@asphyxia/models/profile.ts index 045fb67..e10e995 100644 --- a/sdvx@asphyxia/models/profile.ts +++ b/sdvx@asphyxia/models/profile.ts @@ -11,6 +11,9 @@ export interface Profile { packets: number; blocks: number; + expPoint: number; + mUserCnt: number; + musicID: number; musicType: number; sortType: number; @@ -28,4 +31,6 @@ export interface Profile { effCLeft: number; effCRight: number; narrowDown: number; + + boothFrame: number[]; } diff --git a/sdvx@asphyxia/templates/booth/common.pug b/sdvx@asphyxia/templates/booth/common.pug new file mode 100644 index 0000000..6882290 --- /dev/null +++ b/sdvx@asphyxia/templates/booth/common.pug @@ -0,0 +1,16 @@ +- let music = 0; +- let event = 0; +- let catalog = 0; + +game + limited + while music < 200 + music(id=music++, flag=2) + + event + while event < 16 + info(id=event++) + + catalog + while catalog < 256 + info(id=catalog++, currency=1, price=1) diff --git a/sdvx@asphyxia/templates/booth/load.pug b/sdvx@asphyxia/templates/booth/load.pug new file mode 100644 index 0000000..3562737 --- /dev/null +++ b/sdvx@asphyxia/templates/booth/load.pug @@ -0,0 +1,23 @@ +game + name(__type="str") #{name} + code(__type="str") #{code} + gamecoin_packet(__type="u32") #{packets} + gamecoin_block(__type="u32") #{blocks} + exp_point(__type="u32") #{expPoint ? expPoint : 0} + m_user_cnt(__type="u32") #{mUserCnt ? mUserCnt : 0} + have_item(__type="bool" __count=512) #{Array(512).fill(1).join(" ")} + have_note(__type="bool" __count=512) #{Array(512).fill(1).join(" ")} + + last( + music_id=musicID, + music_type=musicType, + sort_type=sortType, + headphone=headphone, + hispeed=hiSpeed, + appeal_id=appeal, + frame0=boothFrame ? boothFrame[0] : 0, + frame1=boothFrame ? boothFrame[1] : 0, + frame2=boothFrame ? boothFrame[2] : 0, + frame3=boothFrame ? boothFrame[3] : 0, + frame4=boothFrame ? boothFrame[4] : 0, + ) diff --git a/sdvx@asphyxia/utils.ts b/sdvx@asphyxia/utils.ts index 3fd5e88..93010c7 100644 --- a/sdvx@asphyxia/utils.ts +++ b/sdvx@asphyxia/utils.ts @@ -1,4 +1,5 @@ -import { Counter } from './models/counter'; +import {Counter} from './models/counter'; + export function IDToCode(id: number) { const padded = _.padStart(id.toString(), 8); return `${padded.slice(0, 4)}-${padded.slice(4)}`; @@ -12,3 +13,11 @@ export async function GetCounter(key: string) { ) ).docs[0].value; } + +export function getVersion(info: EamuseInfo) { + const dateCode = parseInt(info.model.split(":")[4]); + if (dateCode <= 2013052900) return 1; + if (info.method.startsWith('sv4')) return 4; + if (info.method.startsWith('sv5')) return 5; + return 0; +}