diff --git a/popn@asphyxia/README.md b/popn@asphyxia/README.md new file mode 100644 index 0000000..ed3fad4 --- /dev/null +++ b/popn@asphyxia/README.md @@ -0,0 +1,18 @@ +# Pop'n Music + +Plugin Version: **v1.0.0** + +Supported Versions +------------------- +- pop'n music 23 Eclale +- pop'n music 24 Usagi to Neko to Shōnen no Yume + +Changelog +========= +1.0.0 +----- +Initial Release. + +TODO +==== +- Import of old Asphyxia data \ No newline at end of file diff --git a/popn@asphyxia/handler/common.ts b/popn@asphyxia/handler/common.ts new file mode 100644 index 0000000..9cd2305 --- /dev/null +++ b/popn@asphyxia/handler/common.ts @@ -0,0 +1,278 @@ +import { getVersion } from './utils'; + +const PHASE23 = [ + { id: 0, p: 16 }, + { id: 1, p: 3 }, + { id: 2, p: 1 }, + { id: 3, p: 2 }, + { id: 4, p: 1 }, + { id: 5, p: 1 }, + { id: 6, p: 1 }, + { id: 7, p: 4 }, + { id: 8, p: 3 }, + { id: 9, p: 4 }, + { id: 10, p: 4 }, + { id: 11, p: 1 }, + { id: 13, p: 4 }, +]; + +const PHASE24 = [ + { id: 3, p: 1 }, + // { id: 5, p: 1 }, + { id: 6, p: 1 }, + { id: 16, p: 1 }, + { id: 17, p: 1 }, + { id: 18, p: 1 }, + { id: 19, p: 1 }, + { id: 20, p: 1 }, +]; + +export const getInfo = (req) => { + const version = getVersion(req); + + if (version == 'v23') { + return getInfo23(); + } else if (version == 'v24') { + return getInfo24(); + } +} + +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 = () => { + const result: any = { + phase: [], + goods: [], + }; + + for (const phase of PHASE24) { + result.phase.push({ + event_id: K.ITEM('s16', phase.id), + phase: K.ITEM('s16', phase.p), + }); + } + + for (let i = 3; i <= 96; ++i) { + result.goods.push({ + item_id: K.ITEM('s32', i), + item_type: K.ITEM('s16', 3), + price: K.ITEM('s32', 60), + goods_type: K.ITEM('s16', 0), + }); + } + + return result; +}; + +export const M39_EXTRA_DATA: { + [ver: string]: { + [field: string]: { + path: string; + type: string; + default: any; + isArray?: true; + }; + }; +} = { + v23: { + tutorial: { type: 's8', path: 'account', default: 0 }, + area_id: { type: 's16', path: 'account', default: 0 }, + lumina: { type: 's16', path: 'account', default: 300 }, + medal_set: { type: 's16', path: 'account', default: [0, 0, 0, 0], isArray: true }, + read_news: { type: 's16', path: 'account', default: 0 }, + staff: { type: 's8', path: 'account', default: 0 }, + item_type: { type: 's16', path: 'account', default: 0 }, + item_id: { type: 's16', path: 'account', default: 0 }, + is_conv: { type: 's8', path: 'account', default: 0 }, + active_fr_num: { type: 'u8', path: 'account', default: 0 }, + nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, + favorite_chara: { type: 's16', path: 'account', default: Array(20).fill(-1), isArray: true }, + special_area: { type: 's16', path: 'account', default: Array(8).fill(0), isArray: true }, + chocolate_charalist: { + type: 's16', + path: 'account', + default: Array(5).fill(-1), + isArray: true, + }, + teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(0), isArray: true }, + license_data: { type: 's16', path: 'account', default: [-1, -1], isArray: true }, + welcom_pack: { type: 'bool', path: 'account', default: 1 }, + meteor_flg: { type: 'bool', path: 'account', default: 1 }, + + hispeed: { type: 's16', path: 'option', default: 0 }, + popkun: { type: 'u8', path: 'option', default: 0 }, + hidden: { type: 'bool', path: 'option', default: 0 }, + hidden_rate: { type: 's16', path: 'option', default: 0 }, + sudden: { type: 'bool', path: 'option', default: 0 }, + sudden_rate: { type: 's16', path: 'option', default: 0 }, + randmir: { type: 's8', path: 'option', default: 0 }, + gauge_type: { type: 's8', path: 'option', default: 0 }, + ojama_0: { type: 'u8', path: 'option', default: 0 }, + ojama_1: { type: 'u8', path: 'option', default: 0 }, + forever_0: { type: 'bool', path: 'option', default: 0 }, + forever_1: { type: 'bool', path: 'option', default: 0 }, + full_setting: { type: 'bool', path: 'option', default: 0 }, + judge: { type: 'u8', path: 'option', default: 0 }, + + ep: { type: 'u16', path: 'info', default: 0 }, + + effect_left: { type: 'u16', path: 'customize', default: 0 }, + effect_center: { type: 'u16', path: 'customize', default: 0 }, + effect_right: { type: 'u16', path: 'customize', default: 0 }, + hukidashi: { type: 'u16', path: 'customize', default: 0 }, + comment_1: { type: 'u16', path: 'customize', default: 0 }, + comment_2: { type: 'u16', path: 'customize', default: 0 }, + + mode: { type: 'u8', path: 'config', default: 0 }, + chara: { type: 's16', path: 'config', default: -1 }, + music: { type: 's16', path: 'config', default: -1 }, + sheet: { type: 'u8', path: 'config', default: 0 }, + category: { type: 's8', path: 'config', default: -1 }, + sub_category: { type: 's8', path: 'config', default: -1 }, + chara_category: { type: 's8', path: 'config', default: -1 }, + course_id: { type: 's16', path: 'config', default: 0 }, + course_folder: { type: 's8', path: 'config', default: 0 }, + ms_banner_disp: { type: 's8', path: 'config', default: 0 }, + ms_down_info: { type: 's8', path: 'config', default: 0 }, + ms_side_info: { type: 's8', path: 'config', default: 0 }, + ms_raise_type: { type: 's8', path: 'config', default: 0 }, + ms_rnd_type: { type: 's8', path: 'config', default: 0 }, + + enemy_medal: { type: 's16', path: 'event', default: 0 }, + hp: { type: 's16', path: 'event', default: 0 }, + + valid: { type: 's8', path: 'custom_cate', default: 0 }, + lv_min: { type: 's8', path: 'custom_cate', default: -1 }, + lv_max: { type: 's8', path: 'custom_cate', default: -1 }, + medal_min: { type: 's8', path: 'custom_cate', default: -1 }, + medal_max: { type: 's8', path: 'custom_cate', default: -1 }, + friend_no: { type: 's8', path: 'custom_cate', default: -1 }, + score_flg: { type: 's8', path: 'custom_cate', default: -1 }, + },v24: { + enemy_medal: { type: 's16', path: 'event', default: 0 }, + hp: { type: 's16', path: 'event', default: 0 }, + + tutorial: { type: 's16', path: 'account', default: -1 }, + area_id: { type: 's16', path: 'account', default: 51 }, + lumina: { type: 's16', path: 'account', default: 0 }, + medal_set: { type: 's16', path: 'account', default: [0, 0], isArray: true }, + read_news: { type: 's16', path: 'account', default: 0 }, + staff: { type: 's8', path: 'account', default: 0 }, + is_conv: { type: 's8', path: 'account', default: 0 }, + item_type: { type: 's16', path: 'account', default: 0 }, + item_id: { type: 's16', path: 'account', default: 0 }, + license_data: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, + active_fr_num: { type: 'u8', path: 'account', default: 0 }, + nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true }, + favorite_chara: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, + special_area: { type: 's16', path: 'account', default: Array(8).fill(-1), isArray: true }, + chocolate_charalist: { + type: 's16', + path: 'account', + default: Array(5).fill(-1), + isArray: true, + }, + chocolate_sp_chara: { type: 's32', path: 'account', default: 0 }, + chocolate_pass_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_hon_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_giri_cnt: { type: 's32', path: 'account', default: 0 }, + chocolate_kokyu_cnt: { type: 's32', path: 'account', default: 0 }, + teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true }, + welcom_pack: { type: 'bool', path: 'account', default: 0 }, + meteor_flg: { type: 'bool', path: 'account', default: 0 }, + use_navi: { type: 's16', path: 'account', default: 0 }, + ranking_node: { type: 's32', path: 'account', default: 0 }, + chara_ranking_kind_id: { type: 's32', path: 'account', default: 0 }, + navi_evolution_flg: { type: 's8', path: 'account', default: 0 }, + ranking_news_last_no: { type: 's32', path: 'account', default: 0 }, + power_point: { type: 's32', path: 'account', default: 0 }, + player_point: { type: 's32', path: 'account', default: 0 }, + power_point_list: { type: 's32', path: 'account', default: [0], isArray: true }, + + mode: { type: 'u8', path: 'config', default: 0 }, + chara: { type: 's16', path: 'config', default: 0 }, + music: { type: 's16', path: 'config', default: 0 }, + sheet: { type: 'u8', path: 'config', default: 0 }, + category: { type: 's8', path: 'config', default: 0 }, + sub_category: { type: 's8', path: 'config', default: 0 }, + chara_category: { type: 's8', path: 'config', default: 0 }, // check + story_id: { type: 's16', path: 'config', default: 0 }, + ms_banner_disp: { type: 's8', path: 'config', default: 0 }, + ms_down_info: { type: 's8', path: 'config', default: 0 }, + ms_side_info: { type: 's8', path: 'config', default: 0 }, + ms_raise_type: { type: 's8', path: 'config', default: 0 }, + ms_rnd_type: { type: 's8', path: 'config', default: 0 }, + banner_sort: { type: 's8', path: 'config', default: 0 }, + course_id: { type: 's16', path: 'config', default: 0 }, + course_folder: { type: 's8', path: 'config', default: 0 }, + story_folder: { type: 's8', path: 'config', default: 0 }, + + hispeed: { type: 's16', path: 'option', default: 10 }, + popkun: { type: 'u8', path: 'option', default: 0 }, + hidden: { type: 'bool', path: 'option', default: 0 }, + hidden_rate: { type: 's16', path: 'option', default: -1 }, + sudden: { type: 'bool', path: 'option', default: 0 }, + sudden_rate: { type: 's16', path: 'option', default: -1 }, + randmir: { type: 's8', path: 'option', default: 0 }, + gauge_type: { type: 's8', path: 'option', default: 0 }, + ojama_0: { type: 'u8', path: 'option', default: 0 }, + ojama_1: { type: 'u8', path: 'option', default: 0 }, + forever_0: { type: 'bool', path: 'option', default: 0 }, + forever_1: { type: 'bool', path: 'option', default: 0 }, + full_setting: { type: 'bool', path: 'option', default: 0 }, + guide_se: { type: 's8', path: 'option', default: 0 }, + judge: { type: 'u8', path: 'option', default: 0 }, + slow: { type: 's16', path: 'option', default: 0 }, + fast: { type: 's16', path: 'option', default: 0 }, + + valid: { type: 's8', path: 'custom_cate', default: 0 }, + lv_min: { type: 's8', path: 'custom_cate', default: 0 }, + lv_max: { type: 's8', path: 'custom_cate', default: 0 }, + medal_min: { type: 's8', path: 'custom_cate', default: 0 }, + medal_max: { type: 's8', path: 'custom_cate', default: 0 }, + friend_no: { type: 's8', path: 'custom_cate', default: 0 }, + score_flg: { type: 's8', path: 'custom_cate', default: 0 }, + + ep: { type: 'u16', path: 'info', default: 0 }, + ap: { type: 'u16', path: 'info', default: 0 }, + + effect_left: { type: 'u16', path: 'customize', default: 0 }, + effect_center: { type: 'u16', path: 'customize', default: 0 }, + effect_right: { type: 'u16', path: 'customize', default: 0 }, + hukidashi: { type: 'u16', path: 'customize', default: 0 }, + comment_1: { type: 'u16', path: 'customize', default: 0 }, + comment_2: { type: 'u16', path: 'customize', default: 0 }, + }, +}; \ No newline at end of file diff --git a/popn@asphyxia/handler/player.ts b/popn@asphyxia/handler/player.ts new file mode 100644 index 0000000..a9949fb --- /dev/null +++ b/popn@asphyxia/handler/player.ts @@ -0,0 +1,519 @@ +import { Profile } from '../models/profile'; +import { Scores } from '../models/scores'; +import { getInfo, M39_EXTRA_DATA } from './common'; +import { getVersion } from './utils'; + +const getPlayer = async (refid: string, version: string, name?: string) => { + const profile = await readProfile(refid); + + if (name && name.length > 0) { + profile.name = name; + await writeProfile(refid, profile); + } + + let player: any = { + result: K.ITEM('s8', 0), + account: { + name: K.ITEM('str', profile.name), + g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'), + + // Fixed stats + total_play_cnt: K.ITEM('s16', 100), + today_play_cnt: K.ITEM('s16', 50), + consecutive_days: K.ITEM('s16', 365), + total_days: K.ITEM('s16', 366), + interval_day: K.ITEM('s16', 1), + + // TODO: do these + my_best: K.ARRAY('s16', Array(10).fill(-1)), + latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]), + }, + + netvs: { + record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]), + dialog: [ + K.ITEM('str', 'dialog#0'), + K.ITEM('str', 'dialog#1'), + K.ITEM('str', 'dialog#2'), + K.ITEM('str', 'dialog#3'), + K.ITEM('str', 'dialog#4'), + K.ITEM('str', 'dialog#5'), + ], + ojama_condition: K.ARRAY('s8', Array(74).fill(0)), + set_ojama: K.ARRAY('s8', [0, 0, 0]), + set_recommend: K.ARRAY('s8', [0, 0, 0]), + netvs_play_cnt: K.ITEM('u32', 0), + }, + + eaappli: { + relation: K.ITEM('s8', 0), + }, + + stamp: [], + item: [], + chara_param: [], + medal: [], + }; + + const profileStamps = profile.stamps[version] || { '0': 0 }; + + for (const stamp in profileStamps) { + player.stamp.push({ + stamp_id: K.ITEM('s16', parseInt(stamp, 10)), + cnt: K.ITEM('s16', profileStamps[stamp]), + }); + } + + const profileCharas = profile.charas[version] || {}; + + for (const chara_id in profileCharas) { + player.chara_param.push({ + chara_id: K.ITEM('u16', parseInt(chara_id, 10)), + friendship: K.ITEM('u16', profileCharas[chara_id]), + }); + } + + const profileMedals = profile.medals[version] || {}; + + for (const medal_id in profileMedals) { + const medal = profileMedals[medal_id]; + player.medal.push({ + medal_id: K.ITEM('s16', parseInt(medal_id, 10)), + level: K.ITEM('s16', medal.level), + exp: K.ITEM('s32', medal.exp), + set_count: K.ITEM('s32', medal.set_count), + get_count: K.ITEM('s32', medal.get_count), + }); + } + + const profileItems = profile.items[version] || {}; + + for (const key in profileItems) { + const keyData = key.split(':'); + const type = parseInt(keyData[0], 10); + const id = parseInt(keyData[1], 10); + + const item: any = { + type: K.ITEM('u8', type), + id: K.ITEM('u16', id), + param: K.ITEM('u16', profileItems[key]), + is_new: K.ITEM('bool', 0), + }; + + if (version != 'v23') { + item.get_time = K.ITEM('u64', BigInt(0)); + } + + player.item.push(item); + } + + // EXTRA DATA + if (M39_EXTRA_DATA[version]) { + for (const field in M39_EXTRA_DATA[version]) { + const fieldMetaData = M39_EXTRA_DATA[version][field]; + if (fieldMetaData.isArray) { + _.set( + player, + `${fieldMetaData.path}.${field}`, + K.ARRAY( + fieldMetaData.type as any, + _.get(profile, `extras.${version}.${field}`, fieldMetaData.default) + ) + ); + } else { + _.set( + player, + `${fieldMetaData.path}.${field}`, + K.ITEM( + fieldMetaData.type as any, + _.get(profile, `extras.${version}.${field}`, fieldMetaData.default) + ) + ); + } + } + } + + // Extra Fixed Data + if (version == 'v24') { + player = { + ...player, + navi_data: { + raisePoint: K.ARRAY('s32', [-1, -1, -1, -1, -1]), + navi_param: { + navi_id: K.ITEM('u16', 0), + friendship: K.ITEM('s32', 0), + }, + }, + + area: { + area_id: K.ITEM('u32', 0), + chapter_index: K.ITEM('u8', 0), + gauge_point: K.ITEM('u16', 0), + is_cleared: K.ITEM('bool', 0), + diary: K.ITEM('u32', 0), + }, + + mission: [ + { + mission_id: K.ITEM('u32', 170), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + { + mission_id: K.ITEM('u32', 157), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + { + mission_id: K.ITEM('u32', 47), + gauge_point: K.ITEM('u32', 0), + mission_comp: K.ITEM('u32', 0), + }, + ], + }; + } + + return player; +}; + +export const newPlayer: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const version = getVersion(req); + const name = $(data).str('name'); + + send.object(await getPlayer(refid, version, name)); +}; + +export const read: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const version = getVersion(req); + + send.object(await getPlayer(refid, version)); +}; + +export const readScore: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const scoresData = await readScores(refid); + const version = getVersion(req); + const result: any = { + music: [], + }; + + if(version == 'v24') { + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + result.music.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', score.clear_type || 0), + clear_rank: K.ITEM('u8', score.clear_rank || 0), + cnt: K.ITEM('s16', score.cnt), + }); + } + } else if(version == 'v23') { + for (const key in scoresData.scores) { + const keyData = key.split(':'); + const score = scoresData.scores[key]; + const music = parseInt(keyData[0], 10); + const sheet = parseInt(keyData[1], 10); + result.music.push({ + music_num: K.ITEM('s16', music), + sheet_num: K.ITEM('u8', sheet), + score: K.ITEM('s32', score.score), + clear_type: K.ITEM('u8', score.clearmedal || 0), + cnt: K.ITEM('s16', score.cnt), + old_score: K.ITEM('s32', 0), + old_clear_type: K.ITEM('u8', 0), + }); + } + } + + send.object(result); +}; + +export const writeMusic: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const music = $(data).number('music_num', -1); + const sheet = $(data).number('sheet_num', -1); + const clear_type = $(data).number('clear_type'); + const clear_rank = $(data).number('clear_rank'); + const clearmedal = $(data).number('clearmedal'); + const score = $(data).number('score', 0); + + if (music < 0 || sheet < 0) { + return send.deny(); + } + + const key = `${music}:${sheet}`; + + const scoresData = await readScores(refid); + if (!scoresData.scores[key]) { + scoresData.scores[key] = { + score, + cnt: 1, + }; + } else { + scoresData.scores[key] = { + score: Math.max(score, scoresData.scores[key].score), + cnt: scoresData.scores[key].cnt + 1, + }; + } + + if (clear_type) { + scoresData.scores[key].clear_type = Math.max(clear_type, scoresData.scores[key].clear_type || 0); + } + + if (clear_rank) { + scoresData.scores[key].clear_rank = Math.max(clear_rank, scoresData.scores[key].clear_rank || 0); + } + + if (clearmedal) { + scoresData.scores[key].clearmedal = Math.max(clearmedal, scoresData.scores[key].clearmedal || 0); + } + + writeScores(refid, scoresData); + + const version = getVersion(req); + if (version == 'v24') { + + const p = await readProfile(refid); + + const settings = [ + 'hispeed', + 'popkun', + 'hidden', + 'hidden_rate', + 'sudden', + 'sudden_rate', + 'randmir', + 'ojama_0', + 'ojama_1', + 'forever_0', + 'forever_1', + 'full_setting', + 'guide_se', + 'judge', + 'slow', + 'fast', + 'mode', + ]; + + for (const setting of settings) { + _.set( + p, + `extras.v24.${setting}`, + _.get(data, `${setting}.@content.0`, _.get(p, `extras.v24.${setting}`, 0)) + ); + } + + _.set(p, `extras.v24.tutorial`, 32767); + + const chara = $(data).number('chara_num'); + if (chara) { + _.set(p, 'extras.v24.chara', chara); + } + + const music = $(data).number('music_num'); + if (music) { + _.set(p, 'extras.v24.music', music); + } + + const sheet = $(data).number('sheet_num'); + if (sheet) { + _.set(p, 'extras.v24.sheet', sheet); + } + + writeProfile(refid, p); + } + + send.success(); +}; + +export const write: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const version = getVersion(req); + const profile = await readProfile(refid); + + const writeData: Partial = {}; + + if (M39_EXTRA_DATA[version]) { + const extraFields = M39_EXTRA_DATA[version]; + for (const field in extraFields) { + const fieldMetaData = extraFields[field]; + let value = _.get(data, `${fieldMetaData.path}.${field}.@content`); + if ( value == 'undefined' && value == null ) { + continue; + } + + if (_.isArray(value) && value.length == 1) { + value = value[0]; + } + + _.set(writeData, `extras.${version}.${field}`, value); + } + } + + const newProfile:Profile = { + ...profile, + ...writeData, + }; + + // stamps + let stamps = _.get(data, 'stamp', []); + if (!newProfile.stamps[version]) { + newProfile.stamps[version] = { '0': 0 }; + } + + if (!_.isArray(stamps)) { + stamps = [stamps]; + } + + for (const stamp of stamps) { + const id = $(stamp).number('stamp_id'); + const cnt = $(stamp).number('cnt'); + + newProfile.stamps[version][id] = cnt; + } + + // medals + let medals = _.get(data, 'medal', []); + if (!newProfile.medals[version]) { + newProfile.medals[version] = {}; + } + + if (!_.isArray(medals)) { + medals = [medals]; + } + + for (const medal of medals) { + const id = $(medal).number('medal_id'); + const level = $(medal).number('level'); + const exp = $(medal).number('exp'); + const set_count = $(medal).number('set_count'); + const get_count = $(medal).number('get_count'); + + newProfile.medals[version][id] = { + level, + exp, + set_count, + get_count, + }; + } + + // items + let items = _.get(data, 'item', []); + if (!newProfile.items[version]) { + newProfile.items[version] = {}; + } + + if (!_.isArray(items)) { + items = [items]; + } + + for (const item of items) { + const type = $(item).number('type'); + const id = $(item).number('id'); + const param = $(item).number('param'); + + const key = `${type}:${id}`; + + newProfile.items[version][key] = param; + } + + // charas + let charas = _.get(data, 'chara_param', []); + if (!newProfile.charas[version]) { + newProfile.charas[version] = {}; + } + + if (!_.isArray(charas)) { + charas = [charas]; + } + + for (const chara of charas) { + const id = $(chara).number('chara_id'); + const param = $(chara).number('friendship'); + + newProfile.charas[version][id] = param; + } + + await writeProfile(refid, newProfile); + send.success(); +}; + +export const start: EPR = async (req, data, send) => { + const result = { + play_id: K.ITEM('s32', 1), + ...getInfo(req), + }; + await send.object(result); +}; + +export const buy: EPR = async (req, data, send) => { + const refid = $(data).str('ref_id'); + if (!refid) return send.deny(); + + const type = $(data).number('type', -1); + const id = $(data).number('id', -1); + const param = $(data).number('param', 0); + const version = getVersion(req); + + if (type < 0 || id < 0) { + return send.deny(); + } + + const key = `${type}:${id}`; + + const profile = await readProfile(refid); + if (!profile.items[version]) { + profile.items[version] = {}; + } + profile.items[version][key] = param; + await writeProfile(refid, profile); + send.success(); +}; + +const defaultProfile:Profile = { + collection: 'profile', + + name: 'ゲスト', + + stamps: {}, + medals: {}, + items: {}, + charas: {}, + + extras: {}, +}; + +async function readProfile(refid: string): Promise { + const profile = await DB.FindOne(refid, { collection: 'profile'} ) + return profile || defaultProfile +} + +async function writeProfile(refid: string, profile: Profile) { + await DB.Upsert(refid, { collection: 'profile'}, profile) +} + +async function readScores(refid: string): Promise { + const score = await DB.FindOne(refid, { collection: 'scores'} ) + return score || { collection: 'scores', scores: {}} +} + +async function writeScores(refid: string, scores: Scores) { + await DB.Upsert(refid, { collection: 'scores'}, scores) +} \ No newline at end of file diff --git a/popn@asphyxia/handler/utils.ts b/popn@asphyxia/handler/utils.ts new file mode 100644 index 0000000..46d083a --- /dev/null +++ b/popn@asphyxia/handler/utils.ts @@ -0,0 +1,4 @@ +export const getVersion = (info: EamuseInfo) => { + const moduleName: string = info.module; + return `v${moduleName.match(/[0-9]+/)[0]}`; +}; \ No newline at end of file diff --git a/popn@asphyxia/index.ts b/popn@asphyxia/index.ts new file mode 100644 index 0000000..0f83c80 --- /dev/null +++ b/popn@asphyxia/index.ts @@ -0,0 +1,30 @@ +import { getInfo } from "./handler/common"; +import { newPlayer, read, readScore, start, writeMusic, write, buy} from "./handler/player" + +export function register() { + R.GameCode('M39'); + + 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)); + }); + + // Player + PlayerRoute('new', newPlayer); + PlayerRoute('read', read); + PlayerRoute('read_score', readScore); + PlayerRoute('write_music', writeMusic); + PlayerRoute('write', write); + PlayerRoute('start', start); + PlayerRoute('buy', buy); +} \ No newline at end of file diff --git a/popn@asphyxia/models/profile.ts b/popn@asphyxia/models/profile.ts new file mode 100644 index 0000000..2faf776 --- /dev/null +++ b/popn@asphyxia/models/profile.ts @@ -0,0 +1,40 @@ +export interface Profile { + collection: 'profile', + + name: string; + + stamps: { + [ver: string]: { + [stamp_id: string]: number; + }; + }; + + medals: { + [ver: string]: { + [id: string]: { + level: number; + exp: number; + set_count: number; + get_count: number; + }; + }; + }; + + items: { + [ver: string]: { + [key: string]: number; + }; + }; + + charas: { + [ver: string]: { + [chara_id: string]: number; + }; + }; + + extras: { + [ver: string]: { + [key: string]: any; + }; + }; +} \ No newline at end of file diff --git a/popn@asphyxia/models/scores.ts b/popn@asphyxia/models/scores.ts new file mode 100644 index 0000000..5472678 --- /dev/null +++ b/popn@asphyxia/models/scores.ts @@ -0,0 +1,13 @@ +export interface Scores { + collection: 'scores', + + scores: { + [key: string]: { + clearmedal?: number; + clear_type?: number; + clear_rank?: number; + score: number; + cnt: number; + }; + }; +} \ No newline at end of file