plugins/gitadora@asphyxia/handlers/profiles.ts
2022-05-16 06:47:59 +02:00

1177 lines
37 KiB
TypeScript

import { PlayerInfo } from "../models/playerinfo";
import { PlayerRanking } from "../models/playerranking";
import { Profile } from "../models/profile";
import { Record } from "../models/record";
import { Extra } from "../models/extra";
import { getVersion, isDM } from "../utils";
import { Scores } from "../models/scores";
import { PlayerStickerResponse } from "../models/playerstickerresponse";
import { SecretMusicResponse } from "../models/secretmusicresponse";
import { PLUGIN_VER } from "../const";
import Logger from "../utils/logger"
import { isAsphyxiaDebugMode } from "../Utils/index";
import { SecretMusicEntry } from "../models/secretmusicentry";
import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic";
const logger = new Logger("profiles")
export const regist: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info);
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no);
await send.object({
player: K.ATTR({ no: `${no}` }, {
is_succession: K.ITEM("bool", 0), //FIX THIS with upsert result.
did: K.ITEM("s32", playerInfo.id)
})
})
}
export const check: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info)
const playerInfo = await getOrRegisterPlayerInfo(refid, version, no)
await send.object({
player: K.ATTR({ no: `${no}`, state: '2' }, {
name: K.ITEM('str', playerInfo.name),
charaid: K.ITEM('s32', 0),
did: K.ITEM('s32', playerInfo.id),
skilldata: {
skill: K.ITEM('s32', 0),
all_skill: K.ITEM('s32', 0),
old_skill: K.ITEM('s32', 0),
old_all_skill: K.ITEM('s32', 0),
}
})
})
}
export const getPlayer: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) {
logger.error("Request data is missing required parameter: player.refid")
return send.deny();
}
const no = getPlayerNo(data);
const version = getVersion(info);
const time = BigInt(31536000);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`)
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
const dmProfile = await getProfile(refid, version, 'dm')
const gfProfile = await getProfile(refid, version, 'gf')
const dmRecord = await getRecord(refid, version, 'dm')
const gfRecord = await getRecord(refid, version, 'gf')
const dmExtra = await getExtra(refid, version, 'dm')
const gfExtra = await getExtra(refid, version, 'gf')
const dmScores = (await getScore(refid, version, 'dm')).scores
const gfScores = (await getScore(refid, version, 'gf')).scores
const profile = dm ? dmProfile : gfProfile;
const extra = dm ? dmExtra : gfExtra;
await applySharedFavoriteMusicToExtra(refid, extra)
const record: any = {
gf: {},
dm: {},
};
for (const mode of ['dm', 'gf']) {
let game = mode == 'gf' ? gfProfile : dmProfile;
let rec = mode == 'gf' ? gfRecord : dmRecord;
record[mode] = {
max_record: {
skill: K.ITEM('s32', game.max_skill),
all_skill: K.ITEM('s32', game.max_all_skill),
clear_diff: K.ITEM('s32', game.clear_diff),
full_diff: K.ITEM('s32', game.full_diff),
exce_diff: K.ITEM('s32', game.exce_diff),
clear_music_num: K.ITEM('s32', game.clear_music_num),
full_music_num: K.ITEM('s32', game.full_music_num),
exce_music_num: K.ITEM('s32', game.exce_music_num),
clear_seq_num: K.ITEM('s32', game.clear_seq_num),
classic_all_skill: K.ITEM('s32', game.classic_all_skill),
},
diff_record: {
diff_100_nr: K.ITEM('s32', rec.diff_100_nr),
diff_150_nr: K.ITEM('s32', rec.diff_150_nr),
diff_200_nr: K.ITEM('s32', rec.diff_200_nr),
diff_250_nr: K.ITEM('s32', rec.diff_250_nr),
diff_300_nr: K.ITEM('s32', rec.diff_300_nr),
diff_350_nr: K.ITEM('s32', rec.diff_350_nr),
diff_400_nr: K.ITEM('s32', rec.diff_400_nr),
diff_450_nr: K.ITEM('s32', rec.diff_450_nr),
diff_500_nr: K.ITEM('s32', rec.diff_500_nr),
diff_550_nr: K.ITEM('s32', rec.diff_550_nr),
diff_600_nr: K.ITEM('s32', rec.diff_600_nr),
diff_650_nr: K.ITEM('s32', rec.diff_650_nr),
diff_700_nr: K.ITEM('s32', rec.diff_700_nr),
diff_750_nr: K.ITEM('s32', rec.diff_750_nr),
diff_800_nr: K.ITEM('s32', rec.diff_800_nr),
diff_850_nr: K.ITEM('s32', rec.diff_850_nr),
diff_900_nr: K.ITEM('s32', rec.diff_900_nr),
diff_950_nr: K.ITEM('s32', rec.diff_950_nr),
diff_100_clear: K.ARRAY('s32', rec.diff_100_clear),
diff_150_clear: K.ARRAY('s32', rec.diff_150_clear),
diff_200_clear: K.ARRAY('s32', rec.diff_200_clear),
diff_250_clear: K.ARRAY('s32', rec.diff_250_clear),
diff_300_clear: K.ARRAY('s32', rec.diff_300_clear),
diff_350_clear: K.ARRAY('s32', rec.diff_350_clear),
diff_400_clear: K.ARRAY('s32', rec.diff_400_clear),
diff_450_clear: K.ARRAY('s32', rec.diff_450_clear),
diff_500_clear: K.ARRAY('s32', rec.diff_500_clear),
diff_550_clear: K.ARRAY('s32', rec.diff_550_clear),
diff_600_clear: K.ARRAY('s32', rec.diff_600_clear),
diff_650_clear: K.ARRAY('s32', rec.diff_650_clear),
diff_700_clear: K.ARRAY('s32', rec.diff_700_clear),
diff_750_clear: K.ARRAY('s32', rec.diff_750_clear),
diff_800_clear: K.ARRAY('s32', rec.diff_800_clear),
diff_850_clear: K.ARRAY('s32', rec.diff_850_clear),
diff_900_clear: K.ARRAY('s32', rec.diff_900_clear),
diff_950_clear: K.ARRAY('s32', rec.diff_950_clear),
},
};
}
// Format scores
const musicdata = [];
const scores = dm ? dmScores : gfScores;
for (const [musicid, score] of _.entries(scores)) {
musicdata.push(K.ATTR({ musicid }, {
mdata: K.ARRAY('s16', [
-1,
_.get(score, 'diffs.1.perc', -2),
_.get(score, 'diffs.2.perc', -2),
_.get(score, 'diffs.3.perc', -2),
_.get(score, 'diffs.4.perc', -2),
_.get(score, 'diffs.5.perc', -2),
_.get(score, 'diffs.6.perc', -2),
_.get(score, 'diffs.7.perc', -2),
_.get(score, 'diffs.8.perc', -2),
_.get(score, 'diffs.1.rank', 0),
_.get(score, 'diffs.2.rank', 0),
_.get(score, 'diffs.3.rank', 0),
_.get(score, 'diffs.4.rank', 0),
_.get(score, 'diffs.5.rank', 0),
_.get(score, 'diffs.6.rank', 0),
_.get(score, 'diffs.7.rank', 0),
_.get(score, 'diffs.8.rank', 0),
0,
0,
0,
]),
flag: K.ARRAY('u16', [
_.get(score, 'diffs.1.fc', false) * 2 +
_.get(score, 'diffs.2.fc', false) * 4 +
_.get(score, 'diffs.3.fc', false) * 8 +
_.get(score, 'diffs.4.fc', false) * 16 +
_.get(score, 'diffs.5.fc', false) * 32 +
_.get(score, 'diffs.6.fc', false) * 64 +
_.get(score, 'diffs.7.fc', false) * 128 +
_.get(score, 'diffs.8.fc', false) * 256,
_.get(score, 'diffs.1.ex', false) * 2 +
_.get(score, 'diffs.2.ex', false) * 4 +
_.get(score, 'diffs.3.ex', false) * 8 +
_.get(score, 'diffs.4.ex', false) * 16 +
_.get(score, 'diffs.5.ex', false) * 32 +
_.get(score, 'diffs.6.ex', false) * 64 +
_.get(score, 'diffs.7.ex', false) * 128 +
_.get(score, 'diffs.8.ex', false) * 256,
_.get(score, 'diffs.1.clear', false) * 2 +
_.get(score, 'diffs.2.clear', false) * 4 +
_.get(score, 'diffs.3.clear', false) * 8 +
_.get(score, 'diffs.4.clear', false) * 16 +
_.get(score, 'diffs.5.clear', false) * 32 +
_.get(score, 'diffs.6.clear', false) * 64 +
_.get(score, 'diffs.7.clear', false) * 128 +
_.get(score, 'diffs.8.clear', false) * 256,
0,
0,
]),
sdata: K.ARRAY('s16', score.update),
meter: K.ARRAY('u64', [
BigInt(_.get(score, 'diffs.1.meter', '0')),
BigInt(_.get(score, 'diffs.2.meter', '0')),
BigInt(_.get(score, 'diffs.3.meter', '0')),
BigInt(_.get(score, 'diffs.4.meter', '0')),
BigInt(_.get(score, 'diffs.5.meter', '0')),
BigInt(_.get(score, 'diffs.6.meter', '0')),
BigInt(_.get(score, 'diffs.7.meter', '0')),
BigInt(_.get(score, 'diffs.8.meter', '0')),
]),
meter_prog: K.ARRAY('s16', [
_.get(score, 'diffs.1.prog', 0),
_.get(score, 'diffs.2.prog', 0),
_.get(score, 'diffs.3.prog', 0),
_.get(score, 'diffs.4.prog', 0),
_.get(score, 'diffs.5.prog', 0),
_.get(score, 'diffs.6.prog', 0),
_.get(score, 'diffs.7.prog', 0),
_.get(score, 'diffs.8.prog', 0),
]),
}));
}
const sticker: PlayerStickerResponse[] = getPlayerStickers(name.card);
const playerData: any = {
playerboard: {
index: K.ITEM('s32', 1),
is_active: K.ITEM('bool', _.isArray(name.card) ? 1 : 0),
sticker,
},
player_info: {
player_type: K.ITEM('s8', 0),
did: K.ITEM('s32', 13376666),
name: K.ITEM('str', name.name),
title: K.ITEM('str', name.title),
charaid: K.ITEM('s32', 0),
},
customdata: {
playstyle: K.ARRAY('s32', extra.playstyle),
custom: K.ARRAY('s32', extra.custom),
},
playinfo: {
cabid: K.ITEM('s32', 0),
play: K.ITEM('s32', profile.play),
playtime: K.ITEM('s32', profile.playtime),
playterm: K.ITEM('s32', profile.playterm),
session_cnt: K.ITEM('s32', profile.session_cnt),
matching_num: K.ITEM('s32', 0),
extra_stage: K.ITEM('s32', profile.extra_stage),
extra_play: K.ITEM('s32', profile.extra_play),
extra_clear: K.ITEM('s32', profile.extra_clear),
encore_play: K.ITEM('s32', profile.encore_play),
encore_clear: K.ITEM('s32', profile.encore_clear),
pencore_play: K.ITEM('s32', profile.pencore_play),
pencore_clear: K.ITEM('s32', profile.pencore_clear),
max_clear_diff: K.ITEM('s32', profile.max_clear_diff),
max_full_diff: K.ITEM('s32', profile.max_full_diff),
max_exce_diff: K.ITEM('s32', profile.max_exce_diff),
clear_num: K.ITEM('s32', profile.clear_num),
full_num: K.ITEM('s32', profile.full_num),
exce_num: K.ITEM('s32', profile.exce_num),
no_num: K.ITEM('s32', profile.no_num),
e_num: K.ITEM('s32', profile.e_num),
d_num: K.ITEM('s32', profile.d_num),
c_num: K.ITEM('s32', profile.c_num),
b_num: K.ITEM('s32', profile.b_num),
a_num: K.ITEM('s32', profile.a_num),
s_num: K.ITEM('s32', profile.s_num),
ss_num: K.ITEM('s32', profile.ss_num),
last_category: K.ITEM('s32', profile.last_category),
last_musicid: K.ITEM('s32', profile.last_musicid),
last_seq: K.ITEM('s32', profile.last_seq),
disp_level: K.ITEM('s32', profile.disp_level),
},
tutorial: {
progress: K.ITEM('s32', profile.progress),
disp_state: K.ITEM('u32', profile.disp_state),
},
skilldata: {
skill: K.ITEM('s32', profile.skill),
all_skill: K.ITEM('s32', profile.all_skill),
old_skill: K.ITEM('s32', 0),
old_all_skill: K.ITEM('s32', 0),
},
favoritemusic: {
list_1: K.ARRAY('s32', extra.list_1),
list_2: K.ARRAY('s32', extra.list_2),
list_3: K.ARRAY('s32', extra.list_3),
},
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
record,
groove: {
extra_gauge: K.ITEM('s32', profile.extra_gauge),
encore_gauge: K.ITEM('s32', profile.encore_gauge),
encore_cnt: K.ITEM('s32', profile.encore_cnt),
encore_success: K.ITEM('s32', profile.encore_success),
unlock_point: K.ITEM('s32', profile.unlock_point),
},
musiclist: { '@attr': { nr: musicdata.length }, musicdata },
};
const playerRanking = await getPlayerRanking(refid, version, game)
const addition: any = {
monstar_subjugation: {},
bear_fes: {},
};
for (let i = 1; i <= 20; ++i) {
const obj = { point: K.ITEM('s32', 0) };
if (i == 1) {
addition['long_otobear_fes_1'] = obj;
addition['phrase_combo_challenge'] = obj;
addition['sdvx_stamprally3'] = obj;
addition['chronicle_1'] = obj;
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
if (i <= 4) {
addition.bear_fes[`bear_fes_${i}`] = {
stage: K.ITEM('s32', 0),
point: K.ARRAY('s32', [0, 0, 0, 0, 0, 0, 0, 0]),
};
}
if (i <= 3) {
addition.monstar_subjugation[`monstar_subjugation_${i}`] = {
stage: K.ITEM('s32', 0),
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
};
addition[`kouyou_challenge_${i}`] = { point: K.ITEM('s32', 0) };
}
}
const innerSecretMusic = getSecretMusicResponse(profile)
const innerFriendData = getFriendDataResponse(profile)
const response = {
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: {
music: innerSecretMusic
},
chara_list: {},
title_parts: {},
information: {
info: K.ARRAY('u32', Array(50).fill(0)),
},
reward: {
status: K.ARRAY('u32', extra.reward_status ?? Array(50).fill(0)),
},
rivaldata: {},
frienddata: {
friend: innerFriendData
},
thanks_medal: {
medal: K.ITEM('s32', 0),
grant_medal: K.ITEM('s32', 0),
grant_total_medal: K.ITEM('s32', 0),
},
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
skindata: {
skin: K.ARRAY('u32', Array(100).fill(-1)),
},
battledata: {
info: {
orb: K.ITEM('s32', 0),
get_gb_point: K.ITEM('s32', 0),
send_gb_point: K.ITEM('s32', 0),
},
greeting: {
greeting_1: K.ITEM('str', ''),
greeting_2: K.ITEM('str', ''),
greeting_3: K.ITEM('str', ''),
greeting_4: K.ITEM('str', ''),
greeting_5: K.ITEM('str', ''),
greeting_6: K.ITEM('str', ''),
greeting_7: K.ITEM('str', ''),
greeting_8: K.ITEM('str', ''),
greeting_9: K.ITEM('str', ''),
},
setting: {
matching: K.ITEM('s32', 0),
info_level: K.ITEM('s32', 0),
},
score: {
battle_class: K.ITEM('s32', 0),
max_battle_class: K.ITEM('s32', 0),
battle_point: K.ITEM('s32', 0),
win: K.ITEM('s32', 0),
lose: K.ITEM('s32', 0),
draw: K.ITEM('s32', 0),
consecutive_win: K.ITEM('s32', 0),
max_consecutive_win: K.ITEM('s32', 0),
glorious_win: K.ITEM('s32', 0),
max_defeat_skill: K.ITEM('s32', 0),
latest_result: K.ITEM('s32', 0),
},
history: {},
},
is_free_ok: K.ITEM('bool', 0),
ranking: {
skill: { rank: K.ITEM('s32', playerRanking.skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
all_skill: { rank: K.ITEM('s32', playerRanking.all_skill), total_nr: K.ITEM('s32', playerRanking.totalPlayers) },
},
stage_result: {},
monthly_skill: {},
event_skill: {
skill: K.ITEM('s32', 0),
ranking: {
rank: K.ITEM('s32', 0),
total_nr: K.ITEM('s32', 0),
},
eventlist: {},
},
event_score: { eventlist: {} },
rockwave: { score_list: {} },
jubeat_omiyage_challenge: {},
light_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
standard_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
delux_mode_reward_item: { itemid: K.ITEM('s32', -1), rarity: K.ITEM('s32', 0) },
kac2018: {
entry_status: K.ITEM('s32', 0),
data: {
term: K.ITEM('s32', 0),
total_score: K.ITEM('s32', 0),
score: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
music_type: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
play_count: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
sticker_campaign: {},
kac2017: {
entry_status: K.ITEM('s32', 0),
},
nostalgia_concert: {},
bemani_summer_2018: {
linkage_id: K.ITEM('s32', -1),
is_entry: K.ITEM('bool', 0),
target_music_idx: K.ITEM('s32', -1),
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
point_4: K.ITEM('s32', 0),
point_5: K.ITEM('s32', 0),
point_6: K.ITEM('s32', 0),
point_7: K.ITEM('s32', 0),
reward_1: K.ITEM('bool', 0),
reward_2: K.ITEM('bool', 0),
reward_3: K.ITEM('bool', 0),
reward_4: K.ITEM('bool', 0),
reward_5: K.ITEM('bool', 0),
reward_6: K.ITEM('bool', 0),
reward_7: K.ITEM('bool', 0),
unlock_status_1: K.ITEM('s32', 0),
unlock_status_2: K.ITEM('s32', 0),
unlock_status_3: K.ITEM('s32', 0),
unlock_status_4: K.ITEM('s32', 0),
unlock_status_5: K.ITEM('s32', 0),
unlock_status_6: K.ITEM('s32', 0),
unlock_status_7: K.ITEM('s32', 0),
},
thanksgiving: {
term: K.ITEM("u8", 0),
score: {
one_day_play_cnt: K.ITEM("s32", 0),
one_day_lottery_cnt: K.ITEM("s32", 0),
lucky_star: K.ITEM("s32", 0),
bear_mark: K.ITEM("s32", 0),
play_date_ms: K.ITEM("u64", BigInt(0))
},
lottery_result: {
unlock_bit: K.ITEM("u64", BigInt(0))
}
},
lotterybox: {},
...addition,
...playerData,
finish: K.ITEM('bool', 1),
}),
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastGetPlayerRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastGetPlayerResponse.json`, JSON.stringify(response, null, 4))
}
send.object(response);
}
function getPlayerStickers(playerCard) : PlayerStickerResponse[] {
let stickers : PlayerStickerResponse[] = []
if (_.isArray(playerCard)) {
for (const item of playerCard) {
const id = _.get(item, 'id');
const posX = _.get(item, 'position.0');
const posY = _.get(item, 'position.1');
const scaleX = _.get(item, 'scale.0');
const scaleY = _.get(item, 'scale.1');
const rotation = _.get(item, 'rotation');
if (
!isFinite(id) ||
!isFinite(posX) ||
!isFinite(posY) ||
!isFinite(scaleX) ||
!isFinite(scaleY) ||
!isFinite(rotation)
) {
continue;
}
stickers.push({
id: K.ITEM('s32', id),
pos_x: K.ITEM('float', posX),
pos_y: K.ITEM('float', posY),
scale_x: K.ITEM('float', scaleX),
scale_y: K.ITEM('float', scaleY),
rotate: K.ITEM('float', rotation),
});
}
}
return stickers
}
async function getOrRegisterPlayerInfo(refid: string, version: string, no: number) {
let playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
});
if (!playerInfo) {
logger.debugInfo(`Registering new profile for player ${no} with refid: ${refid}`);
playerInfo = await registerUser(refid, version);
}
return playerInfo;
}
function getPlayerNo(data: any): number {
return parseInt($(data).attr("player").no || '1', 10)
}
async function registerUser(refid: string, version: string, id = _.random(0, 99999999)) {
while (await DB.FindOne<PlayerInfo>(null, { collecttion: 'profile', id })) {
id = _.random(0, 99999999);
}
const defaultInfo: PlayerInfo = {
collection: 'playerinfo',
pluginVer: PLUGIN_VER,
id,
version,
name: 'ASPHYXIA-CORE USER',
title: 'Please edit on WebUI',
}
const defaultProfile = (game: 'gf' | 'dm'): Profile => {
return {
collection: 'profile',
pluginVer: PLUGIN_VER,
game,
version,
id,
play: 0,
playtime: 0,
playterm: 0,
session_cnt: 0,
extra_stage: 0,
extra_play: 0,
extra_clear: 0,
encore_play: 0,
encore_clear: 0,
pencore_play: 0,
pencore_clear: 0,
max_clear_diff: 0,
max_full_diff: 0,
max_exce_diff: 0,
clear_num: 0,
full_num: 0,
exce_num: 0,
no_num: 0,
e_num: 0,
d_num: 0,
c_num: 0,
b_num: 0,
a_num: 0,
s_num: 0,
ss_num: 0,
last_category: 0,
last_musicid: -1,
last_seq: 0,
disp_level: 0,
progress: 0,
disp_state: 0,
skill: 0,
all_skill: 0,
extra_gauge: 0,
encore_gauge: 0,
encore_cnt: 0,
encore_success: 0,
unlock_point: 0,
max_skill: 0,
max_all_skill: 0,
clear_diff: 0,
full_diff: 0,
exce_diff: 0,
clear_music_num: 0,
full_music_num: 0,
exce_music_num: 0,
clear_seq_num: 0,
classic_all_skill: 0,
secretmusic: {
music: []
}
}
};
const defaultRecord = (game: 'gf' | 'dm'): Record => {
return {
collection: 'record',
pluginVer: PLUGIN_VER,
game,
version,
diff_100_nr: 0,
diff_150_nr: 0,
diff_200_nr: 0,
diff_250_nr: 0,
diff_300_nr: 0,
diff_350_nr: 0,
diff_400_nr: 0,
diff_450_nr: 0,
diff_500_nr: 0,
diff_550_nr: 0,
diff_600_nr: 0,
diff_650_nr: 0,
diff_700_nr: 0,
diff_750_nr: 0,
diff_800_nr: 0,
diff_850_nr: 0,
diff_900_nr: 0,
diff_950_nr: 0,
diff_100_clear: [0, 0, 0, 0, 0, 0, 0],
diff_150_clear: [0, 0, 0, 0, 0, 0, 0],
diff_200_clear: [0, 0, 0, 0, 0, 0, 0],
diff_250_clear: [0, 0, 0, 0, 0, 0, 0],
diff_300_clear: [0, 0, 0, 0, 0, 0, 0],
diff_350_clear: [0, 0, 0, 0, 0, 0, 0],
diff_400_clear: [0, 0, 0, 0, 0, 0, 0],
diff_450_clear: [0, 0, 0, 0, 0, 0, 0],
diff_500_clear: [0, 0, 0, 0, 0, 0, 0],
diff_550_clear: [0, 0, 0, 0, 0, 0, 0],
diff_600_clear: [0, 0, 0, 0, 0, 0, 0],
diff_650_clear: [0, 0, 0, 0, 0, 0, 0],
diff_700_clear: [0, 0, 0, 0, 0, 0, 0],
diff_750_clear: [0, 0, 0, 0, 0, 0, 0],
diff_800_clear: [0, 0, 0, 0, 0, 0, 0],
diff_850_clear: [0, 0, 0, 0, 0, 0, 0],
diff_900_clear: [0, 0, 0, 0, 0, 0, 0],
diff_950_clear: [0, 0, 0, 0, 0, 0, 0],
}
}
const defaultExtra = (game: 'gf' | 'dm'): Extra => {
return {
collection: 'extra',
pluginVer: PLUGIN_VER,
game,
version,
id,
playstyle: [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
20,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
20,
0,
],
custom: Array(50).fill(0),
list_1: Array(100).fill(-1),
list_2: Array(100).fill(-1),
list_3: Array(100).fill(-1),
recommend_musicid_list: Array(5).fill(-1),
reward_status: Array(50).fill(0),
}
}
const defaultScores = (game: 'gf' | 'dm'): Scores => {
return {
collection: 'scores',
version,
pluginVer: PLUGIN_VER,
game,
scores: {}
}
};
const gf = { game: 'gf', version };
const dm = { game: 'dm', version };
await DB.Upsert(refid, { collection: 'playerinfo', version }, defaultInfo);
await DB.Upsert(refid, { collection: 'profile', ...gf }, defaultProfile('gf'));
await DB.Upsert(refid, { collection: 'profile', ...dm }, defaultProfile('dm'));
await DB.Upsert(refid, { collection: 'record', ...gf }, defaultRecord('gf'));
await DB.Upsert(refid, { collection: 'record', ...dm }, defaultRecord('dm'));
await DB.Upsert(refid, { collection: 'extra', ...gf }, defaultExtra('gf'));
await DB.Upsert(refid, { collection: 'extra', ...dm }, defaultExtra('dm'));
await DB.Upsert(refid, { collection: 'scores', ...gf }, defaultScores('gf'));
await DB.Upsert(refid, { collection: 'scores', ...dm }, defaultScores('dm'));
return defaultInfo
}
export const savePlayers: EPR = async (info, data, send) => {
const version = getVersion(info);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
let players = $(data).elements("player")
let response = {
player: [],
gamemode: _.get(data, 'gamemode'),
};
try
{
for (let player of players) {
const no = parseInt(player.attr().no || '1', 10)
// Only save players that are using a profile. Don't try to save guest players.
const hasCard = player.attr().card === 'use'
if (!hasCard) {
logger.debugInfo(`Skipping save for guest ${game} player ${no}.`)
continue
}
const refid = player.str('refid')
if (!refid) {
throw "Request data is missing required parameter: player.refid"
}
await saveSinglePlayer(player, refid, no, version, game);
let ranking = await getPlayerRanking(refid, version, game)
let responsePart = getSaveProfileResponse(no, ranking)
response.player.push(responsePart)
}
if (isAsphyxiaDebugMode()) {
await IO.WriteFile(`apisamples/lastSavePlayersRequest.json`, JSON.stringify(data, null, 4))
await IO.WriteFile(`apisamples/lastSavePlayersResponse.json`, JSON.stringify(response, null, 4))
}
await send.object(response);
}
catch (e) {
logger.error(e)
logger.error(e.stack)
return send.deny();
}
};
async function saveSinglePlayer(dataplayer: KDataReader, refid: string, no: number, version: string, game: 'gf' | 'dm')
{
logger.debugInfo(`Saving ${game} profile for player ${no} with refid: ${refid}`)
const profile = await getProfile(refid, version, game) as any;
const extra = await getExtra(refid, version, game) as any;
const rec = await getRecord(refid, version, game) as any;
const autoSet = function (field: keyof Profile, path: string, array = false): void {
if (array) {
profile[field] = dataplayer.numbers(path, profile[field])
} else {
profile[field] = dataplayer.number(path, profile[field])
}
};
const autoExtra = (field: keyof Extra, path: string, array = false): void => {
if (array) {
extra[field] = dataplayer.numbers(path, extra[field])
} else {
extra[field] = dataplayer.number(path, extra[field])
}
};
const autoRec = (field: keyof Record, path: string, array = false): void => {
if (array) {
rec[field] = dataplayer.numbers(path, rec[field])
} else {
rec[field] = dataplayer.number(path, rec[field])
}
};
let newSecretMusic = parseSecretMusic(dataplayer)
profile.secretmusic = {
music: newSecretMusic
}
autoSet('max_skill', 'record.max.skill');
autoSet('max_all_skill', 'record.max.all_skill');
autoSet('clear_diff', 'record.max.clear_diff');
autoSet('full_diff', 'record.max.full_diff');
autoSet('exce_diff', 'record.max.exce_diff');
autoSet('clear_music_num', 'record.max.clear_music_num');
autoSet('full_music_num', 'record.max.full_music_num');
autoSet('exce_music_num', 'record.max.exce_music_num');
autoSet('clear_seq_num', 'record.max.clear_seq_num');
autoSet('classic_all_skill', 'record.max.classic_all_skill');
autoSet('play', 'playinfo.play');
autoSet('playtime', 'playinfo.playtime');
autoSet('playterm', 'playinfo.playterm');
autoSet('session_cnt', 'playinfo.session_cnt');
autoSet('extra_stage', 'playinfo.extra_stage');
autoSet('extra_play', 'playinfo.extra_play');
autoSet('extra_clear', 'playinfo.extra_clear');
autoSet('encore_play', 'playinfo.encore_play');
autoSet('encore_clear', 'playinfo.encore_clear');
autoSet('pencore_play', 'playinfo.pencore_play');
autoSet('pencore_clear', 'playinfo.pencore_clear');
autoSet('max_clear_diff', 'playinfo.max_clear_diff');
autoSet('max_full_diff', 'playinfo.max_full_diff');
autoSet('max_exce_diff', 'playinfo.max_exce_diff');
autoSet('clear_num', 'playinfo.clear_num');
autoSet('full_num', 'playinfo.full_num');
autoSet('exce_num', 'playinfo.exce_num');
autoSet('no_num', 'playinfo.no_num');
autoSet('e_num', 'playinfo.e_num');
autoSet('d_num', 'playinfo.d_num');
autoSet('c_num', 'playinfo.c_num');
autoSet('b_num', 'playinfo.b_num');
autoSet('a_num', 'playinfo.a_num');
autoSet('s_num', 'playinfo.s_num');
autoSet('ss_num', 'playinfo.ss_num');
autoSet('last_category', 'playinfo.last_category');
autoSet('last_musicid', 'playinfo.last_musicid');
autoSet('last_seq', 'playinfo.last_seq');
autoSet('disp_level', 'playinfo.disp_level');
autoSet('extra_gauge', 'groove.extra_gauge');
autoSet('encore_gauge', 'groove.encore_gauge');
autoSet('encore_cnt', 'groove.encore_cnt');
autoSet('encore_success', 'groove.encore_success');
autoSet('unlock_point', 'groove.unlock_point');
autoSet('progress', 'tutorial.progress');
autoSet('disp_state', 'tutorial.disp_state');
autoSet('skill', 'skilldata.skill');
autoSet('all_skill', 'skilldata.all_skill');
autoRec('diff_100_nr', 'record.diff.diff_100_nr');
autoRec('diff_150_nr', 'record.diff.diff_150_nr');
autoRec('diff_200_nr', 'record.diff.diff_200_nr');
autoRec('diff_250_nr', 'record.diff.diff_250_nr');
autoRec('diff_300_nr', 'record.diff.diff_300_nr');
autoRec('diff_350_nr', 'record.diff.diff_350_nr');
autoRec('diff_400_nr', 'record.diff.diff_400_nr');
autoRec('diff_450_nr', 'record.diff.diff_450_nr');
autoRec('diff_500_nr', 'record.diff.diff_500_nr');
autoRec('diff_550_nr', 'record.diff.diff_550_nr');
autoRec('diff_600_nr', 'record.diff.diff_600_nr');
autoRec('diff_650_nr', 'record.diff.diff_650_nr');
autoRec('diff_700_nr', 'record.diff.diff_700_nr');
autoRec('diff_750_nr', 'record.diff.diff_750_nr');
autoRec('diff_800_nr', 'record.diff.diff_800_nr');
autoRec('diff_850_nr', 'record.diff.diff_850_nr');
autoRec('diff_900_nr', 'record.diff.diff_900_nr');
autoRec('diff_950_nr', 'record.diff.diff_950_nr');
autoRec('diff_100_clear', 'record.diff.diff_100_clear', true);
autoRec('diff_150_clear', 'record.diff.diff_150_clear', true);
autoRec('diff_200_clear', 'record.diff.diff_200_clear', true);
autoRec('diff_250_clear', 'record.diff.diff_250_clear', true);
autoRec('diff_300_clear', 'record.diff.diff_300_clear', true);
autoRec('diff_350_clear', 'record.diff.diff_350_clear', true);
autoRec('diff_400_clear', 'record.diff.diff_400_clear', true);
autoRec('diff_450_clear', 'record.diff.diff_450_clear', true);
autoRec('diff_500_clear', 'record.diff.diff_500_clear', true);
autoRec('diff_550_clear', 'record.diff.diff_550_clear', true);
autoRec('diff_600_clear', 'record.diff.diff_600_clear', true);
autoRec('diff_650_clear', 'record.diff.diff_650_clear', true);
autoRec('diff_700_clear', 'record.diff.diff_700_clear', true);
autoRec('diff_750_clear', 'record.diff.diff_750_clear', true);
autoRec('diff_800_clear', 'record.diff.diff_800_clear', true);
autoRec('diff_850_clear', 'record.diff.diff_850_clear', true);
autoRec('diff_900_clear', 'record.diff.diff_900_clear', true);
autoRec('diff_950_clear', 'record.diff.diff_950_clear', true);
autoExtra('list_1', 'favoritemusic.music_list_1', true);
autoExtra('list_2', 'favoritemusic.music_list_2', true);
autoExtra('list_3', 'favoritemusic.music_list_3', true);
autoExtra('recommend_musicid_list', 'recommend_musicid_list', true);
autoExtra('playstyle', 'customdata.playstyle', true);
autoExtra('custom', 'customdata.custom', true);
autoExtra('reward_status', 'reward.status', true)
await DB.Upsert(refid, { collection: 'profile', game, version }, profile)
await DB.Upsert(refid, { collection: 'record', game, version }, rec)
await DB.Upsert(refid, { collection: 'extra', game, version }, extra)
const playedStages = dataplayer.elements('stage');
logStagesPlayed(playedStages)
const scores = await updatePlayerScoreCollection(refid, playedStages, version, game)
await saveScore(refid, version, game, scores);
await saveSharedFavoriteMusicFromExtra(refid, extra)
}
async function updatePlayerScoreCollection(refid, playedStages, version, game) {
const scores = (await getScore(refid, version, game)).scores;
for (const stage of playedStages) {
const mid = stage.number('musicid', -1);
const seq = stage.number('seq', -1);
if (mid < 0 || seq < 0) continue;
// const skill = stage.number('skill', 0);
const newSkill = stage.number('new_skill', 0);
const clear = stage.bool('clear');
const fc = stage.bool('fullcombo');
const ex = stage.bool('excellent');
const perc = stage.number('perc', 0);
const rank = stage.number('rank', 0);
const meter = stage.bigint('meter', BigInt(0));
const prog = stage.number('meter_prog', 0);
if(!scores[mid]) {
scores[mid] = {
update: [0, 0],
diffs: {}
}
}
if (newSkill > scores[mid].update[1]) {
scores[mid].update[0] = seq;
scores[mid].update[1] = newSkill;
}
scores[mid].diffs[seq] = { //FIXME: Real server is bit complicated. this one is too buggy.
perc: Math.max(_.get(scores[mid].diffs[seq], 'perc', 0), perc),
rank: Math.max(_.get(scores[mid].diffs[seq], 'rank', 0), rank),
meter: meter.toString(),
prog: Math.max(_.get(scores[mid].diffs[seq], 'prog', 0), prog),
clear: _.get(scores[mid].diffs[seq], 'clear') || clear,
fc: _.get(scores[mid].diffs[seq], 'fc') || fc,
ex: _.get(scores[mid].diffs[seq], 'ex') || ex,
};
}
return scores
}
async function getPlayerRanking(refid: string, version: string, game: 'gf' | 'dm') : Promise<PlayerRanking> {
let profiles = await getAllProfiles(version, game)
let playerCount = profiles.length
let sortedProfilesA = profiles.sort((a,b) => b.skill - a.skill)
let sortedProfilesB = profiles.sort((a,b) => b.all_skill - a.all_skill)
let idxA = _.findIndex(sortedProfilesA, (e) => e.__refid === refid)
idxA = idxA > -1 ? idxA + 1 : playerCount // Default to last place if not found in the DB.
let idxB = _.findIndex(sortedProfilesB, (e) => e.__refid === refid)
idxB = idxB > -1 ? idxB + 1 : playerCount // Default to last place if not found in the DB.
return {
refid,
skill: idxA,
all_skill: idxB,
totalPlayers: playerCount
}
}
function getSaveProfileResponse(playerNo: number, ranking : PlayerRanking)
{
const result = K.ATTR({ no: `${playerNo}` }, {
skill: { rank: K.ITEM('s32', ranking.skill), total_nr: K.ITEM('s32', ranking.totalPlayers) },
all_skill: { rank: K.ITEM('s32', ranking.all_skill), total_nr: K.ITEM('s32', ranking.totalPlayers) },
kac2018: {
data: {
term: K.ITEM('s32', 0),
total_score: K.ITEM('s32', 0),
score: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
music_type: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
play_count: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
})
return result
}
async function getAllProfiles( version: string, game: 'gf' | 'dm') {
return await DB.Find<Profile>(null, {
collection: 'profile',
version: version,
game: game
})
}
async function getProfile(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Profile>(refid, {
collection: 'profile',
version: version,
game: game
})
}
async function getExtra(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Extra>(refid, {
collection: 'extra',
version: version,
game: game
})
}
async function getRecord(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne<Record>(refid, {
collection: 'record',
version: version,
game: game
})
}
async function getScore(refid: string, version: string, game: 'gf' | 'dm'): Promise<Scores> {
return (await DB.FindOne<Scores>(refid, {
collection: 'scores',
version: version,
game: game
})) || {
collection: 'scores',
version: version,
pluginVer: PLUGIN_VER,
game: game,
scores: {}
}
}
async function saveScore(refid: string, version: string, game: 'gf' | 'dm', scores: Scores['scores']) {
return await DB.Upsert<Scores>(refid, {
collection: 'scores',
version,
game
}, {
collection: 'scores',
version,
game,
scores
})
}
function parseSecretMusic(playerData: KDataReader) : SecretMusicEntry[]
{
let response : SecretMusicEntry[] = []
let elements = playerData.element('secretmusic')?.elements('music')
if (!elements) {
return response
}
for (let el of elements) {
let item : SecretMusicEntry = {
musicid: el.number('musicid'),
seq: el.number('seq'),
kind: el.number('kind')
}
response.push(item)
}
return response
}
function getSecretMusicResponse(profile: Profile) : SecretMusicResponse[] {
let response : SecretMusicResponse[] = []
if (!profile.secretmusic?.music ) {
return response
}
for (let music of profile.secretmusic.music) {
response.push({
musicid: K.ITEM('s32', music.musicid),
seq: K.ITEM('u16', music.seq),
kind: K.ITEM('s32', music.kind)
})
}
return response
}
function getFriendDataResponse(profile: Profile) {
let response = []
return response;
}
function logStagesPlayed(playedStages: KDataReader[]) {
let result = "Stages played: "
for (let stage of playedStages) {
let id = stage.number('musicid')
result += `${id}, `
}
logger.debugLog(result)
}