plugins/gitadora@asphyxia/handlers/profiles.ts
Thome Valentin 257c9962f5 Added several player profile stats to the web UI.
Refactored and cleaned up several functions.
MDB loader now logs the number of loaded songs available to GF and DM.
MDB: Fixed "is_secret" field being ignored (always set to false)
2022-05-04 06:58:17 +02:00

1172 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";
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;
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)
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 = (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);
}
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)
}