///
import { getDefaultPlayerInfo, PlayerInfo } from "../models/playerinfo";
import { PlayerRanking } from "../models/playerranking";
import { getDefaultProfile, Profile } from "../models/profile";
import { getDefaultRecord, Record } from "../models/record";
import { Extra, getDefaultExtra } from "../models/extra";
import { getVersion, isDM } from "../utils";
import { getDefaultScores, Scores } from "../models/scores";
import { PLUGIN_VER } from "../const";
import Logger from "../utils/logger"
import { isAsphyxiaDebugMode, isSharedSongScoresEnabled } from "../utils/index";
import { SecretMusicEntry } from "../models/secretmusicentry";
import { CheckPlayerResponse, getCheckPlayerResponse } from "../models/Responses/checkplayerresponse";
import { getPlayerStickerResponse, PlayerStickerResponse } from "../models/Responses/playerstickerresponse";
import { getSecretMusicResponse, SecretMusicResponse } from "../models/Responses/secretmusicresponse";
import { getSaveProfileResponse } from "../models/Responses/saveprofileresponse";
import { getDefaultBattleDataResponse } from "../models/Responses/battledataresponse";
import { applySharedFavoriteMusicToExtra, saveSharedFavoriteMusicFromExtra } from "./FavoriteMusic";
import { getPlayerRecordResponse } from "../models/Responses/playerrecordresponse";
import { getPlayerPlayInfoResponse, PlayerPlayInfoResponse } from "../models/Responses/playerplayinforesponse";
import { getMergedSharedScores, mergeScoresIntoShared } from "./SharedScores";
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)
const result : CheckPlayerResponse = getCheckPlayerResponse(no, playerInfo.name, playerInfo.id)
await send.object(result)
}
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';
const sharedScoresEnabled = isSharedSongScoresEnabled();
logger.debugInfo(`Loading ${game} profile for player ${no} with refid: ${refid}`)
const name = await DB.FindOne(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 = sharedScoresEnabled ? await getMergedSharedScores(refid, 'dm') : (await getScore(refid, version, 'dm')).scores
const gfScores = sharedScoresEnabled ? await getMergedSharedScores(refid, 'gf') : (await getScore(refid, version, 'gf')).scores
const profile = dm ? dmProfile : gfProfile;
const extra = dm ? dmExtra : gfExtra;
await applySharedFavoriteMusicToExtra(refid, extra)
const record: any = {
gf: getPlayerRecordResponse(gfProfile, gfRecord),
dm: getPlayerRecordResponse(dmProfile, dmRecord),
};
// 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.clear', false) ? _.get(score, 'diffs.1.perc', -2) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.perc', -2) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.perc', -2) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.perc', -2) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.perc', -2) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.perc', -2) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.perc', -2) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.perc', -2) : -1,
_.get(score, 'diffs.1.clear', false) ? _.get(score, 'diffs.1.rank', 0) : -1,
_.get(score, 'diffs.2.clear', false) ? _.get(score, 'diffs.2.rank', 0) : -1,
_.get(score, 'diffs.3.clear', false) ? _.get(score, 'diffs.3.rank', 0) : -1,
_.get(score, 'diffs.4.clear', false) ? _.get(score, 'diffs.4.rank', 0) : -1,
_.get(score, 'diffs.5.clear', false) ? _.get(score, 'diffs.5.rank', 0) : -1,
_.get(score, 'diffs.6.clear', false) ? _.get(score, 'diffs.6.rank', 0) : -1,
_.get(score, 'diffs.7.clear', false) ? _.get(score, 'diffs.7.rank', 0) : -1,
_.get(score, 'diffs.8.clear', false) ? _.get(score, 'diffs.8.rank', 0) : -1,
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[] = getPlayerStickerResponse(name.card);
const playinfo: PlayerPlayInfoResponse = getPlayerPlayInfoResponse(profile);
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: playinfo,
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+95)),
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 },
deluxe: {
deluxe_content: K.ITEM('s32', 0),
target_id: K.ITEM('s32', 0),
multiply: K.ITEM('s32', 0),
point: K.ITEM('s32', 0),
},
galaxy_parade: {
score_list: {},
last_corner_id: K.ITEM('s32', 0),
chara_list: {},
last_sort_category: K.ITEM('s32', 0),
last_sort_order: K.ITEM('s32', 0),
team_member: {
chara_id_guitar: K.ITEM('s32', 0),
chara_id_bass: K.ITEM('s32', 0),
chara_id_drum: K.ITEM('s32', 0),
chara_id_free1: K.ITEM('s32', 0),
chara_id_free2: K.ITEM('s32', 0),
},
},
};
const playerRanking = await getPlayerRanking(refid, version, game)
const addition: any = {
monstar_subjugation: {},
bear_fes: {},
galaxy_parade: {
corner_list: {
corner: {
is_open: K.ITEM('bool', 0),
data_ver: K.ITEM('s32', 0),
genre: K.ITEM('s32', 0),
corner_id: K.ITEM('s32', 0),
corner_name: K.ITEM('str', ''),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
requirements_musicid: K.ITEM('s32', 0),
reward_list: {
reward: {
reward_id: K.ITEM('s32', 0),
reward_kind: K.ITEM('s32', 0),
reward_itemid: K.ITEM('s32', 0),
unlock_border: K.ITEM('s32', 0),
}
},
}
},
gacha_table: {
chara_odds: {
chara_id: K.ITEM('s32', 0),
odds: K.ITEM('s32', 0),
}
},
bonus: {
term: K.ITEM('s32', 0),
stage_bonus: K.ITEM('s32', 0),
charm_bonus: K.ITEM('s32', 0),
start_date_ms: K.ITEM('u64', BigInt(0)),
end_date_ms: K.ITEM('u64', BigInt(0)),
}
},
};
for (let i = 1; i <= 20; ++i) {
const obj = { point: K.ITEM('s32', 0) };
if (i == 1) {
addition['long_otobear_fes_1'] = obj;
addition['long_otobear_fes_2'] = obj;
addition['phrase_combo_challenge'] = obj;
addition['sdvx_stamprally'] = obj;
addition['sdvx_stamprally2'] = obj;
addition['sdvx_stamprally3'] = obj;
addition['chronicle_1'] = obj;
addition['gitadora_oracle_1'] = obj;
addition['gitadora_oracle_2'] = 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) };
addition[`dokidoki_valentine2_${i}`] = { point: K.ITEM('s32', 0) };
addition[`ohanami_challenge_${i}`] = { point: K.ITEM('s32', 0) };
addition[`otobear_in_the_tsubo_${i}`] = { point: K.ITEM('s32', 0) };
addition[`summer_craft_${i}`] = { point: K.ITEM('s32', 0) };
addition[`wakuteka_whiteday2_${i}`] = {
point_1: K.ITEM('s32', 0),
point_2: K.ITEM('s32', 0),
point_3: K.ITEM('s32', 0),
};
}
}
const innerSecretMusic = getSecretMusicResponse(profile)
const innerFriendData = getFriendDataResponse(profile)
const innerBattleData = getDefaultBattleDataResponse()
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: innerBattleData,
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: {} },
livehouse: {
score_list: {
score: {
term: K.ITEM('u8', -1),
reward_id: K.ITEM('s32', -1),
unlock_point: K.ITEM('s32', -1),
chara_id_guitar: K.ITEM('s32', -1),
chara_id_bass: K.ITEM('s32', -1),
chara_id_drum: K.ITEM('s32', -1),
chara_id_other: K.ITEM('s32', -1),
leader: K.ITEM('s32', -1),
},
last_livehouse: K.ITEM('s32', -1),
}
},
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),
},
KAC2016: {
is_entry: K.ITEM('bool', 0),
},
KAC2016_skill_ranking: {
skill: {
skill: K.ITEM('s32', -1),
rank: K.ITEM('s32', -1),
total_nr: K.ITEM('s32', -1),
}
},
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);
}
async function getOrRegisterPlayerInfo(refid: string, version: string, no: number) {
let playerInfo = await DB.FindOne(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(null, { collection: 'profile', id })) {
id = _.random(0, 99999999);
}
const defaultInfo: PlayerInfo = getDefaultPlayerInfo(version, id)
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 }, getDefaultProfile('gf', version, id));
await DB.Upsert(refid, { collection: 'profile', ...dm }, getDefaultProfile('dm', version, id));
await DB.Upsert(refid, { collection: 'record', ...gf }, getDefaultRecord('gf', version));
await DB.Upsert(refid, { collection: 'record', ...dm }, getDefaultRecord('dm', version));
await DB.Upsert(refid, { collection: 'extra', ...gf }, getDefaultExtra('gf', version, id));
await DB.Upsert(refid, { collection: 'extra', ...dm }, getDefaultExtra('dm', version, id));
await DB.Upsert(refid, { collection: 'scores', ...gf }, getDefaultScores('gf', version));
await DB.Upsert(refid, { collection: 'scores', ...dm }, getDefaultScores('dm', version));
return defaultInfo
}
export const savePlayers: EPR = async (info, data, send) => {
const version = getVersion(info);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
const sharedScoresEnabled = isSharedSongScoresEnabled();
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, sharedScoresEnabled);
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', sharedScoresEnabled: boolean)
{
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);
if (sharedScoresEnabled) {
await mergeScoresIntoShared(refid, 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 newMeter = stage.bool('is_new_meter');
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: newMeter ? meter.toString() : _.get(scores[mid].diffs[seq], 'meter', 0),
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 {
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
}
}
async function getAllProfiles( version: string, game: 'gf' | 'dm') {
return await DB.Find(null, {
collection: 'profile',
version: version,
game: game
})
}
async function getProfile(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne(refid, {
collection: 'profile',
version: version,
game: game
})
}
async function getExtra(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne(refid, {
collection: 'extra',
version: version,
game: game
})
}
async function getRecord(refid: string, version: string, game: 'gf' | 'dm') {
return await DB.FindOne(refid, {
collection: 'record',
version: version,
game: game
})
}
async function getScore(refid: string, version: string, game: 'gf' | 'dm'): Promise {
return (await DB.FindOne(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(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 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)
}