From f72f723b1772f06d44d07864b8cbe414ebb27d95 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Tue, 15 Dec 2020 20:03:50 +0100 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 ad677b4c2b0684ded99385eff1e71fdeea3b2632 Mon Sep 17 00:00:00 2001 From: cracrayol Date: Fri, 9 Apr 2021 23:08:37 +0200 Subject: [PATCH 5/8] 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 6/8] 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 7/8] 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 8/8] 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; }