Merge pull request #1 from thomeval/bug/39_twoPlayerSave

Bug/39 two player save
This commit is contained in:
thomeval 2022-04-28 13:58:05 +02:00 committed by GitHub
commit b7377a3d0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 205 additions and 96 deletions

1
gitadora@asphyxia/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
apisamples/

View File

@ -1,6 +1,6 @@
GITADORA Plugin for Asphyxia-Core
=================================
![Version: v1.1.1](https://img.shields.io/badge/version-v1.1.1-blue)
![Version: v1.2.0](https://img.shields.io/badge/version-v1.2.0-blue)
This plugin is based on converted from public-exported Asphyxia's Routes.
@ -14,10 +14,10 @@ Supported Versions
When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------
The folder structure between v1.0 and v1.1 is quite different. Do not overwrite plugin folder.
<br>If encounter error, Please try these step:
<br>If you encounter errors, Please try these steps:
1. Remove `gitadora@asphyxia` folder.
2. C-C and C-V the newest version of `gitadora@asphyxia`
2. Ctrl-C and Ctrl-V the newest version of `gitadora@asphyxia`
3. (Custom MDB Users) Reupload MDB or move `data/custom_mdb.xml` to `data/mdb/custom.xml`
@ -29,7 +29,20 @@ Known Issues
Release Notes
=============
v1.1.1 (Current)
v1.2.0
----------------
* Fixed server error when saving profiles for two Guitar Freaks players at the end of a session. Fixes Github issue #39.
* Fixed another server error when two players are present, but only one player is using a profile.
* Added support for the "ranking" field. Gitadora will now correctly display your server ranking (based on Skill) on the post-game screen.
* "Recommended to friends" songs are now saved and loaded correctly. Since you don't have any friends, this won't be terribly useful, but it does at least provide an extra five slots for saving your favourite songs.
* Fixed "Recommended to friends" song list being incorrectly initialized to "I think about you".
* misc: Added logging for profile loading/saving when Asphyxia is running in dev mode.
* misc: Added more logging to mdb (song database) loading.
* misc: Removed some unneeded duplicate code.
* misc: Latest getPlayer() and savePlayers() API requests and responses are now saved to file when Asphyxia is in dev mode. Useful for debugging.
v1.1.1
----------------
* fix: Error when create new profile on exchain.
* fix: last song doesn't work correctly.

View File

@ -1,3 +1,5 @@
import Logger from "../../utils/logger";
export interface CommonMusicDataField {
id: KITEM<"s32">;
cont_gf: KITEM<"bool">;
@ -21,13 +23,17 @@ export enum DATAVersion {
type processRawDataHandler = (path: string) => Promise<CommonMusicData>
const logger = new Logger("mdb")
export async function readXML(path: string) {
logger.debugInfo(`Loading MDB data from ${path}.`)
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
logger.debugInfo(`Loading MDB data from ${path}.`)
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
@ -35,16 +41,19 @@ export async function readJSON(path: string) {
export async function readJSONOrXML(jsonPath: string, xmlPath: string, processHandler: processRawDataHandler): Promise<CommonMusicData> {
if (!IO.Exists(jsonPath)) {
logger.debugInfo(`Loading MDB data from ${xmlPath}.`)
const data = await processHandler(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
return data
} else {
logger.debugInfo(`Loading MDB data from ${jsonPath}.`)
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
return json
}
}
export async function readB64JSON(b64path: string) {
logger.debugInfo(`Loading MDB data from ${b64path}.`)
const buff = await IO.ReadFile(b64path, 'utf-8');
return JSON.parse(Buffer.from(buff, 'base64').toString('utf-8'));
}
@ -66,7 +75,7 @@ export function gameVerToDataVer(ver: string): DATAVersion {
export async function processDataBuilder(gameVer: string, processHandler?: processRawDataHandler) {
const ver = gameVerToDataVer(gameVer)
const base = `data/mdb/${ver}`
if (IO.Exists(`${base}.b64`)) {
if (IO.Exists(`${base}.b64`)) {
return await readB64JSON(`${base}.b64`);
}
const { music } = await readJSONOrXML(`${base}.json`, `${base}.xml`, processHandler ?? defaultProcessRawData)
@ -74,7 +83,6 @@ export async function processDataBuilder(gameVer: string, processHandler?: proce
return { music };
}
export async function defaultProcessRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");

View File

@ -22,7 +22,6 @@ export const playableMusic: EPR = async (info, data, send) => {
music = _.get(await processDataBuilder(version), 'music', []);
}
await send.object({
hot: {
major: K.ITEM('s32', 1),

View File

@ -1,94 +1,78 @@
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 { PLUGIN_VER } from "../const";
import Logger from "../utils/logger"
import { isAsphyxiaDebugMode } from "../Utils/index";
const logger = new Logger("profiles")
export const regist: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) return send.deny();
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 playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
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)
})
})
if (playerInfo) {
send.object({
player: K.ATTR({ no: `${no}` }, {
is_succession: K.ITEM("bool", 0), //FIX THIS with upsert result.
did: K.ITEM("s32", playerInfo.id)
})
})
} else {
let info = await registerUser(refid, version)
send.object({
player: K.ATTR({ no: `${no}` }, {
is_succession: K.ITEM("bool", 0), //FIX THIS with upsert result.
did: K.ITEM("s32", info.id)
})
})
}
}
export const check: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) return send.deny();
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 playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
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),
}
})
})
if (playerInfo) {
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),
}
})
})
} else {
let info = await registerUser(refid, version)
send.object({
player: K.ATTR({ no: `${no}`, state: '2' }, {
name: K.ITEM('str', info.name),
charaid: K.ITEM('s32', 0),
did: K.ITEM('s32', info.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) return send.deny();
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
@ -343,6 +327,7 @@ export const getPlayer: EPR = async (info, data, send) => {
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),
@ -354,6 +339,8 @@ export const getPlayer: EPR = async (info, data, send) => {
musiclist: { '@attr': { nr: musicdata.length }, musicdata },
};
const playerRanking = await getPlayerRanking(refid, version, game)
const addition: any = {
monstar_subjugation: {},
bear_fes: {},
@ -387,7 +374,7 @@ export const getPlayer: EPR = async (info, data, send) => {
}
}
send.object({
const response = {
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: { // TODO: FIX THIS
@ -404,7 +391,7 @@ export const getPlayer: EPR = async (info, data, send) => {
},
reward: {
status: K.ARRAY('u32', Array(50).fill(0)),
},
},
rivaldata: {},
frienddata: {},
thanks_medal: {
@ -412,7 +399,7 @@ export const getPlayer: EPR = async (info, data, send) => {
grant_medal: K.ITEM('s32', 0),
grant_total_medal: K.ITEM('s32', 0),
},
recommend_musicid_list: K.ARRAY('s32', [0, 0, 0, 0, 0]),
recommend_musicid_list: K.ARRAY('s32', extra.recommend_musicid_list ?? Array(5).fill(-1)),
skindata: {
skin: K.ARRAY('u32', Array(100).fill(-1)),
},
@ -454,8 +441,8 @@ export const getPlayer: EPR = async (info, data, send) => {
},
is_free_ok: K.ITEM('bool', 0),
ranking: {
skill: { rank: K.ITEM('s32', 1), total_nr: K.ITEM('s32', 1) },
all_skill: { rank: K.ITEM('s32', 1), total_nr: K.ITEM('s32', 1) },
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: {},
@ -532,7 +519,26 @@ export const getPlayer: EPR = async (info, data, send) => {
...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<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 {
@ -725,7 +731,8 @@ async function registerUser(refid: string, version: string, id = _.random(0, 999
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),
}
}
@ -755,20 +762,61 @@ async function registerUser(refid: string, version: string, id = _.random(0, 999
return defaultInfo
}
export const savePlayer: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) return send.deny();
export const savePlayers: EPR = async (info, data, send) => {
const no = getPlayerNo(data);
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 dataplayer = $(data).element("player")
const autoSet = (field: keyof Profile, path: string, array = false): void => {
if (array) {
@ -781,7 +829,7 @@ export const savePlayer: EPR = async (info, data, send) => {
const autoExtra = (field: keyof Extra, path: string, array = false): void => {
if (array) {
extra[field] = dataplayer.numbers(path, extra[field])
} else {
} else {
extra[field] = dataplayer.number(path, extra[field])
}
};
@ -887,6 +935,7 @@ export const savePlayer: EPR = async (info, data, send) => {
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);
@ -895,7 +944,7 @@ export const savePlayer: EPR = async (info, data, send) => {
await DB.Upsert(refid, { collection: 'record', game, version }, rec)
await DB.Upsert(refid, { collection: 'extra', game, version }, extra)
const stages = $(data).elements('player.stage');
const stages = dataplayer.elements('stage');
const scores = (await getScore(refid, version, game)).scores;
for (const stage of stages) {
const mid = stage.number('musicid', -1);
@ -937,25 +986,55 @@ export const savePlayer: EPR = async (info, data, send) => {
};
}
await saveScore(refid, version, game, scores);
await saveScore(refid, version, game, 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)
await send.object({
player: K.ATTR({ no: `${no}` }, {
skill: { rank: K.ITEM('s32', 1), total_nr: K.ITEM('s32', 1) },
all_skill: { rank: K.ITEM('s32', 1), total_nr: K.ITEM('s32', 1) },
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]),
},
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]),
},
}),
gamemode: _.get(data, 'gamemode'),
});
};
},
})
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, {

View File

@ -1,6 +1,6 @@
import { gameInfoGet, shopInfoRegist } from "./handlers/info";
import { playableMusic } from "./handlers/MusicList"
import { getPlayer, check, regist, savePlayer } from "./handlers/profiles";
import { getPlayer, check, regist, savePlayers } from "./handlers/profiles";
import { updatePlayerInfo } from "./handlers/webui";
import { isAsphyxiaDebugMode, isRequiredCoreVersion } from "./utils";
import Logger from "./utils/logger";
@ -64,7 +64,7 @@ export function register() {
MultiRoute('cardutil.regist', regist);
MultiRoute('cardutil.check', check);
MultiRoute('gametop.get', getPlayer);
MultiRoute('gameend.regist', savePlayer);
MultiRoute('gameend.regist', savePlayers);
// Misc
R.Route('bemani_gakuen.get_music_info', true)

View File

@ -11,4 +11,6 @@ export interface Extra {
list_1: number[];
list_2: number[];
list_3: number[];
recommend_musicid_list: number[];
reward_status: number[];
}

View File

@ -0,0 +1,7 @@
export interface PlayerRanking
{
refid: string;
skill: number;
all_skill: number;
totalPlayers: number;
}