mirror of
https://github.com/asphyxia-core/plugins.git
synced 2026-03-21 17:34:46 -05:00
Merge pull request #16 from cracrayol/stable
Updated Pop'n Music plugin
This commit is contained in:
commit
b90427387d
|
|
@ -1,13 +1,32 @@
|
|||
# Pop'n Music
|
||||
|
||||
Plugin Version: **v1.1.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
|
||||
|
||||
Important : require minimum Asphyxia Core **v1.31**
|
||||
|
||||
## 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
|
||||
* Net Taisen disabled on 24/25 (code not implemented)
|
||||
|
||||
#### 1.1.0
|
||||
Update phase data : All versions are on latest phase.
|
||||
|
||||
|
|
@ -20,4 +39,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.
|
||||
* 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)
|
||||
|
|
@ -1,322 +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 },
|
||||
{ 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 },
|
||||
{ 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') {
|
||||
if(req.model == 'M39:J:A:A:2020092800') {
|
||||
return getInfo24(PHASE25);
|
||||
} 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 },
|
||||
},
|
||||
};
|
||||
513
popn@asphyxia/handler/eclale.ts
Normal file
513
popn@asphyxia/handler/eclale.ts
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
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<any> => {
|
||||
return send.object(getInfoCommon(req));
|
||||
}
|
||||
|
||||
const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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 = <AchievementsEclale>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<any> => {
|
||||
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<any> => {
|
||||
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 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: {
|
||||
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', myBest),
|
||||
latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]),
|
||||
active_fr_num: K.ITEM('u8', 0),
|
||||
},
|
||||
netvs: {
|
||||
record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]),
|
||||
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 = <AchievementsEclale>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<any> => {
|
||||
const refid = $(data).str('ref_id');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const params = await utils.readParams(refid, version);
|
||||
const achievements = <AchievementsEclale>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 },
|
||||
}
|
||||
292
popn@asphyxia/handler/fantasia.ts
Normal file
292
popn@asphyxia/handler/fantasia.ts
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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);
|
||||
const playCount = new Map();
|
||||
for (const key in scoresData.scores) {
|
||||
const keyData = key.split(':');
|
||||
const score = scoresData.scores[key];
|
||||
const music = parseInt(keyData[0], 10);
|
||||
const sheet = parseInt(keyData[1], 10);
|
||||
|
||||
if (music > GAME_MAX_MUSIC_ID) {
|
||||
continue;
|
||||
}
|
||||
if ([0, 1, 2, 3].indexOf(sheet) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const medal = {
|
||||
100: 1,
|
||||
200: 2,
|
||||
300: 3,
|
||||
400: 5,
|
||||
500: 5,
|
||||
600: 6,
|
||||
700: 7,
|
||||
800: 9,
|
||||
900: 10,
|
||||
1000: 11,
|
||||
1100: 15,
|
||||
}[score.clear_type];
|
||||
clear_medal[music] = clear_medal[music] | (medal << (sheet * 4));
|
||||
|
||||
const hiscore_index = (music * 4) + sheet;
|
||||
const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8);
|
||||
const hiscore_bit_pos = ((hiscore_index * 17) % 8);
|
||||
const hiscore_value = score.score << hiscore_bit_pos;
|
||||
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF);
|
||||
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF);
|
||||
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF);
|
||||
|
||||
playCount.set(music, (playCount.get(music) || 0) + score.cnt);
|
||||
}
|
||||
|
||||
let myBest = Array(20).fill(-1);
|
||||
const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1]));
|
||||
let i = 0;
|
||||
for (const value of sortedPlayCount.keys()) {
|
||||
if (i >= 20) {
|
||||
break;
|
||||
}
|
||||
myBest[i] = value;
|
||||
i++;
|
||||
}
|
||||
|
||||
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),
|
||||
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),
|
||||
latest_music: K.ARRAY('s16', [-1, -1, -1]),
|
||||
active_fr_num: K.ITEM('u8', 0),
|
||||
},
|
||||
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);
|
||||
|
||||
player.player_card.best_music = K.ARRAY('s16', myBest.slice(0, 3));
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unformat and write the end game data into DB
|
||||
*/
|
||||
export const write = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
|
||||
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 },
|
||||
}
|
||||
455
popn@asphyxia/handler/lapistoria.ts
Normal file
455
popn@asphyxia/handler/lapistoria.ts
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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),
|
||||
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);
|
||||
const playCount = new Map();
|
||||
for (const key in scoresData.scores) {
|
||||
const keyData = key.split(':');
|
||||
const score = scoresData.scores[key];
|
||||
const music = parseInt(keyData[0], 10);
|
||||
const sheet = parseInt(keyData[1], 10);
|
||||
|
||||
if (music > GAME_MAX_MUSIC_ID) {
|
||||
continue;
|
||||
}
|
||||
if ([0, 1, 2, 3].indexOf(sheet) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
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 = <AchievementsLapistoria>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<any> => {
|
||||
const refid = $(data).str('ref_id');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const params = await utils.readParams(refid, version);
|
||||
const achievements = <AchievementsLapistoria>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 },
|
||||
}
|
||||
|
|
@ -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<Profile> = {};
|
||||
|
||||
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<Profile> {
|
||||
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile'} )
|
||||
return profile || defaultProfile
|
||||
}
|
||||
|
||||
async function writeProfile(refid: string, profile: Profile) {
|
||||
await DB.Upsert<Profile>(refid, { collection: 'profile'}, profile)
|
||||
}
|
||||
|
||||
async function readScores(refid: string): Promise<Scores> {
|
||||
const score = await DB.FindOne<Scores>(refid, { collection: 'scores'} )
|
||||
return score || { collection: 'scores', scores: {}}
|
||||
}
|
||||
|
||||
async function writeScores(refid: string, scores: Scores) {
|
||||
await DB.Upsert<Scores>(refid, { collection: 'scores'}, scores)
|
||||
}
|
||||
335
popn@asphyxia/handler/sunny.ts
Normal file
335
popn@asphyxia/handler/sunny.ts
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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);
|
||||
const playCount = new Map();
|
||||
for (const key in scoresData.scores) {
|
||||
const keyData = key.split(':');
|
||||
const score = scoresData.scores[key];
|
||||
const music = parseInt(keyData[0], 10);
|
||||
const sheet = parseInt(keyData[1], 10);
|
||||
|
||||
if (music > GAME_MAX_MUSIC_ID) {
|
||||
continue;
|
||||
}
|
||||
if ([0, 1, 2, 3].indexOf(sheet) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const medal = {
|
||||
100: 1,
|
||||
200: 2,
|
||||
300: 3,
|
||||
400: 5,
|
||||
500: 5,
|
||||
600: 6,
|
||||
700: 7,
|
||||
800: 9,
|
||||
900: 10,
|
||||
1000: 11,
|
||||
1100: 15,
|
||||
}[score.clear_type];
|
||||
clear_medal[music] = clear_medal[music] | (medal << (sheet * 4));
|
||||
|
||||
const hiscore_index = (music * 4) + sheet;
|
||||
const hiscore_byte_pos = Math.floor((hiscore_index * 17) / 8);
|
||||
const hiscore_bit_pos = ((hiscore_index * 17) % 8);
|
||||
const hiscore_value = score.score << hiscore_bit_pos;
|
||||
hiscore_array[hiscore_byte_pos] = hiscore_array[hiscore_byte_pos] | (hiscore_value & 0xFF);
|
||||
hiscore_array[hiscore_byte_pos + 1] = hiscore_array[hiscore_byte_pos + 1] | ((hiscore_value >> 8) & 0xFF);
|
||||
hiscore_array[hiscore_byte_pos + 2] = hiscore_array[hiscore_byte_pos + 2] | ((hiscore_value >> 16) & 0xFF);
|
||||
|
||||
playCount.set(music, (playCount.get(music) || 0) + score.cnt);
|
||||
}
|
||||
|
||||
let myBest = Array(20).fill(-1);
|
||||
const sortedPlayCount = new Map([...playCount.entries()].sort((a, b) => b[1] - a[1]));
|
||||
let i = 0;
|
||||
for (const value of sortedPlayCount.keys()) {
|
||||
if (i >= 20) {
|
||||
break;
|
||||
}
|
||||
myBest[i] = value;
|
||||
i++;
|
||||
}
|
||||
|
||||
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),
|
||||
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),
|
||||
latest_music: K.ARRAY('s16', [-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('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<any> => {
|
||||
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 },
|
||||
}
|
||||
294
popn@asphyxia/handler/tunestreet.ts
Normal file
294
popn@asphyxia/handler/tunestreet.ts
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import * as utils from "./utils";
|
||||
|
||||
export const getInfo = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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 = 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] = {
|
||||
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);
|
||||
const playCount = new Map();
|
||||
for (const key in scoresData.scores) {
|
||||
const keyData = key.split(':');
|
||||
const score = scoresData.scores[key];
|
||||
const music = parseInt(keyData[0], 10);
|
||||
const sheet = parseInt(keyData[1], 10);
|
||||
|
||||
if (music > GAME_MAX_MUSIC_ID) {
|
||||
continue;
|
||||
}
|
||||
if (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);
|
||||
|
||||
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 = {
|
||||
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<any> => {
|
||||
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;
|
||||
738
popn@asphyxia/handler/usaneko.ts
Normal file
738
popn@asphyxia/handler/usaneko.ts
Normal file
|
|
@ -0,0 +1,738 @@
|
|||
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<any> => {
|
||||
return send.object(getInfoCommon(req));
|
||||
}
|
||||
|
||||
const start = async (req: EamuseInfo, data: any, send: EamuseSend): Promise<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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 = <AchievementsUsaneko>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<any> => {
|
||||
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<any> => {
|
||||
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);
|
||||
}
|
||||
|
||||
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: {
|
||||
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)),
|
||||
my_best: K.ARRAY('s16', myBest),
|
||||
|
||||
// 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),
|
||||
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 achievements = <AchievementsUsaneko>await utils.readAchievements(refid, version, {...defaultAchievements, version});
|
||||
|
||||
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<any> => {
|
||||
const refid = $(data).str('ref_id');
|
||||
if (!refid) return send.deny();
|
||||
|
||||
const version = getVersion(req);
|
||||
|
||||
const params = await utils.readParams(refid, version);
|
||||
const achievements = <AchievementsUsaneko>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 },
|
||||
}
|
||||
|
|
@ -1,4 +1,218 @@
|
|||
export const getVersion = (info: EamuseInfo) => {
|
||||
const moduleName: string = info.module;
|
||||
return `v${moduleName.match(/[0-9]+/)[0]}`;
|
||||
};
|
||||
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<Profile> => {
|
||||
const profile = await DB.FindOne<Profile>(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<Profile>(refid, { collection: 'profile' }, profile);
|
||||
}
|
||||
|
||||
export const readParams = async (refid: string, version: string): Promise<Params> => {
|
||||
const params = await DB.FindOne<Params>(refid, { collection: 'params', version });
|
||||
return params || { collection: 'params', version, params: {} };
|
||||
}
|
||||
|
||||
export const writeParams = async (refid: string, version: string, params: Params) => {
|
||||
await DB.Upsert<Params>(refid, { collection: 'params', version }, params);
|
||||
}
|
||||
|
||||
export const readScores = async (refid: string, version: string, forceVersion: boolean = false): Promise<Scores> => {
|
||||
if (forceVersion || !U.GetConfig("enable_score_sharing")) {
|
||||
const score = await DB.FindOne<Scores>(refid, { collection: 'scores', version });
|
||||
return score || { collection: 'scores', version, scores: {} };
|
||||
} else {
|
||||
let retScore = <Scores>{ collection: 'scores', version, scores: {} };
|
||||
const scores = await DB.Find<Scores>(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<Scores>(refid, { collection: 'scores', version }, scores);
|
||||
}
|
||||
|
||||
export const readAchievements = async (refid: string, version: string, defaultValue: Achievements): Promise<Achievements> => {
|
||||
const achievements = await DB.FindOne<Achievements>(refid, { collection: 'achievements', version });
|
||||
return achievements || defaultValue;
|
||||
}
|
||||
|
||||
export const writeAchievements = async (refid: string, version: string, achievements: Achievements) => {
|
||||
await DB.Upsert<Achievements>(refid, { collection: 'achievements', version }, achievements);
|
||||
}
|
||||
|
||||
const doConvert = async (profile: ProfileDoc<any>): Promise<ProfileDoc<Profile>> => {
|
||||
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<Achievements>(profile.__refid, { collection: 'achievements', version });
|
||||
if (nbAchievements == 0) {
|
||||
await DB.Insert<Achievements>(profile.__refid, achievements[version]);
|
||||
}
|
||||
}
|
||||
|
||||
// Write extras/params
|
||||
if (profile.extras !== undefined) {
|
||||
for (let version in profile.extras) {
|
||||
const nbParams = await DB.Count<Params>(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<Params>(profile.__refid, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update profile
|
||||
const newProfile = await (await DB.Upsert<Profile>(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<any>(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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,33 +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_score_sharing", {
|
||||
name: "Score sharing",
|
||||
desc: "Enable sharing scores between versions",
|
||||
type: "boolean",
|
||||
default: true,
|
||||
});
|
||||
|
||||
R.WebUIEvent('importPnmData', importPnmData);
|
||||
|
||||
const PlayerRoute = (method: string, handler: EPR | boolean) => {
|
||||
R.Route(`player24.${method}`, handler);
|
||||
R.Route(`player23.${method}`, handler);
|
||||
};
|
||||
|
||||
const CommonRoute = (method: string, handler: EPR | boolean) => {
|
||||
R.Route(`info24.${method}`, handler);
|
||||
R.Route(`info23.${method}`, handler);
|
||||
};
|
||||
|
||||
// Common
|
||||
CommonRoute('common', (req, data, send) => {
|
||||
return send.object(getInfo(req));
|
||||
R.WebUIEvent('updatePnmPlayerInfo', async (data: any) => {
|
||||
await DB.Update(data.refid, { collection: 'profile' }, { $set: { name: data.name } });
|
||||
});
|
||||
|
||||
// Player
|
||||
PlayerRoute('new', newPlayer);
|
||||
PlayerRoute('read', read);
|
||||
PlayerRoute('read_score', readScore);
|
||||
PlayerRoute('write_music', writeMusic);
|
||||
PlayerRoute('write', write);
|
||||
PlayerRoute('start', start);
|
||||
PlayerRoute('buy', buy);
|
||||
// Route management for PnM <= 21
|
||||
|
||||
R.Route(`game.get`, async (req, data, send) => getVersion(req).getInfo(req, data, send));
|
||||
R.Route(`playerdata.new`, async (req, data, send) => getVersion(req).newPlayer(req, data, send));
|
||||
R.Route(`playerdata.conversion`, async (req, data, send) => getVersion(req).newPlayer(req, data, send));
|
||||
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));
|
||||
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
93
popn@asphyxia/models/achievements.ts
Normal file
93
popn@asphyxia/models/achievements.ts
Normal file
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
42
popn@asphyxia/models/common.ts
Normal file
42
popn@asphyxia/models/common.ts
Normal file
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
export interface Scores {
|
||||
collection: 'scores',
|
||||
|
||||
scores: {
|
||||
[key: string]: {
|
||||
clearmedal?: number;
|
||||
clear_type?: number;
|
||||
clear_rank?: number;
|
||||
score: number;
|
||||
cnt: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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")
|
||||
Loading…
Reference in New Issue
Block a user