import { Profile } from '../models/profile'; import { Scores } from '../models/scores'; import { NosVersionHelper } from '../utils'; import { permitted_list, forte_permitted_list } from './common'; // export const event_list = { // event: { // '@attr': { // index: 1, // }, // 'status': K.ITEM('s8', 1), // 'start_time': K.ITEM('u64', BigInt(0)), // 'end_time': K.ITEM('u64', BigInt(0)), // }, // }; const getEventInfo = (version: NosVersionHelper) => { const event: any[] = []; const event_num = version.getEventMaxIndex() for (let i = 1; i <= event_num; ++i) { event.push({ type: K.ITEM('s32', 4), index: K.ITEM('s32', i), status: K.ITEM('s8', 1), start_time: K.ITEM('u64', BigInt(0)), end_time: K.ITEM('u64', BigInt(0)), param1: K.ITEM('u64', BigInt(0)), param2: K.ITEM('u64', BigInt(0)), }); } return event; }; const getPlayerData = async (refid: string, info: EamuseInfo, name?: string) => { const p = await readProfile(refid); const version = new NosVersionHelper(info) if (name && name.length > 0) { p.name = name; await writeProfile(refid, p); } const param: any[] = []; for (const t in p.params) { const para = p.params[t]; param.push(K.ATTR({ type: t }, { count: K.ITEM('s32', para.length), params_array: K.ARRAY('s32', para), })); } const brooch: any[] = []; for (const b in p.brooches) { if (parseInt(b, 10) > version.getBroochMaxIndex()) continue; const bData = p.brooches[b]; brooch.push(K.ATTR({ index: b }, { watch_count: K.ITEM('s32', bData.watch), level: K.ITEM('s8', bData.level), invested_steps: K.ITEM('s32', bData.steps), is_new_brooch: K.ITEM('bool', bData.new), })); } // Unlock brooches for (let i = 101; i <= 124; ++i) { if (i > version.getBroochMaxIndex()) continue; brooch.push(K.ATTR({ index: `${i}` }, { 'watch_count': K.ITEM('s32', 0), 'level': K.ITEM('s8', 1), 'invested_steps': K.ITEM('s32', 0), 'is_new_brooch': K.ITEM('bool', 0), })); } // Forte const stairs: any[] = []; for (const s in (p.cat_stairs || defaultProfile.cat_stairs)) { const stair = (p.cat_stairs || defaultProfile.cat_stairs)[s]; stairs.push(K.ATTR({ index: s }, { total_steps: K.ITEM("s32", stair.total), chapter_index: K.ITEM("s32", stair.index), chapter_steps: K.ITEM("s32", stair.steps), chapter_goal: K.ITEM("s32", stair.goal) })); } // >= Op2 const kentei_record: any[] = []; for (const k in p.kentei) { const kentei = p.kentei[k]; kentei_record.push({ stage_prog: K.ITEM('s8', kentei.stage), kentei_index: K.ITEM('s32', parseInt(k, 10)), score: K.ARRAY('s32', kentei.score), clear_rate: K.ITEM('s32', kentei.rate), clear_flag: K.ITEM('u32', kentei.flag), play_count: K.ITEM('s32', kentei.count), }); } // >= Op2 const island_progress: any[] = []; for (const i in p.islands) { const island = p.islands[i]; const container: any[] = []; for (const c in island.containers) { const cont = island.containers[c]; const reward: any[] = []; for (const r in cont.rewards) { const rew = cont.rewards[r]; reward.push({ reward_index: K.ITEM('s32', parseInt(r, 10)), point: K.ITEM('s32', rew), }); } container.push({ container_no: K.ITEM('s32', parseInt(c, 10)), movie_prog: K.ITEM('s8', cont.prog), reward, }); } island_progress.push(K.ATTR({ index: i }, { lookUnlockWin: K.ITEM('s32', island.look), select_container: K.ITEM('s32', island.select), travelledTime: K.ITEM('u32', island.time), container, })); } const correct_permitted_list = !version.isFirstOrForte() ? permitted_list : forte_permitted_list const music_list = [ K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }), K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }), K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }), K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }), ]; const music_list2 = [ K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }), K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }), K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }), K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }), ]; if(version.isFirstOrForte()) { music_list.pop(); music_list2.pop(); } return buildLegacyPlayerPayload(p, version, { param, brooch, stairs, kentei_record, island_progress, correct_permitted_list, music_list, music_list2, }); }; const buildMusicFlags = (p: Profile) => ({ flag: [ K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }), K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }), K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }), K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }), ], }); const buildFreeMusicFlags = (p: Profile) => ({ flag: [ K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }), K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }), K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }), K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }), ], }); const op3Num = (value: number | undefined, fallback = 0) => typeof value === 'number' && !Number.isNaN(value) ? value : fallback; const padS32 = (arr: number[] | undefined, len: number, fill: number) => { const out = Array(len).fill(fill); if (!arr) return out; for (let i = 0; i < Math.min(len, arr.length); i++) { const v = arr[i]; out[i] = typeof v === 'number' && !Number.isNaN(v) ? v : fill; } return out; }; const padOp3MusicLists = (profile: Profile): Profile => ({ ...profile, musicList: { type_0: padS32(profile.musicList?.type_0, 32, -1), type_1: padS32(profile.musicList?.type_1, 32, -1), type_2: padS32(profile.musicList?.type_2, 32, -1), type_3: padS32(profile.musicList?.type_3, 32, -1), }, musicList2: { type_0: padS32(profile.musicList2?.type_0, 32, -1), type_1: padS32(profile.musicList2?.type_1, 32, -1), type_2: padS32(profile.musicList2?.type_2, 32, -1), type_3: padS32(profile.musicList2?.type_3, 32, -1), }, params: { '1': padS32(profile.params?.['1'], 11, 0), '2': padS32(profile.params?.['2'], 8, 0), }, }); const op3FreshMusicFlags = () => ({ flag: [ K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '0' }), K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '1' }), K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '2' }), K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '3' }), ], }); const buildOp3Last = (p: Profile, isRegist = false) => { const last: Record = { music_group: K.ITEM('s32', op3Num(p.group)), music_index: K.ITEM('s32', op3Num(p.music)), sheet_type: K.ITEM('s8', op3Num(p.sheet)), perform_type: K.ITEM('s32', op3Num(p.performType)), filter_flag: K.ITEM('u64', BigInt(op3Num(p.filterFlag))), brooch_index: K.ITEM('s32', op3Num(p.brooch)), hi_speed_level: K.ITEM('s32', op3Num(p.hispeed)), beat_guide: K.ITEM('s8', op3Num(p.beatGuide)), headphone_volume: K.ITEM('s8', op3Num(p.headphone)), judge_bar_pos: K.ITEM('s32', isRegist ? 0 : op3Num(p.judgeBar, 250)), hands_mode: K.ITEM('s8', op3Num(p.mode)), near_setting: K.ITEM('s8', op3Num(p.near)), judge_delay_offset: K.ITEM('s8', op3Num(p.offset)), key_beam_level: K.ITEM('s8', op3Num(p.keyBeam)), orbit_type: K.ITEM('s8', op3Num(p.orbit)), note_height: K.ITEM('s8', op3Num(p.noteHeight, 10)), note_width: K.ITEM('s8', op3Num(p.noteWidth, 10)), judge_width_type: K.ITEM('s8', op3Num(p.judgeWidth, 10)), beat_guide_volume: K.ITEM('s8', op3Num(p.beatVolume)), beat_guide_type: K.ITEM('s8', op3Num(p.beatType)), key_volume_offset: K.ITEM('s8', op3Num(p.keyVolume)), bgm_volume_offset: K.ITEM('s8', op3Num(p.bgmVolume)), note_disp_type: K.ITEM('s8', op3Num(p.note)), slow_fast: K.ITEM('s8', op3Num(p.sf)), option_setting: K.ITEM('s32', op3Num(p.optionSetting)), judge_effect_adjust: K.ITEM('s8', op3Num(p.judgeFX)), simple_bg: K.ITEM('s8', op3Num(p.simple)), bingo_index: K.ITEM('s32', op3Num(p.bingo)), }; if (!isRegist) { last.class_basic = K.ITEM('s32', op3Num(p.classBasic)); last.class_recital = K.ITEM('s32', op3Num(p.classRecital)); last.grade_basic = K.ITEM('s32', op3Num(p.gradeBasic)); last.grade_recital = K.ITEM('s32', op3Num(p.gradeRecital)); } return last; }; const buildOp3Travel = (p: Profile) => ({ money: K.ITEM('s32', op3Num(p.money)), pianist_power: K.ITEM('s32', op3Num(p.pianistPower)), fame_index: K.ITEM('s32', op3Num(p.fameId)), kingdom_id: K.ITEM('s32', op3Num(p.kingdomId)), quest_index: K.ITEM('s32', op3Num(p.questIndex)), }); const buildOp3ExtraParam = () => ({ param: [ K.ATTR({ type: '1' }, { count: K.ITEM('s32', 11), params_array: K.ARRAY('s32', [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]), }), K.ATTR({ type: '2' }, { count: K.ITEM('s32', 8), params_array: K.ARRAY('s32', [64, 0, 0, 0, 0, 0, 0, 0]), }), ], }); const op3RegistExtras = () => ({ valid_quest_list: { quest: K.ATTR({ index: '1' }, {}), }, valid_course_list: { course: K.ATTR({ index: '1' }, {}), }, }); const getPlayerDataOp3 = async ( refid: string, _info: EamuseInfo, options?: { name?: string; isRegist?: boolean }, ) => { const p = await readProfile(refid); if (options?.name && options.name.length > 0) { p.name = options.name; await writeProfile(refid, p); } const identity = { name: K.ITEM('str', p.name || 'GUEST'), play_count: K.ITEM('s32', 0), today_play_count: K.ITEM('s32', 0), old_play_count: K.ITEM('s32', 0), old_recital_count: K.ITEM('s32', 0), }; if (options?.isRegist) { return { permitted_list, ...op3RegistExtras(), ...identity, music_list: op3FreshMusicFlags(), free_for_play_music_list: op3FreshMusicFlags(), last: buildOp3Last(p, true), travel: buildOp3Travel(p), }; } return { permitted_list, ...identity, music_list: op3FreshMusicFlags(), free_for_play_music_list: op3FreshMusicFlags(), last: buildOp3Last(p, false), travel: buildOp3Travel(p), extra_param: buildOp3ExtraParam(), }; }; function buildLegacyPlayerPayload(p: Profile, version: NosVersionHelper, parts: any) { const { param, brooch, stairs, kentei_record, island_progress, correct_permitted_list, music_list, music_list2, } = parts; return { name: K.ITEM('str', p.name), play_count: K.ITEM('s32', p.playCount), today_play_count: K.ITEM('s32', p.todayPlayCount), permitted_list: correct_permitted_list, event_info_list: { event: getEventInfo(version) }, // Op2 event_control_list: { event: getEventInfo(version) }, // Forte music_list: { flag: music_list, }, free_for_play_music_list: { flag: music_list2, }, last: { music_index: K.ITEM('s32', version.numericHandler('music_index', p.music, 0)), sheet_type: K.ITEM('s8', version.numericHandler('sheet_type', p.sheet, 0)), brooch_index: K.ITEM('s32', version.numericHandler('brooch_index', p.brooch, 0)), hi_speed_level: K.ITEM('s32', p.hispeed), beat_guide: K.ITEM('s8', p.beatGuide), headphone_volume: K.ITEM('s8', p.headphone), judge_bar_pos: K.ITEM('s32', p.judgeBar), music_group: K.ITEM('s32', p.group), hands_mode: version.isFirstOrForte() ? K.ITEM('s32', p.mode) : K.ITEM('s8', p.mode), near_setting: K.ITEM('s8', p.near), judge_delay_offset: version.isFirstOrForte() ? K.ITEM('s32', p.offset) : K.ITEM('s8', p.offset), bingo_index: K.ITEM('s32', p.bingo), total_skill_value: K.ITEM('u64', BigInt(p.skill)), key_beam_level: K.ITEM('s8', p.keyBeam), orbit_type: K.ITEM('s8', p.orbit), note_height: K.ITEM('s8', p.noteHeight), note_width: K.ITEM('s8', p.noteWidth), judge_width_type: K.ITEM('s8', p.judgeWidth), beat_guide_volume: K.ITEM('s8', p.beatVolume), beat_guide_type: K.ITEM('s8', p.beatType), key_volume_offset: K.ITEM('s8', p.keyVolume), bgm_volume_offset: K.ITEM('s8', p.bgmVolume), note_disp_type: K.ITEM('s8', p.note), slow_fast: K.ITEM('s8', p.sf), judge_effect_adjust: K.ITEM('s8', p.judgeFX), simple_bg: K.ITEM('s8', p.simple), }, // TODO: Full unlock instead of saving? cat_progress: { stair: stairs }, brooch_list: { brooch }, extra_param: { param }, present_list: {}, various_music_list: { data: [ K.ATTR({ list_type: '0' }, { cond_flag: K.ITEM('s32', 0), flag: K.ITEM('s32', 0, { sheet_type: '0' }), }), ], }, island_progress_list: { island_progress }, player_information_list: {}, kentei_record_list: { kentei_record }, linkage_data_list: {}, travel: { money: K.ITEM('s32', p.money), fame: K.ITEM('s32', p.fame), fame_index: K.ITEM('s32', p.fameId), island_id: K.ITEM('s32', p.island), }, }; }; export const regist_playdata: EPR = async (info, data, send) => { const refid = $(data).str('refid'); if (!refid) return send.deny(); const name = $(data).str('name'); const version = new NosVersionHelper(info); console.debug(`nos ${version.version} regist: ${name}`); if (version.isOp3()) { const payload = await getPlayerDataOp3(refid, info, { name, isRegist: true }); console.log(`[nostalgia@asphyxia] op3 regist_playdata refid=${refid} name=${name}`); send.object(payload); return; } send.object(await getPlayerData(refid, info, name)); }; export const get_playdata: EPR = async (info, data, send) => { const refid = $(data).str('refid'); if (!refid) return send.deny(); const version = new NosVersionHelper(info); if (version.isOp3()) { send.object(await getPlayerDataOp3(refid, info)); return; } send.object(await getPlayerData(refid, info)); }; export const set_stage_result: EPR = async (info, data, send) => { const version = new NosVersionHelper(info); if (!version.isOp3()) { send.success(); return; } const refid = $(data).str('refid'); if (!refid) return send.deny(); const scoreData = await readScores(refid); const stages = $(data).elements('stageinfo.stage'); const stage = stages.length > 0 ? stages[stages.length - 1] : null; if (!stage) { send.success(); return; } const mid = stage.attr().music_index; const type = stage.attr().sheet_type; const common = stage.element('common'); const key = `${mid}:${type}`; const o = _.get(scoreData, `scores.${key}`, {}); const isHigh = common.number('score', 0) >= _.get(o, 'score', 0); scoreData.scores[key] = { score: Math.max(common.number('score', 0), _.get(o, 'score', 0)), grade: isHigh ? common.number('grade', 0) : _.get(o, 'grade', 0), recital: _.get(o, 'recital', 0), mode: isHigh ? common.number('hands_mode', 0) : _.get(o, 'mode', 0), count: Math.max(common.number('play_count', 0), _.get(o, 'count', 1)), clear: common.number('clear_count', _.get(o, 'clear', 0)), multi: common.number('multi_count', _.get(o, 'multi', 0)), flag: Math.max(common.number('clear_flag', 0), _.get(o, 'flag', 0)), }; await writeScores(refid, scoreData); send.object({ player: {} }); }; export const set_total_result: EPR = async (info, data, send) => { const refid = $(data).str('refid'); if (!refid) return send.deny(); const version = new NosVersionHelper(info); if (version.isOp3()) { const p = await readProfile(refid); p.playCount = $(data).number('play_count', p.playCount); p.todayPlayCount = $(data).number('today_play_count', p.todayPlayCount); p.oldPlayCount = $(data).number('old_play_count', p.oldPlayCount); p.oldRecitalCount = $(data).number('old_recital_count', p.oldRecitalCount); const last = $(data).element('last'); p.group = last.number('music_group', p.group); p.music = last.number('music_index', p.music); p.sheet = last.number('sheet_type', p.sheet); p.performType = last.number('perform_type', p.performType); p.filterFlag = Number(last.bigint('filter_flag') ?? BigInt(p.filterFlag)); p.brooch = last.number('brooch_index', p.brooch); p.hispeed = last.number('hi_speed_level', p.hispeed); p.beatGuide = last.number('beat_guide', p.beatGuide); p.headphone = last.number('headphone_volume', p.headphone); p.judgeBar = last.number('judge_bar_pos', p.judgeBar); p.mode = last.number('hands_mode', p.mode); p.near = last.number('near_setting', p.near); p.offset = last.number('judge_delay_offset', p.offset); p.keyBeam = last.number('key_beam_level', p.keyBeam); p.orbit = last.number('orbit_type', p.orbit); p.noteHeight = last.number('note_height', p.noteHeight); p.noteWidth = last.number('note_width', p.noteWidth); p.judgeWidth = last.number('judge_width_type', p.judgeWidth); p.beatVolume = last.number('beat_guide_volume', p.beatVolume); p.beatType = last.number('beat_guide_type', p.beatType); p.keyVolume = last.number('key_volume_offset', p.keyVolume); p.bgmVolume = last.number('bgm_volume_offset', p.bgmVolume); p.note = last.number('note_disp_type', p.note); p.sf = last.number('slow_fast', p.sf); p.optionSetting = last.number('option_setting', p.optionSetting); p.judgeFX = last.number('judge_effect_adjust', p.judgeFX); p.simple = last.number('simple_bg', p.simple); p.bingo = last.number('bingo_index', p.bingo); p.classBasic = last.number('class_basic', p.classBasic); p.classRecital = last.number('class_recital', p.classRecital); p.gradeBasic = last.number('grade_basic', p.gradeBasic); p.gradeRecital = last.number('grade_recital', p.gradeRecital); p.money = $(data).number('travel.money', p.money); p.pianistPower = $(data).number('travel.pianist_power', p.pianistPower); p.fameId = $(data).number('travel.fame_index', p.fameId); p.kingdomId = $(data).number('travel.kingdom_id', p.kingdomId); p.questIndex = $(data).number('travel.quest_index', p.questIndex); let flags = _.get($(data).obj, 'music_list.flag', []); if (!_.isArray(flags)) flags = [flags]; for (const flag of flags) { const sheet = _.get(flag, '@attr.sheet_type', -1); if (sheet == '0') p.musicList.type_0 = _.get(flag, '@content', p.musicList.type_0); else if (sheet == '1') p.musicList.type_1 = _.get(flag, '@content', p.musicList.type_1); else if (sheet == '2') p.musicList.type_2 = _.get(flag, '@content', p.musicList.type_2); else if (sheet == '3') p.musicList.type_3 = _.get(flag, '@content', p.musicList.type_3); } let freeFlags = _.get($(data).obj, 'free_for_play_music_list.flag', []); if (!_.isArray(freeFlags)) freeFlags = [freeFlags]; for (const flag of freeFlags) { const sheet = _.get(flag, '@attr.sheet_type', -1); if (sheet == '0') p.musicList2.type_0 = _.get(flag, '@content', p.musicList2.type_0); else if (sheet == '1') p.musicList2.type_1 = _.get(flag, '@content', p.musicList2.type_1); else if (sheet == '2') p.musicList2.type_2 = _.get(flag, '@content', p.musicList2.type_2); else if (sheet == '3') p.musicList2.type_3 = _.get(flag, '@content', p.musicList2.type_3); } let params = $(data).elements('extra_param.param'); for (const param of params) { const type = param.attr().type; const parray = param.numbers('params_array'); if (type == null || parray == null) continue; p.params[type] = parray; } await writeProfile(refid, p); send.success(); return; } const isForte = version.isFirstOrForte() const p = await readProfile(refid); p.playCount = $(data).number('play_count', p.playCount); p.todayPlayCount = $(data).number('today_play_count', p.todayPlayCount); const last = $(data).element('last'); p.music = last.number('music_index', p.music); p.sheet = last.number('sheet_type', p.sheet); p.brooch = last.number('brooch_index', p.brooch); p.hispeed = last.number('hi_speed_level', p.hispeed); p.beatGuide = last.number('beat_guide', p.beatGuide); p.headphone = last.number('headphone_volume', p.headphone); p.judgeBar = last.number('judge_bar_pos', p.judgeBar); p.group = last.number('music_group', p.group); p.mode = last.number('hands_mode', p.mode); p.near = last.number('near_setting', p.near); p.offset = last.number('judge_delay_offset', p.offset); p.bingo = last.number('bingo_index', p.bingo); p.skill = `${last.bigint('total_skill_value') || p.skill}`; p.keyBeam = last.number('key_beam_level', p.keyBeam); p.orbit = last.number('orbit_type', p.orbit); p.noteHeight = last.number('note_height', p.noteHeight); p.noteWidth = last.number('note_width', p.noteWidth); p.judgeWidth = last.number('judge_width_type', p.judgeWidth); p.beatVolume = last.number('beat_guide_volume', p.beatVolume); p.beatType = last.number('beat_guide_type', p.beatType); p.keyVolume = last.number('key_volume_offset', p.keyVolume); p.bgmVolume = last.number('bgm_volume_offset', p.bgmVolume); p.note = last.number('note_disp_type', p.note); p.sf = last.number('slow_fast', p.sf); p.judgeFX = last.number('judge_effect_adjust', p.judgeFX); p.simple = last.number('simple_bg', p.simple); p.money = $(data).number('travel.money', p.money); p.fame = $(data).number('travel.fame', p.fame); p.fameId = $(data).number('travel.fame_index', p.fameId); p.island = $(data).number('travel.island_id', p.island); let flags = _.get($(data).obj, 'music_list.flag', []); if (!_.isArray(flags)) flags = [flags]; for (const flag of flags) { const sheet = _.get(flag, '@attr.sheet_type', -1); if (sheet == '0') { p.musicList.type_0 = _.get(flag, '@content', p.musicList.type_0); } else if (sheet == '1') { p.musicList.type_1 = _.get(flag, '@content', p.musicList.type_1); } else if (sheet == '2') { p.musicList.type_2 = _.get(flag, '@content', p.musicList.type_2); } else if (sheet == '3') { p.musicList.type_3 = _.get(flag, '@content', p.musicList.type_3); } } let freeFlags = _.get($(data).obj, 'free_for_play_music_list.flag', []); if (!_.isArray(freeFlags)) freeFlags = [freeFlags]; for (const flag of freeFlags) { const sheet = _.get(flag, '@attr.sheet_type', -1); if (sheet == '0') { p.musicList2.type_0 = _.get(flag, '@content', p.musicList2.type_0); } else if (sheet == '1') { p.musicList2.type_1 = _.get(flag, '@content', p.musicList2.type_1); } else if (sheet == '2') { p.musicList2.type_2 = _.get(flag, '@content', p.musicList2.type_2); } else if (sheet == '3') { p.musicList2.type_3 = _.get(flag, '@content', p.musicList2.type_3); } } // KENTEI let kenteis = $(data).elements('kentei_result_list.kentei_result'); for (const kentei of kenteis) { const index = kentei.number('kentei_index', -1); if (index < 0) continue; const clearRate = kentei.number('clear_rate', 0); const oldClearRate = _.get(p, `kentei.${index}.rate`, 0); const isHigh = clearRate >= oldClearRate; p.kentei[index] = { rate: isHigh ? clearRate : oldClearRate, score: isHigh ? kentei.number('score', 0) : _.get(p, `kentei.${index}.score`, 0), stage: Math.max(kentei.number('stage_prog', 0), _.get(p, `kentei.${index}.stage`, 0)), flag: Math.max(kentei.number('clear_flag', 0), _.get(p, `kentei.${index}.flag`, 0)), count: Math.max(kentei.number('play_count', 0), _.get(p, `kentei.${index}.count`, 0)), }; } // PARAMS let params = $(data).elements('extra_param.param'); for (const param of params) { const type = param.attr().type; const parray = param.numbers('params_array'); if (type == null || parray == null) continue; p.params[type] = parray; } // BROOCHES let broochs = $(data).elements('brooch_list.brooch'); for (const brooch of broochs) { const index = parseInt(brooch.attr().index || '-1', 10); if (index < 0) continue; p.brooches[index] = { watch: brooch.number('watch_count', 0), level: brooch.number('level', 1), steps: brooch.number('invested_steps', 0), new: brooch.number('is_new_brooch', 0), }; } // ISLAND let islands = $(data).elements('island_progress_list.island_progress'); for (const island of islands) { const index = parseInt(island.attr().index || '-1', 10); if (index < 0) continue; const containers: Profile['islands']['0']['containers'] = {}; let conts = $(data).elements('container'); for (const cont of conts) { const index = cont.number('container_no', -1); if (index < 0) continue; const rewards: { [key: string]: number } = {}; let rews = $(data).elements('reward'); for (const rew of rews) { const index = rew.number('reward_index', -1); if (index < 0) continue; rewards[index] = rew.number('point', 0); } containers[index] = { prog: cont.number('movie_prog', 1), rewards, }; } p.islands[index] = { look: island.number('lookUnlockWin', 0), select: island.number('select_container', 0), time: island.number('travelledTime', 0), containers, }; } // CAT STAIR let stairs = $(data).elements('cat_progress.stair'); if (!p.cat_stairs) { p.cat_stairs = defaultProfile.cat_stairs } for (const stair of stairs) { const index = parseInt(stair.attr().index || '-1', 10); if (index < 0) continue; p.cat_stairs[index] = { total: stair.number('total_steps', 0), index: stair.number('chapter_index', 1), steps: stair.number('chapter_steps', 0), goal: stair.number('chapter_goal', 0), }; } await writeProfile(refid, p); const scoreData = await readScores(refid); // Save Scores let stages = $(data).elements('stageinfo.stage'); for (const stage of stages) { const mid = stage.attr().music_index const type = stage.attr().sheet_type const key = `${mid}:${type}`; const c = isForte ? stage : stage.element('common'); const o = _.get(scoreData, `scores.${key}`, {}); const isHigh = c.number('score', 0) >= _.get(o, 'score', 0); scoreData.scores[key] = { score: Math.max(c.number('score', 0), _.get(o, 'score', 0)), grade: Math.max(Math.max(c.number('grade_basic', 0), c.number('evaluation', 0)), _.get(o, 'grade', 0)), recital: Math.max(c.number('grade_recital', 0), _.get(o, 'recital', 0)), mode: isHigh ? c.number('hands_mode', 0) : _.get(o, 'mode', 0), count: Math.max(c.number('play_count', 0), _.get(o, 'count', 1)), clear: c.number('clear_count', _.get(o, 'clear', 0)), multi: c.number('multi_count', _.get(o, 'multi', 0)), flag: Math.max(c.number('clear_flag', 0), _.get(o, 'flag', 0)), }; } // Save Recitals const rInfo = $(data).element('recital_info.recital'); if (rInfo) { const rIndex = rInfo.number('recital_index', -1); if (rIndex >= 0) { const r = rInfo.element('result'); const o = _.get(scoreData, `recitals.${rIndex}`); const isHigh = r.number('total_score', 0) >= _.get(o, 'score', 0); scoreData.recitals[rIndex] = { count: Math.max(rInfo.number('recital_count', 0), _.get(o, 'count', 1)), hall: isHigh ? rInfo.number('hall_index') : _.get(o, 'hall', 0), cat: isHigh ? rInfo.numbers('cat_index') : _.get(o, 'cat', [3, 7, 9, 5, 16]), audience: isHigh ? r.number('audience') : _.get(o, 'audience', 0), money: isHigh ? r.number('money') : _.get(o, 'money', 0), fame: isHigh ? r.number('fame') : _.get(o, 'fame', 0), player: isHigh ? r.number('player_fame') : _.get(o, 'player', 0), score: isHigh ? r.number('total_score', 0) : _.get(o, 'score', 0), start: (isHigh ? r.number('recital_start_time', 0) : _.get(o, 'start', 0)).toString(), end: (isHigh ? r.number('recital_end_time', 0) : _.get(o, 'end', 0)).toString(), }; } } await writeScores(refid, scoreData); send.success() }; export const get_musicdata: EPR = async (info, data, send) => { const refid = $(data).str('refid'); if (!refid) return send.deny(); const version = new NosVersionHelper(info) const scoreData = await readScores(refid); if (version.isOp3()) { const music: any[] = []; for (const m in scoreData.scores) { const mdata = m.split(':'); const musi = scoreData.scores[m]; if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue; const chart = { score: K.ITEM('s32', musi.score), play_count: K.ITEM('s32', musi.count), clear_count: K.ITEM('s32', musi.clear), multi_count: K.ITEM('s32', musi.multi), clear_flag: K.ITEM('s32', musi.flag), hands_mode: K.ITEM('s8', musi.mode), evaluation: K.ITEM('u32', 5), grade: K.ITEM('u32', musi.grade), }; music.push(K.ATTR({ music_index: mdata[0], sheet_type: mdata[1], }, { recital: chart, ...chart, })); } console.log(`[nostalgia@asphyxia] op3 get_musicdata refid=${refid} music=${music.length}`); send.object({ music }); return; } const recital_record: any[] = []; const music: any[] = []; for (const r in scoreData.recitals) { const reci = scoreData.recitals[r]; recital_record.push({ recital_index: K.ITEM('s32', parseInt(r, 10)), recital_count: K.ITEM('s32', reci.count), hall_index: K.ITEM('s8', reci.hall), cat_index: K.ARRAY('s16', reci.cat), audience: K.ITEM('s32', reci.audience), money: K.ITEM('s32', reci.money), fame: K.ITEM('s32', reci.fame), player_fame: K.ITEM('s32', reci.player), total_score: K.ITEM('s32', reci.score), total_base_score: K.ITEM('s32', reci.score), best_start_time: K.ITEM('u64', BigInt(reci.start)), best_end_time: K.ITEM('u64', BigInt(reci.end)), }); } for (const m in scoreData.scores) { const mdata = m.split(':'); const musi = scoreData.scores[m]; if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue; music.push(K.ATTR({ music_index: mdata[0], sheet_type: mdata[1], }, { score: K.ITEM('s32', musi.score), evaluation: K.ITEM('u32', musi.grade), // Forte grade_basic: K.ITEM('u32', musi.grade), grade_recital: K.ITEM('u32', musi.recital), play_count: K.ITEM('s32', musi.count), clear_count: K.ITEM('s32', musi.clear), multi_count: K.ITEM('s32', musi.multi), hands_mode: K.ITEM('s8', musi.mode), clear_flag: K.ITEM('s32', musi.flag), })); } send.object({ recital_record, music, }); }; function normalizeProfile(profile?: Profile | null): Profile { if (!profile) { return { ...defaultProfile }; } return padOp3MusicLists({ ...defaultProfile, ...profile, params: { ...defaultProfile.params, ...(profile.params || {}), }, musicList: { ...defaultProfile.musicList, ...(profile.musicList || {}), }, musicList2: { ...defaultProfile.musicList2, ...(profile.musicList2 || {}), }, brooches: { ...defaultProfile.brooches, ...(profile.brooches || {}), }, islands: profile.islands || {}, kentei: profile.kentei || {}, cat_stairs: { ...defaultProfile.cat_stairs, ...(profile.cat_stairs || {}), }, }); } async function readProfile(refid: string): Promise { const profile = await DB.FindOne(refid, { collection: 'profile' }); return normalizeProfile(profile); } 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', recitals: {}, scores: {} } } async function writeScores(refid: string, scores: Scores) { await DB.Upsert(refid, { collection: 'scores' }, scores) } const defaultProfile: Profile = { collection: 'profile', name: 'GUEST', music: 0, sheet: 0, brooch: 0, hispeed: 0, beatGuide: 0, headphone: 0, judgeBar: 250, group: 0, mode: 0, near: 0, offset: 0, bingo: 0, skill: '0', playCount: 0, todayPlayCount: 0, keyBeam: 0, orbit: 0, noteHeight: 10, noteWidth: 10, judgeWidth: 10, beatVolume: 0, beatType: 0, keyVolume: 0, bgmVolume: 0, note: 0, sf: 0, judgeFX: 0, simple: 0, money: 0, fame: 0, fameId: 0, island: 0, performType: 0, filterFlag: 0, optionSetting: 0, classBasic: 0, classRecital: 0, gradeBasic: 0, gradeRecital: 0, pianistPower: 0, kingdomId: 0, questIndex: 0, oldPlayCount: 0, oldRecitalCount: 0, brooches: { '1': { level: 1, watch: 0, steps: 0, new: 0, }, }, islands: {}, kentei: {}, cat_stairs: { '0': { total: 0, index: 0, steps: 0, goal: 0 } }, params: { '1': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], '2': [64, 0, 0, 0, 0, 0, 0, 0], }, musicList: { type_0: Array(32).fill(-1), type_1: Array(32).fill(-1), type_2: Array(32).fill(-1), type_3: Array(32).fill(-1), }, musicList2: { type_0: Array(32).fill(-1), type_1: Array(32).fill(-1), type_2: Array(32).fill(-1), type_3: Array(32).fill(-1), }, }