///
import {
MusicScoreEntry,
ProfileDocument,
ScoreDocument,
UnlockMusicEntry,
} from '../models';
import {
avatarResponse,
createProfile,
dancerGradeResponse,
mergeScore,
mergeUnlocks,
musicScoreKey,
nowMs,
nowS64,
playInfoResponse,
playOptionResponse,
readAvatar,
readDeltaGroup,
readMusicScore,
readMusicUnlockGauges,
readNumber,
readPlayInfo,
readPlayOption,
readString,
readUnlocks,
readViewFlags,
stageResultResponse,
} from '../utils';
import { loadLocalMdb } from '../mdb';
async function findProfile(refid: string): Promise {
return (
(await DB.FindOne(refid, { collection: 'profile' })) ||
null
);
}
async function upsertProfile(
refid: string,
profile: ProfileDocument
): Promise {
profile.updatedAt = nowMs();
await DB.Upsert(
refid,
{ collection: 'profile' },
{ $set: profile }
);
}
async function effectiveUnlocks(
profile: ProfileDocument
): Promise {
if (!U.GetConfig('unlock_all_songs')) {
return profile.unlockedMusics || [];
}
const catalog = await loadLocalMdb();
return mergeUnlocks(
profile.unlockedMusics || [],
catalog ? catalog.unlocks : []
);
}
function unlockMusicResponse(entries: UnlockMusicEntry[]): any {
return {
music: entries.map(entry =>
K.ATTR(
{ music_id: `${entry.musicId}` },
{ fumen_type: entry.fumens.map(fumen => K.ITEM('str', fumen)) }
)
),
};
}
function profileResponse(
profile: ProfileDocument,
unlocks: UnlockMusicEntry[]
): any {
return {
result: K.ITEM('s32', 0),
now_date: K.ITEM('s64', nowS64()),
userid: {
code: K.ITEM('s32', profile.userCode),
dancer_id: K.ITEM('str', profile.dancerId),
},
profile: {
name: K.ITEM('str', profile.name),
},
privacy: {
publish_setting: K.ITEM('s32', profile.privacy.publishSetting),
},
dancer_grade: dancerGradeResponse(profile.dancerGrade),
delta_group: {
delta: (profile.deltaGroup || []).map(delta => ({
name: K.ITEM('str', delta.name),
income: K.ITEM('s32', delta.income),
expense: K.ITEM('s32', delta.expense),
})),
},
avatar: avatarResponse(profile.avatar),
playoption: playOptionResponse(profile.playOption),
playinfo: playInfoResponse(profile.playInfo),
unlock_music: unlockMusicResponse(unlocks),
follow_data: {
follower_num: K.ITEM('s32', 0),
data: [],
},
view_flags: {
view_flag: (profile.viewFlags || []).map(entry => ({
view_id: K.ITEM('str', entry.viewId),
flag: K.ITEM('s32', entry.flag),
})),
},
music_unlock_gauges: {
music_unlock_gauge: (profile.musicUnlockGauges || []).map(entry => ({
event_id: K.ITEM('str', entry.eventId),
progress: K.ITEM('s32', entry.progress),
})),
},
};
}
export const getPlayData: EPR = async (info, data, send) => {
const reader = $(data);
const refid = readString(reader, 'userid.ref_id');
if (!refid) {
console.error('[Dance Around] get_playdata is missing userid.ref_id');
return send.deny();
}
const profile = await findProfile(refid);
if (!profile) {
return send.object(
{
result: K.ITEM('s32', 1),
now_date: K.ITEM('s64', nowS64()),
},
{ status: 0 }
);
}
await DB.Update(
refid,
{ collection: 'profile' },
{ $inc: { loginCount: 1 }, $set: { updatedAt: nowMs() } }
);
return send.object(
profileResponse(profile, await effectiveUnlocks(profile)),
{ status: 0 }
);
};
export const signup: EPR = async (info, data, send) => {
const reader = $(data);
const refid = readString(reader, 'userid.ref_id');
if (!refid) {
console.error('[Dance Around] sign_up is missing userid.ref_id');
return send.deny();
}
const name = readString(reader, 'profile.name', 'PLAYER');
const dataId = readString(reader, 'userid.data_id');
const cardNumber = readString(reader, 'userid.card_no');
const existing = await findProfile(refid);
const profile =
existing || createProfile(refid, name, dataId, cardNumber, info.model);
profile.name = name || profile.name;
profile.dataId = dataId || profile.dataId;
profile.cardNumber = cardNumber || profile.cardNumber;
await upsertProfile(refid, profile);
console.log(
`[Dance Around] registered player ${profile.name} (${profile.dancerId})`
);
return send.success();
};
async function mergeScoreEntries(
refid: string,
incoming: MusicScoreEntry[]
): Promise {
if (!incoming.length) {
return;
}
const document = (await DB.FindOne(refid, {
collection: 'scores',
})) || {
collection: 'scores' as const,
schemaVersion: 1,
entries: [],
updatedAt: 0,
};
const scores: { [key: string]: MusicScoreEntry } = {};
for (const entry of document.entries || []) {
scores[musicScoreKey(entry)] = entry;
}
for (const entry of incoming) {
if (entry.musicId <= 0) {
continue;
}
const key = musicScoreKey(entry);
scores[key] = mergeScore(scores[key], entry);
}
document.entries = Object.keys(scores)
.map(key => scores[key])
.sort(
(a, b) => a.musicId - b.musicId || a.fumenType.localeCompare(b.fumenType)
);
document.updatedAt = nowMs();
await DB.Upsert(
refid,
{ collection: 'scores' },
{ $set: document }
);
}
export const savePlayData: EPR = async (info, data, send) => {
const reader = $(data);
const refid = readString(
reader,
'data.userid.ref_id',
readString(reader, 'userid.ref_id')
);
if (!refid) {
console.error('[Dance Around] save_playdata is missing data.userid.ref_id');
return send.deny();
}
const profile =
(await findProfile(refid)) ||
createProfile(refid, 'PLAYER', '', '', info.model);
profile.privacy = {
publishSetting: readNumber(
reader,
'data.privacy.publish_setting',
profile.privacy.publishSetting
),
};
profile.dancerGrade = {
grade: readNumber(
reader,
'data.dancer_grade.grade',
profile.dancerGrade.grade
),
gvGauge: readNumber(
reader,
'data.dancer_grade.gv_gauge',
profile.dancerGrade.gvGauge
),
achievedGv: readNumber(
reader,
'data.dancer_grade.achieve_gv',
profile.dancerGrade.achievedGv
),
receivedGv: 0,
increaseGv: 0,
};
profile.avatar = readAvatar(reader, 'data.avatar');
profile.playOption = readPlayOption(reader, 'data.playoption');
profile.playInfo = readPlayInfo(reader, 'data.playinfo', info.model);
profile.unlockedMusics = mergeUnlocks(
profile.unlockedMusics || [],
readUnlocks(reader, 'data.unlock_music.music')
);
profile.viewFlags = readViewFlags(reader, 'data.view_flags.view_flag');
profile.musicUnlockGauges = readMusicUnlockGauges(
reader,
'data.music_unlock_gauges.music_unlock_gauge'
);
profile.deltaGroup = readDeltaGroup(reader, 'data.delta_group.delta');
await upsertProfile(refid, profile);
const stagedata = reader.elements('data.stagedata.data').map(readMusicScore);
await mergeScoreEntries(refid, stagedata);
console.log(
`[Dance Around] saved profile ${profile.dancerId}; ${stagedata.length} stage result(s)`
);
return send.success();
};
export const saveMusicScore: EPR = async (info, data, send) => {
const reader = $(data);
const refid = readString(
reader,
'data.userid.ref_id',
readString(reader, 'userid.ref_id')
);
if (!refid) {
console.error(
'[Dance Around] save_musicscore is missing data.userid.ref_id'
);
return send.deny();
}
const scoreReader = reader.element('data');
const score = readMusicScore(scoreReader);
await mergeScoreEntries(refid, [score]);
console.log(
`[Dance Around] saved score ${score.musicId}/${score.fumenType}: ${score.score}`
);
return send.success();
};
export const getMusicScore: EPR = async (info, data, send) => {
const refid = readString($(data), 'userid.ref_id');
if (!refid) {
console.error('[Dance Around] get_musicscore is missing userid.ref_id');
return send.deny();
}
const document = await DB.FindOne(refid, {
collection: 'scores',
});
const entries = document ? document.entries || [] : [];
return send.object(
{
scoredata: {
music: entries.map(score => ({
...stageResultResponse(score),
play_cnt: K.ITEM('s32', score.playCount),
play_date: K.ITEM('s64', BigInt(score.playDate)),
bestscore_date: K.ITEM('s64', BigInt(score.bestScoreDate)),
pcb_id: K.ITEM('str', score.pcbId),
loc_id: K.ITEM('str', score.locationId),
shopname: K.ITEM('str', score.shopName),
rec_cnt: K.ITEM('s32', score.recordCount),
gv_score: K.ITEM('s32', score.gvScore),
})),
},
},
{ status: 0 }
);
};