mirror of
https://github.com/laochan-eacnet/backend.git
synced 2026-09-22 22:28:11 +03:00
sdvx basic data save working
This commit is contained in:
@@ -32,3 +32,5 @@ build/
|
||||
|
||||
input.xml
|
||||
output.json
|
||||
dump.xml
|
||||
pdata.json
|
||||
|
||||
@@ -15,7 +15,7 @@ export class Game {
|
||||
entry_begin_clock: v.u64(0),
|
||||
championship_begin_clock: v.u64(0),
|
||||
championship_end_clock: v.u64(0),
|
||||
is_possible_play: v.bool(0),
|
||||
is_possible_play: v.bool(false),
|
||||
repertoire_list: {
|
||||
repertoire_num: v.s32(0),
|
||||
}
|
||||
|
||||
@@ -4,6 +4,37 @@ import { Serializable, v } from "../../utils/kxml-value.js";
|
||||
import * as data from '../../datas/sdvx.js';
|
||||
|
||||
export class AcRelay {
|
||||
@sdvx()
|
||||
async sv6_play_e(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
game: {
|
||||
$status: 0,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async sv6_play_s(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
game: {
|
||||
$status: 0,
|
||||
play_id: v.u32(Math.floor(new Date().valueOf() / 6e4)),
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async sv6_hiscore() {
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,79 @@
|
||||
import { sdvx } from "../../decorators/eacnet.js";
|
||||
import { UserService } from "../../services/sdvx/user.js";
|
||||
import { Serializable, v } from "../../utils/kxml-value.js";
|
||||
import { Context } from "../../types.js";
|
||||
import { PlayerMusicData, PlayerPlayLog } from "../../types/sdvx/index.js";
|
||||
|
||||
export class Music {
|
||||
userService: UserService;
|
||||
|
||||
@sdvx()
|
||||
async sv6_save_m(ctx: Context): Promise<Serializable> {
|
||||
const { track } = ctx.body as { track: PlayerPlayLog };
|
||||
|
||||
track.player = ctx.token;
|
||||
track.play_time = new Date();
|
||||
|
||||
const mdata = (await this.userService.getPlayerMusicData(ctx.token, track.music_id, track.music_type)) ?? {
|
||||
player: ctx.token,
|
||||
music_id: track.music_id,
|
||||
music_type: track.music_type,
|
||||
score: 0, exscore: 0, clear_type: 0, score_grade: 0,
|
||||
max_chain: 0, play_count: 0,
|
||||
btn_rate: 0, long_rate: 0, vol_rate: 0,
|
||||
best_play_time: track.play_time,
|
||||
} as PlayerMusicData;
|
||||
|
||||
if (track.score > mdata.score) {
|
||||
mdata.score = track.score;
|
||||
mdata.score_grade = track.score_grade;
|
||||
|
||||
mdata.best_play_time = track.play_time;
|
||||
mdata.btn_rate = track.btn_rate;
|
||||
mdata.long_rate = track.long_rate;
|
||||
mdata.vol_rate = track.vol_rate;
|
||||
}
|
||||
|
||||
if (track.exscore > mdata.score) {
|
||||
mdata.exscore = track.exscore;
|
||||
|
||||
// if score is same, use exscore to compare
|
||||
if (track.score == mdata.score) {
|
||||
mdata.best_play_time = track.play_time;
|
||||
mdata.btn_rate = track.btn_rate;
|
||||
mdata.long_rate = track.long_rate;
|
||||
mdata.vol_rate = track.vol_rate;
|
||||
}
|
||||
}
|
||||
|
||||
if (track.clear_type > mdata.clear_type) {
|
||||
mdata.clear_type = track.clear_type;
|
||||
}
|
||||
|
||||
if (track.max_chain > mdata.max_chain) {
|
||||
mdata.max_chain = track.max_chain;
|
||||
}
|
||||
|
||||
mdata.play_count++;
|
||||
|
||||
await Promise.all([
|
||||
this.userService.insertPlayLog(track),
|
||||
this.userService.upsertMusicData(mdata),
|
||||
]);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
xrpc_status_code: v.s32(0),
|
||||
xrpc_fault_code: v.s32(0),
|
||||
response: {
|
||||
game: {
|
||||
$status: 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@sdvx()
|
||||
async sv6_load_r(): Promise<Serializable> {
|
||||
return {
|
||||
@@ -20,8 +89,15 @@ export class Music {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* music_id diff
|
||||
* score exscore clear_type score_grade max_chain play_count btn_rate long_rate vol_rate
|
||||
* 0 0 0 0 0 timestamp_s 0 0 0 0 0 0
|
||||
*/
|
||||
@sdvx()
|
||||
async sv6_load_m(): Promise<Serializable> {
|
||||
async sv6_load_m(ctx: Context): Promise<Serializable> {
|
||||
const mdata = await this.userService.getPlayerMusicDatas(ctx.token);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
@@ -31,7 +107,17 @@ export class Music {
|
||||
game: {
|
||||
$status: 0,
|
||||
music: {
|
||||
info: []
|
||||
info: mdata.map(d => ({
|
||||
date: d.best_play_time,
|
||||
param: v.u32([
|
||||
d.music_id, d.music_type,
|
||||
d.score, d.exscore, d.clear_type, d.score_grade, d.max_chain,
|
||||
d.play_count, d.btn_rate, d.long_rate, d.vol_rate,
|
||||
0, 0, 0, 0, 0,
|
||||
Math.floor(d.best_play_time.valueOf() / 1000),
|
||||
0, 0, 0, 0, 0, 0,
|
||||
])
|
||||
}))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+101
-57
@@ -4,8 +4,10 @@ import { Context } from "../../types.js";
|
||||
import { sdvx } from '../../decorators/eacnet.js';
|
||||
import { KValueG, Serializable, ValueTypes, v } from '../../utils/kxml-value.js';
|
||||
import { UserService } from '../../services/sdvx/user.js';
|
||||
import { Item, Param } from '../../types/sdvx/savedata.js';
|
||||
import { dateToString } from '../../utils/time.js';
|
||||
|
||||
function trySave<T>(kvalue: KValueG<ValueTypes, T>, value: T) {
|
||||
function trySet<T>(kvalue: KValueG<ValueTypes, T>, value: T) {
|
||||
if (value === undefined || value === null)
|
||||
return;
|
||||
|
||||
@@ -22,11 +24,74 @@ function tryAdd(kvalue: KValueG<ValueTypes, number>, value: number) {
|
||||
kvalue.__value += value;
|
||||
}
|
||||
|
||||
export function tryMergeItem(params: Item[], newParams: object[]) {
|
||||
if (!params) {
|
||||
params = [];
|
||||
}
|
||||
|
||||
if (!newParams) {
|
||||
return params;
|
||||
}
|
||||
|
||||
if (!(newParams instanceof Array)) {
|
||||
newParams = [newParams];
|
||||
}
|
||||
|
||||
for (const np of newParams) {
|
||||
const target = params.find(p => p.id.__value === np['id'] && p.type.__value === np['type']);
|
||||
|
||||
if (target) {
|
||||
target.type = v.u8(np['type']);
|
||||
target.param = v.u32(np['param']);
|
||||
continue;
|
||||
}
|
||||
|
||||
params.push({
|
||||
type: v.u8(np['type']),
|
||||
id: v.u32(np['id']),
|
||||
param: v.u32(np['param']),
|
||||
})
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export function tryMergeParam(params: Param[], newParams: object[]) {
|
||||
if (!params) {
|
||||
params = [];
|
||||
}
|
||||
|
||||
if (!newParams) {
|
||||
return params;
|
||||
}
|
||||
|
||||
if (!(newParams instanceof Array)) {
|
||||
newParams = [newParams];
|
||||
}
|
||||
|
||||
for (const np of newParams) {
|
||||
const target = params.find(p => p.id.__value === np['id'] && p.type.__value === np['type']);
|
||||
|
||||
if (target) {
|
||||
target.param = v.s32(np['param']);
|
||||
continue;
|
||||
}
|
||||
|
||||
params.push({
|
||||
type: v.s32(np['type']),
|
||||
id: v.s32(np['id']),
|
||||
param: v.s32(np['param']),
|
||||
})
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export class User {
|
||||
userService: UserService;
|
||||
|
||||
@sdvx()
|
||||
async sv6_play_s(): Promise<Serializable> {
|
||||
async sv6_save_c(): Promise<Serializable> {
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
@@ -36,7 +101,7 @@ export class User {
|
||||
game: {
|
||||
$status: 0,
|
||||
play_id: v.u32(Math.floor(new Date().valueOf() / 6e4)),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -55,64 +120,43 @@ export class User {
|
||||
|
||||
const newData = ctx.body;
|
||||
|
||||
trySave(playData.skill_level, newData.skill_level);
|
||||
trySave(playData.skill_base_id, newData.skill_base_id);
|
||||
trySave(playData.skill_name_id, newData.skill_name_id);
|
||||
trySet(playData.appeal_id, newData.appeal_id);
|
||||
|
||||
trySet(playData.skill_level, newData.skill_level);
|
||||
trySet(playData.skill_base_id, newData.skill_base_id);
|
||||
trySet(playData.skill_name_id, newData.skill_name_id);
|
||||
|
||||
tryAdd(playData.blaster_energy, newData.earned_blaster_energy);
|
||||
tryAdd(playData.gamecoin_packet, newData.earned_gamecoin_packet);
|
||||
tryAdd(playData.gamecoin_block, newData.earned_gamecoin_block);
|
||||
|
||||
trySave(playData.hispeed, newData.hispeed);
|
||||
trySave(playData.lanespeed, newData.lanespeed);
|
||||
trySave(playData.gauge_option, newData.gauge_option);
|
||||
trySave(playData.ars_option, newData.ars_option);
|
||||
trySave(playData.notes_option, newData.notes_option);
|
||||
trySave(playData.early_late_disp, newData.early_late_disp);
|
||||
trySave(playData.draw_adjust, newData.draw_adjust);
|
||||
trySave(playData.eff_c_left, newData.eff_c_left);
|
||||
trySave(playData.eff_c_right, newData.eff_c_right);
|
||||
trySave(playData.last_music_id, newData.music_id);
|
||||
trySave(playData.last_music_type, newData.music_type);
|
||||
trySave(playData.sort_type, newData.sort_type);
|
||||
trySave(playData.narrow_down, newData.narrow_down);
|
||||
trySave(playData.headphone, newData.headphone);
|
||||
tryAdd(playData.play_count, 1);
|
||||
|
||||
if (newData.item?.info instanceof Array) {
|
||||
playData.item.info = [
|
||||
...(playData.item.info ?? []),
|
||||
...newData.item.info.map((info: { id: number; type: number; param: number; }) => ({
|
||||
id: v.u8(info.id),
|
||||
type: v.u32(info.type),
|
||||
param: v.u32(info.param),
|
||||
}))
|
||||
]
|
||||
trySet(playData.hispeed, newData.hispeed);
|
||||
trySet(playData.lanespeed, newData.lanespeed);
|
||||
trySet(playData.gauge_option, newData.gauge_option);
|
||||
trySet(playData.ars_option, newData.ars_option);
|
||||
trySet(playData.notes_option, newData.notes_option);
|
||||
trySet(playData.early_late_disp, newData.early_late_disp);
|
||||
trySet(playData.draw_adjust, newData.draw_adjust);
|
||||
trySet(playData.eff_c_left, newData.eff_c_left);
|
||||
trySet(playData.eff_c_right, newData.eff_c_right);
|
||||
trySet(playData.last_music_id, newData.music_id);
|
||||
trySet(playData.last_music_type, newData.music_type);
|
||||
trySet(playData.sort_type, newData.sort_type);
|
||||
trySet(playData.narrow_down, newData.narrow_down);
|
||||
trySet(playData.headphone, newData.headphone);
|
||||
|
||||
playData.item.info = tryMergeItem(playData.item.info, newData.item?.info);
|
||||
playData.item_infinite.info = tryMergeItem(playData.item_infinite.info, newData.item_infinite?.info);
|
||||
playData.param.info = tryMergeParam(playData.param.info, newData.param?.info);
|
||||
|
||||
playData.last_date = v.str(dateToString(new Date()));
|
||||
|
||||
if (!await this.userService.savePlayerData(ctx.token, playData)) {
|
||||
ctx.logger.error('play data save failed');
|
||||
}
|
||||
|
||||
if (newData.item_infinite?.info instanceof Array) {
|
||||
playData.item_infinite.info = [
|
||||
...(playData.item_infinite.info ?? []),
|
||||
...newData.item_infinite.info.map((info: { id: number; type: number; param: number; }) => ({
|
||||
id: v.u8(info.id),
|
||||
type: v.u32(info.type),
|
||||
param: v.u32(info.param),
|
||||
}))
|
||||
]
|
||||
}
|
||||
|
||||
if (newData.param?.info instanceof Array) {
|
||||
playData.param.info = [
|
||||
...(playData.param.info ?? []),
|
||||
...newData.param.info.map((info: { id: number; type: number; param: number; }) => ({
|
||||
id: v.u8(info.id),
|
||||
type: v.u32(info.type),
|
||||
param: v.u32(info.param),
|
||||
}))
|
||||
]
|
||||
}
|
||||
|
||||
await this.userService.savePlayerData(ctx.token, playData);
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
error_code: v.s32(0),
|
||||
@@ -163,10 +207,10 @@ export class User {
|
||||
// blaster pass
|
||||
playData.ea_shop.blaster_pass_enable = v.bool(true);
|
||||
playData.ea_shop.blaster_pass_limit_date = v.u64(new Date().valueOf() + (30 * 60 * 60 * 1000));
|
||||
playData.item.info = [
|
||||
...(playData.item.info ?? []),
|
||||
...data.charaItems,
|
||||
];
|
||||
|
||||
// unlock all navigaters
|
||||
playData.item.info = tryMergeItem(playData.item.info, data.charaItems);
|
||||
playData.start_date = v.str(dateToString(new Date()));
|
||||
|
||||
return {
|
||||
status: v.s32(0),
|
||||
|
||||
+4
-4
@@ -8643,10 +8643,10 @@ export const extendInfos = [
|
||||
}
|
||||
];
|
||||
|
||||
export const charaItems = [...new Array(46).keys()].map((i) => ({
|
||||
type: v.u8(11),
|
||||
id: v.u32(i),
|
||||
param: v.u32(15),
|
||||
export const charaItems = [...new Array(300).keys()].map((i) => ({
|
||||
type: 11,
|
||||
id: i,
|
||||
param: 15,
|
||||
}));
|
||||
|
||||
export const musicInfos = [];
|
||||
|
||||
@@ -114,6 +114,10 @@ async function main(): Promise<void> {
|
||||
JSON.stringify(ctx.body, (key, value) => {
|
||||
if (key === 'pdata') {
|
||||
return '<PDATA>';
|
||||
} else if (key == 'item') {
|
||||
return '<ITEM>';
|
||||
} else if (key == 'param') {
|
||||
return '<PARAM>';
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Binary, Db, FindOptions } from "mongodb";
|
||||
import { inject, injectable } from "tsyringe";
|
||||
import { PlayerPlayData } from "../../types/sdvx/index.js";
|
||||
import { PlayerMusicData, PlayerPlayData, PlayerPlayLog } from "../../types/sdvx/index.js";
|
||||
import { tokenToCode, tokenToSdvxId } from "../../utils/laochan-id.js";
|
||||
import { v } from "../../utils/kxml-value.js";
|
||||
import { dateToString } from "../../utils/time.js";
|
||||
import { brotliCompress, brotliDecompress } from "zlib";
|
||||
import { promisify } from "util";
|
||||
import { SaveData } from "../../types/sdvx/savedata.js";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
@injectable()
|
||||
export class UserService {
|
||||
@@ -19,11 +20,43 @@ export class UserService {
|
||||
return this.db.collection<PlayerPlayData>('sdvx_player_data');
|
||||
}
|
||||
|
||||
get musicDataCol() {
|
||||
return this.db.collection<PlayerMusicData>('sdvx_music_data');
|
||||
}
|
||||
|
||||
get playLogCol() {
|
||||
return this.db.collection<PlayerPlayLog>('sdvx_play_log');
|
||||
}
|
||||
|
||||
getPlayerMusicData(token: string, music_id: number, music_type: number) {
|
||||
return this.musicDataCol.findOne({ player: token, music_id, music_type });
|
||||
}
|
||||
|
||||
getPlayerMusicDatas(token: string) {
|
||||
return this.musicDataCol.find({ player: token }).toArray();
|
||||
}
|
||||
|
||||
async upsertMusicData(musicData: PlayerMusicData) {
|
||||
return await this.musicDataCol.updateOne({
|
||||
player: musicData.player,
|
||||
music_id: musicData.music_id,
|
||||
music_type: musicData.music_type,
|
||||
}, {
|
||||
$set: musicData
|
||||
}, {
|
||||
upsert: true
|
||||
});
|
||||
};
|
||||
|
||||
async insertPlayLog(playlog: PlayerPlayLog) {
|
||||
return this.playLogCol.insertOne(playlog);
|
||||
}
|
||||
|
||||
async hasPlayerData(token: string) {
|
||||
return !!await this.playDataCol.countDocuments({ _id: token });
|
||||
}
|
||||
|
||||
async savePlayerData(token: string, data: SaveData, name?: string) {
|
||||
async savePlayerData(token: string, data: SaveData, name?: string): Promise<boolean> {
|
||||
const bin = await promisify(brotliCompress)(JSON.stringify(data));
|
||||
const $set = {
|
||||
save_data: new Binary(bin),
|
||||
@@ -33,7 +66,10 @@ export class UserService {
|
||||
$set['name'] = name;
|
||||
}
|
||||
|
||||
return this.playDataCol.updateOne({ _id: token }, { $set }, { upsert: true })
|
||||
writeFileSync('pdata.json', await promisify(brotliDecompress)(bin));
|
||||
|
||||
const result = await this.playDataCol.updateOne({ _id: token }, { $set }, { upsert: true })
|
||||
return !!(result.modifiedCount ? result.modifiedCount : result.upsertedCount);
|
||||
}
|
||||
|
||||
async getPlayerData(token: string, options?: FindOptions): Promise<SaveData | undefined> {
|
||||
@@ -95,7 +131,7 @@ export class UserService {
|
||||
param: {},
|
||||
present: {},
|
||||
cloud: {
|
||||
relation: v.s8(0),
|
||||
relation: v.s8(1),
|
||||
},
|
||||
ea_shop: {
|
||||
shop_item: {},
|
||||
|
||||
+63
-1
@@ -1,7 +1,69 @@
|
||||
import { Binary } from "mongodb";
|
||||
import { Binary, ObjectId } from "mongodb";
|
||||
|
||||
export interface PlayerPlayData {
|
||||
_id: string;
|
||||
name: string;
|
||||
save_data: Binary;
|
||||
}
|
||||
|
||||
export interface PlayerMusicData {
|
||||
_id: ObjectId;
|
||||
player: string;
|
||||
|
||||
music_id: number;
|
||||
music_type: number;
|
||||
|
||||
score: number;
|
||||
exscore: number;
|
||||
clear_type: number;
|
||||
score_grade: number;
|
||||
max_chain: number;
|
||||
play_count: number;
|
||||
btn_rate: number;
|
||||
long_rate: number;
|
||||
vol_rate: number;
|
||||
|
||||
best_play_time: Date;
|
||||
}
|
||||
|
||||
export interface PlayerPlayLog {
|
||||
_id: ObjectId;
|
||||
player: string;
|
||||
play_time: Date;
|
||||
play_id: number;
|
||||
track_no: number;
|
||||
music_id: number;
|
||||
music_type: number;
|
||||
score: number;
|
||||
exscore: number;
|
||||
clear_type: number;
|
||||
score_grade: number;
|
||||
max_chain: number;
|
||||
just: number;
|
||||
critical: number;
|
||||
near: number;
|
||||
error: number;
|
||||
effective_rate: number;
|
||||
btn_rate: number;
|
||||
long_rate: number;
|
||||
vol_rate: number;
|
||||
mode: number;
|
||||
gauge_type: number;
|
||||
notes_option: number;
|
||||
online_num: number;
|
||||
local_num: number;
|
||||
challenge_type: number;
|
||||
retry_cnt: number;
|
||||
pad_type: number;
|
||||
judge: number[];
|
||||
drop_frame: number;
|
||||
drop_frame_max: number;
|
||||
drop_count: number;
|
||||
etc: string;
|
||||
mix_id: number;
|
||||
mix_like: boolean;
|
||||
matching: {
|
||||
score: number;
|
||||
}[];
|
||||
is_blaster: boolean;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface SaveData {
|
||||
|
||||
skill: { course?: Course[] };
|
||||
|
||||
param: { info?: Item[] };
|
||||
param: { info?: Param[] };
|
||||
|
||||
present: { info?: Item[] };
|
||||
cloud: { relation: KS8 };
|
||||
@@ -84,3 +84,9 @@ export interface Item {
|
||||
id: KU32;
|
||||
param: KU32;
|
||||
}
|
||||
|
||||
export interface Param {
|
||||
type: KS32;
|
||||
id: KS32;
|
||||
param: KS32;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { to_bin, to_xml } from "@kamyu/kbinxml";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import _ from "lodash";
|
||||
import { Serializable } from "./kxml-value.js";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
export const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
@@ -227,5 +228,6 @@ function serializeObject(obj: Serializable, name: string, linePrefix: string = '
|
||||
|
||||
export function toKBinXml(topName: string, obj: Serializable, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8') {
|
||||
const xml = `<?xml version="1.0" encoding="${encoding}"?>` + serializeObject(obj, topName);
|
||||
writeFileSync('dump.xml', xml);
|
||||
return to_bin(xml);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user