Merge pull request #1 from asphyxia-core/stable

Follow up
This commit is contained in:
LatoWolf 2021-02-21 01:06:15 +08:00 committed by GitHub
commit d95b93077c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 4704 additions and 1145 deletions

10
.gitignore vendored
View File

@ -1,4 +1,12 @@
# Editor configs
.vscode
# External modules
node_modules
package-lock.json
# Project files
asphyxia-core.d.ts
package.json
package-lock.json
typedoc.json
tsconfig.json

View File

@ -21,3 +21,5 @@ I'll do my best to merge PR, but please make sure you are submitting code target
## How do I make plugins?
Checkout our [Documentation](https://asphyxia-core.github.io/typedoc/) and maybe consider join our [Discord](https://discord.gg/3TW3BDm) server. Make sure to familiar yourself with at least XML and Typescript/Javascript.
Note that you should run `npm install` to install typing for node and lodash, and launch CORE using `--dev` arguments to enable console log and typechecking when using typescript.

1115
asphyxia-core.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
GITADORA Plugin for Asphyxia-Core
=================================
This plugin is converted from public-exported Asphyxia's Routes.
Supported Versions
==================
- Matixx
- Exchain
Release Notes
=============
v1.0.0 (Current)
----------------
* Initial release for public

View File

@ -0,0 +1 @@
export const PLUGIN_VER = 1;

5
gitadora@asphyxia/data/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
mdb_ex.xml
mdb_mt.xml
mdb_ex.b64
mdb_mt.b64
custom_mdb.xml

View File

@ -0,0 +1,58 @@
import { CommonMusicData, readJSONOrXML, readXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML('data/mdb_ex.json', 'data/mdb_ex.xml', processRawData)
return {
music,
};
}
export async function processRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver")),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

View File

@ -0,0 +1,58 @@
import { CommonMusicData, readJSONOrXML, readXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML('data/mdb_mt.json', 'data/mdb_mt.xml', processRawData)
return {
music,
};
}
export async function processRawData(path: string): Promise<CommonMusicData> {
const data = await readXML(path)
const mdb = $(data).elements("mdb.mdb_data");
const music: any[] = [];
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver")),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
return {
music,
};
}

View File

@ -0,0 +1,36 @@
export interface CommonMusicDataField {
id: KITEM<"s32">;
cont_gf: KITEM<"bool">;
cont_dm: KITEM<"bool">;
is_secret: KITEM<"bool">;
is_hot: KITEM<"bool">;
data_ver: KITEM<"s32">;
diff: KARRAY<"u16">;
}
export interface CommonMusicData {
music: CommonMusicDataField[]
}
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
}
export async function readJSONOrXML(jsonPath: string, xmlPath: string, processHandler: (path: string) => Promise<CommonMusicData>): Promise<CommonMusicData> {
if (!IO.Exists(jsonPath)) {
const data = await processHandler(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
return data
} else {
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
return json
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,80 @@
import { getVersion } from "../utils";
import { processData as ExchainMusic } from "../data/Exchain"
import { processData as MatixxMusic } from "../data/Matixx"
import { CommonMusicDataField, readJSONOrXML, readXML } from "../data/helper";
export const playableMusic: EPR = async (info, data, send) => {
const version = getVersion(info);
let music: CommonMusicDataField[] = [];
try {
if (U.GetConfig("enable_custom_mdb")) {
const data = await readXML('data/custom_mdb.xml')
const mdb = $(data).elements("mdb.mdb_data");
for (const m of mdb) {
const d = m.numbers("xg_diff_list");
const contain = m.numbers("contain_stat");
const gf = contain[0];
const dm = contain[1];
if (gf == 0 && dm == 0) {
continue;
}
let type = gf;
if (gf == 0) {
type = dm;
}
music.push({
id: K.ITEM('s32', m.number("music_id")),
cont_gf: K.ITEM('bool', gf == 0 ? 0 : 1),
cont_dm: K.ITEM('bool', dm == 0 ? 0 : 1),
is_secret: K.ITEM('bool', 0),
is_hot: K.ITEM('bool', type == 2 ? 0 : 1),
data_ver: K.ITEM('s32', m.number("data_ver", 115)),
diff: K.ARRAY('u16', [
d[0],
d[1],
d[2],
d[3],
d[4],
d[10],
d[11],
d[12],
d[13],
d[14],
d[5],
d[6],
d[7],
d[8],
d[9],
]),
});
}
}
} catch (e) {
console.error(e.stack);
console.error("Fallback: Using default MDB method.")
music = [];
}
if (music.length == 0) {
if (version == 'exchain') {
music = _.get(await ExchainMusic(), 'music', []);
} else {
music = _.get(await MatixxMusic(), 'music', []);
}
}
await send.object({
hot: {
major: K.ITEM('s32', 1),
minor: K.ITEM('s32', 1),
},
musicinfo: K.ATTR({ nr: `${music.length}` }, {
music,
}),
});
};

View File

@ -0,0 +1,143 @@
export const shopInfoRegist: EPR = async (info, data, send) => {
send.object({
data: {
cabid: K.ITEM('u32', 1),
locationid: K.ITEM('str', 'Asphyxia'),
},
temperature: {
is_send: K.ITEM('bool', 0),
},
tax: {
tax_phase: K.ITEM('s32', 0),
},
})
}
export const gameInfoGet: EPR = async (info, data, send) => {
const addition: any = {
monstar_subjugation: {
bonus_musicid: K.ITEM('s32', 0),
},
bear_fes: {},
};
const time = BigInt(31536000);
for (let i = 1; i <= 20; ++i) {
const obj = {
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', time),
end_date_ms: K.ITEM('u64', time),
};
if (i == 1) {
addition[`phrase_combo_challenge`] = obj;
addition[`long_otobear_fes_1`] = {
term: K.ITEM('u8', 0),
start_date_ms: K.ITEM('u64', time),
end_date_ms: K.ITEM('u64', time),
bonus_musicid: {},
};
addition[`sdvx_stamprally3`] = obj;
addition[`chronicle_1`] = obj;
addition[`paseli_point_lottery`] = obj;
addition['sticker_campaign'] = {
term: K.ITEM('u8', 0),
sticker_list: {},
};
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
if (i <= 4) {
addition['monstar_subjugation'][`monstar_subjugation_${i}`] = obj;
addition['bear_fes'][`bear_fes_${i}`] = obj;
}
if (i <= 3) {
addition[`kouyou_challenge_${i}`] = {
term: K.ITEM('u8', 0),
bonus_musicid: K.ITEM('s32', 0),
};
}
}
await send.object({
now_date: K.ITEM('u64', time),
extra: {
extra_lv: K.ITEM('u8', 10),
extramusic: {
music: {
musicid: K.ITEM('s32', 0),
get_border: K.ITEM('u8', 0),
},
},
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('u8', 0) },
battle: { term: K.ITEM('u8', 0) },
battle_chara: { term: K.ITEM('u8', 0) },
data_ver_limit: { term: K.ITEM('u8', 0) },
ea_pass_propel: { term: K.ITEM('u8', 0) },
monthly_skill: {
term: K.ITEM('u8', 0),
target_music: {
music: {
musicid: K.ITEM('s32', 0),
},
},
},
update_prog: { term: K.ITEM('u8', 0) },
rockwave: { event_list: {} },
general_term: {},
jubeat_omiyage_challenge: {},
kac2017: {},
nostalgia_concert: {},
trbitemdata: {},
ctrl_movie: {},
ng_jacket: {},
ng_recommend_music: {},
ranking: {
skill_0_999: {},
skill_1000_1499: {},
skill_1500_1999: {},
skill_2000_2499: {},
skill_2500_2999: {},
skill_3000_3499: {},
skill_3500_3999: {},
skill_4000_4499: {},
skill_4500_4999: {},
skill_5000_5499: {},
skill_5500_5999: {},
skill_6000_6499: {},
skill_6500_6999: {},
skill_7000_7499: {},
skill_7500_7999: {},
skill_8000_8499: {},
skill_8500_9999: {},
total: {},
original: {},
bemani: {},
famous: {},
anime: {},
band: {},
western: {},
},
processing_report_state: K.ITEM('u8', 0),
assert_report_state: K.ITEM('u8', 0),
recommendmusic: { '@attr': { nr: 0 } },
demomusic: { '@attr': { nr: 0 } },
event_skill: {},
temperature: { is_send: K.ITEM('bool', 0) },
bemani_summer_2018: { is_open: K.ITEM('bool', 0) },
kac2018: {
event: {
term: K.ITEM('s32', 0),
since: K.ITEM('u64', BigInt(0)),
till: K.ITEM('u64', BigInt(0)),
is_open: K.ITEM('bool', 0),
target_music: {
music_id: K.ARRAY('s32', [0, 0, 0, 0, 0, 0]),
},
},
},
...addition,
});
};

View File

@ -0,0 +1,993 @@
import { PlayerInfo } from "../models/playerinfo";
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";
export const regist: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) return send.deny();
const no = getPlayerNo(data);
const version = getVersion(info);
const playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
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();
const no = getPlayerNo(data);
const version = getVersion(info)
const playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
})
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();
const no = getPlayerNo(data);
const version = getVersion(info);
const time = BigInt(31536000);
const dm = isDM(info);
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
})
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: any[] = [];
if (_.isArray(name.card)) {
for (const item of name.card) {
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;
}
sticker.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),
});
}
}
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),
},
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 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) };
}
}
send.object({
player: K.ATTR({ 'no': `${no}` }, {
now_date: K.ITEM('u64', time),
secretmusic: {
music: {
musicid: K.ITEM('s32', 0),
seq: K.ITEM('u16', 255),
kind: K.ITEM('s32', 40),
},
},
chara_list: {},
title_parts: {},
information: {
info: K.ARRAY('u32', Array(50).fill(0)),
},
reward: {
status: K.ARRAY('u32', Array(50).fill(0)),
},
rivaldata: {},
frienddata: {},
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', [0, 0, 0, 0, 0]),
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', 1), total_nr: K.ITEM('s32', 1) },
all_skill: { rank: K.ITEM('s32', 1), total_nr: K.ITEM('s32', 1) },
},
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),
},
...addition,
...playerData,
finish: K.ITEM('bool', 1),
}),
});
}
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
}
};
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),
}
}
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 savePlayer: EPR = async (info, data, send) => {
const refid = $(data).str('player.refid');
if (!refid) return send.deny();
const no = getPlayerNo(data);
const version = getVersion(info);
const dm = isDM(info);
const game = dm ? 'dm' : 'gf';
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) {
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])
}
};
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('playstyle', 'customdata.playstyle', true);
autoExtra('custom', 'customdata.custom', 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 stages = $(data).elements('player.stage');
const scores = (await getScore(refid, version, game)).scores;
for (const stage of stages) {
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] = {
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,
};
}
await saveScore(refid, version, game, scores);
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]),
},
},
}),
gamemode: _.get(data, 'gamemode'),
});
};
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
})
}

View File

@ -0,0 +1,28 @@
import { PlayerInfo } from "../models/playerinfo"
export const updatePlayerInfo = async (data: {
refid: string;
version: string;
name?: string;
title?: string;
}) => {
if (data.refid == null) return;
const update: Update<PlayerInfo>['$set'] = {};
if (data.name && data.name.length > 0) {
//TODO: name validator
update.name = data.name;
}
if (data.title && data.title.length > 0) {
//TODO: title validator
update.title = data.title;
}
await DB.Update<PlayerInfo>(
data.refid,
{ collection: 'playerinfo', version: data.version },
{ $set: update }
);
};

View File

@ -0,0 +1,49 @@
import { gameInfoGet, shopInfoRegist } from "./handlers/info";
import { playableMusic } from "./handlers/MusicList"
import { getPlayer, check, regist, savePlayer } from "./handlers/profiles";
import { updatePlayerInfo } from "./handlers/webui";
import { isRequiredVersion } from "./utils";
export function register() {
if(!isRequiredVersion(1, 20)) {
console.error("You need newer version of Core. v1.20 or newer required.")
}
R.GameCode('M32');
R.Config("enable_custom_mdb", {
name: "Enable Custom MDB",
desc: "For who uses own MDB",
type: "boolean",
default: false,
})
R.DataFile("data/custom_mdb.xml", {
accept: ".xml",
name: "Custom MDB",
desc: "You need to enable Custom MDB option first."
})
R.WebUIEvent('updatePlayerInfo', updatePlayerInfo);
const MultiRoute = (method: string, handler: EPR | boolean) => {
// Helper for register multiple versions.
R.Route(`exchain_${method}`, handler);
R.Route(`matixx_${method}`, handler);
// TODO: NEXTAGE
// TODO: TB, TBRE and more older version?
};
// Info
MultiRoute('shopinfo.regist', shopInfoRegist)
MultiRoute('gameinfo.get', gameInfoGet)
// MusicList
MultiRoute('playablemusic.get', playableMusic)
// Profile
MultiRoute('cardutil.regist', regist);
MultiRoute('cardutil.check', check);
MultiRoute('gametop.get', getPlayer);
MultiRoute('gameend.regist', savePlayer);
}

View File

@ -0,0 +1,14 @@
export interface Extra {
collection: 'extra';
game: 'gf' | 'dm';
version: string;
pluginVer: number
id: number;
playstyle: number[];
custom: number[];
list_1: number[];
list_2: number[];
list_3: number[];
}

View File

@ -0,0 +1,19 @@
export interface PlayerInfo {
collection: 'playerinfo',
pluginVer: Number;
id: number;
version: string,
name: string;
title: string;
card?: {
id: number;
position: number[];
scale: number[];
rotation: number;
}[];
// TODO: Add Board things.
}

View File

@ -0,0 +1,57 @@
export interface Profile {
collection: 'profile';
game: 'gf' | 'dm';
version: string;
pluginVer: number
id: number;
play: number;
playtime: number;
playterm: number;
session_cnt: number;
extra_stage: number;
extra_play: number;
extra_clear: number;
encore_play: number;
encore_clear: number;
pencore_play: number;
pencore_clear: number;
max_clear_diff: number;
max_full_diff: number;
max_exce_diff: number;
clear_num: number;
full_num: number;
exce_num: number;
no_num: number;
e_num: number;
d_num: number;
c_num: number;
b_num: number;
a_num: number;
s_num: number;
ss_num: number;
last_category: number;
last_musicid: number;
last_seq: number;
disp_level: number;
progress: number;
disp_state: number;
skill: number;
all_skill: number;
extra_gauge: number;
encore_gauge: number;
encore_cnt: number;
encore_success: number;
unlock_point: number;
max_skill: number;
max_all_skill: number;
clear_diff: number;
full_diff: number;
exce_diff: number;
clear_music_num: number;
full_music_num: number;
exce_music_num: number;
clear_seq_num: number;
classic_all_skill: number;
}

View File

@ -0,0 +1,44 @@
export interface Record {
collection: 'record';
game: 'gf' | 'dm';
version: string;
pluginVer: number
diff_100_nr: number;
diff_150_nr: number;
diff_200_nr: number;
diff_250_nr: number;
diff_300_nr: number;
diff_350_nr: number;
diff_400_nr: number;
diff_450_nr: number;
diff_500_nr: number;
diff_550_nr: number;
diff_600_nr: number;
diff_650_nr: number;
diff_700_nr: number;
diff_750_nr: number;
diff_800_nr: number;
diff_850_nr: number;
diff_900_nr: number;
diff_950_nr: number;
diff_100_clear: number[];
diff_150_clear: number[];
diff_200_clear: number[];
diff_250_clear: number[];
diff_300_clear: number[];
diff_350_clear: number[];
diff_400_clear: number[];
diff_450_clear: number[];
diff_500_clear: number[];
diff_550_clear: number[];
diff_600_clear: number[];
diff_650_clear: number[];
diff_700_clear: number[];
diff_750_clear: number[];
diff_800_clear: number[];
diff_850_clear: number[];
diff_900_clear: number[];
diff_950_clear: number[];
}

View File

@ -0,0 +1,24 @@
export interface Scores {
collection: 'scores';
game: 'gf' | 'dm';
version: string;
pluginVer: number
scores: {
[mid: string]: {
update: number[];
diffs: {
[seq: string]: {
perc: number;
rank: number;
clear: boolean;
fc: boolean;
ex: boolean;
meter: string;
prog: number;
};
};
};
};
}

View File

@ -0,0 +1,19 @@
export const isGF = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'A';
};
export const isDM = (info: EamuseInfo) => {
return info.model.split(':')[2] == 'B';
};
export const getVersion = (info: EamuseInfo) => {
const moduleName: string = info.module;
return moduleName.match(/([^_]*)_(.*)/)[1];
};
export function isRequiredVersion(major: number, minor: number) {
// version value exposed since Core v1.19
const core_major = typeof CORE_VERSION_MAJOR === "number" ? CORE_VERSION_MAJOR : 1
const core_minor = typeof CORE_VERSION_MINOR === "number" ? CORE_VERSION_MINOR : 18
return core_major >= major && core_minor >= minor
}

View File

@ -0,0 +1,36 @@
//DATA//
info: DB.Find(refid, { collection: 'playerinfo' })
-
div
each i in info
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| User Detail (#{i.version})
.card-content
form(method="post" action="/emit/updatePlayerInfo")
.field
label.label ID
.control
input.input(type="text" name="refid", value=refid readonly)
.field
label.label Version
.control
input.input(type="text" name="version", value=i.version readonly)
.field
label.label Name
.control
input.input(type="text" name="name", value=i.name)
.field
label.label Title
.control
input.input(type="text" name="title", value=i.title)
.field
button.button.is-primary(type="submit")
span.icon
i.mdi.mdi-check
span Submit

27
museca@asphyxia/README.md Normal file
View File

@ -0,0 +1,27 @@
MUSECA
======
Plugin Version: **v1.0.0**
Supported Versions
------------------
- 1+1/2
- [MUSECA PLUS](https://museca.plus/) (2020-11-27)
For who plays MUSECA PLUS
-------------------------
If you have a version that not supported on plugin, try Custom MDB feature.
The mdb file is located on `museca-plus/museca/xml/music-info-b.xml`
Only Initial support for now.
-----------------------------
Course is not implemented yet.
Also, Score-Save is only proofed for working correctly. I didn't tested other features. sorry!
Changelog
=========
1.0.0 (Current)
---------------
Initial Support.

1
museca@asphyxia/data/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.xml

View File

@ -0,0 +1,12 @@
import { processMdbData,readJSONOrXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML("./data/mdb_community_plus.json", "./data/mdb_community_plus.xml")
return {
music
};
}
export async function processRawData() {
return await processMdbData("./data/mdb_community_plus.xml")
}

View File

@ -0,0 +1,12 @@
import { processMdbData,readJSONOrXML } from './helper';
export async function processData() {
const { music } = await readJSONOrXML("./data/mdb_one_plus_half.json", "./data/mdb_one_plus_half.xml")
return {
music
};
}
export async function processRawData() {
return await processMdbData("./data/mdb_one_plus_half.xml");
}

View File

@ -0,0 +1,57 @@
export interface CommonMusicDataField {
music_id: KITEM<"s32">;
music_type: KITEM<"u8">;
limited: KITEM<"u8">;
}
interface CommonMusicData {
music: CommonMusicDataField[]
}
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
}
export async function readJSONOrXML(jsonPath: string, xmlPath: string): Promise<CommonMusicData> {
const str: string | null = await IO.ReadFile(jsonPath, 'utf-8');
if (str == null || str.length == 0) {
const data = await processMdbData(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
return data
} else {
const json = JSON.parse(str)
return json
}
}
export async function processMdbData(path: string): Promise<CommonMusicData> {
const data = await readXML(path);
const mdb = $(data).elements("mdb.music");
const diff_list = ["novice", "advanced", "exhaust", "infinite"]
const music: CommonMusicDataField[] = [];
for (const m of mdb) {
for (const [i, d] of diff_list.entries()) {
const elem = m.element(`difficulty.${d}`)
if (elem.number("difnum", 0) == 0) {
continue;
}
music.push({
music_id: K.ITEM("s32", parseInt(m.attr().id)),
music_type: K.ITEM("u8", i),
limited: K.ITEM("u8", elem.number("limited"))
});
}
};
return {
music,
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,88 @@
import * as path from "path"
import { processMdbData } from "../data/helper"
import { processData as processCommunityPlusData } from "../data/CommunityPlusMDB"
import { processData as processOnePlusHalfData } from "../data/OnePlusHalfMDB"
export const shop: EPR = async (info, data, send) => {
// Ignore shop name setter.
send.object({
nxt_time: K.ITEM("u32", 1000 * 5 * 60)
})
}
export const common: EPR = async (info, data, send) => {
let { music } = U.GetConfig("enable_custom_mdb")
? await processCustomData()
: (await processValidData(info))
if (music.length === 0) {
music = (await processValidData(info)).music
}
if (U.GetConfig("unlock_all_songs")) {
music.forEach(element => {
element.limited = K.ITEM("u8", 3)
});
}
// Flags
const event_list = [1, 83, 130, 194, 195, 98, 145, 146, 147, 148, 149, 56, 86, 105, 140, 211, 143]
const event = {
info: event_list.map((e) => {
return {
event_id: K.ITEM("u32", e)
}
})
}
send.object({
music_limited: {
info: music
},
event,
// TODO: Skill course, Extended option.
// skill_course,
// extend
})
}
// TODO: Implement this.
export const hiscore: EPR = async (info, data, send) => {
send.success()
}
export const frozen: EPR = async (info, data, send) => {
send.object({
result: K.ITEM("u8", 0)
})
}
// TODO: Implement this fully.
export const lounge: EPR = async (info, data, send) => {
send.object({
interval: K.ITEM("u32", 10),
// wait
})
}
export const exception: EPR = async (info, data, send) => {
send.success()
}
async function processCustomData() {
return processMdbData("data/custom_mdb.xml")
}
async function processValidData(info: EamuseInfo) {
const version = parseInt(info.model.trim().substr(10), 10)
if (version >= 2020102200) {
// MUSECA PLUS
processCommunityPlusData();
} else /** if (version > 2016071300) */ {
return await processOnePlusHalfData()
} /** else {
// Museca 1
return await processOneData()
}**/
}

View File

@ -0,0 +1,223 @@
import { Profile } from "../models/profile";
import { Scores } from "../models/scores";
import { IDToCode } from "../utils";
export const load: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const profile = await DB.FindOne<Profile>(refid, { collection: "profile" })
if (profile == null) {
// Request New Profile from game side.
return send.object({
result: K.ITEM("u8", 1)
})
}
const item = _.map(profile.item, (v, k) => {
const id = k.replace("g", "")
return {
type: K.ITEM("u8", v.type),
id: K.ITEM("u32", parseInt(id)),
param: K.ITEM("u32", v.param)
}
})
send.object({
hidden_param: K.ARRAY("s32", profile.hidden_param),
play_count: K.ITEM("u32", profile.play_count),
daily_count: K.ITEM("u32", profile.daily_count),
play_chain: K.ITEM("u32", profile.play_chain),
last: {
headphone: K.ITEM("u8", profile.last.headphone),
appeal_id: K.ITEM("u16", profile.last.appeal_id),
comment_id: K.ITEM("u16", profile.last.comment_id),
music_id: K.ITEM("s32", profile.last.music_id),
music_type: K.ITEM("u8", profile.last.music_type),
sort_type: K.ITEM("u8", profile.last.sort_type),
narrow_down: K.ITEM("u8", profile.last.narrow_down),
gauge_option: K.ITEM("u8", profile.last.gauge_option),
},
blaster_energy: K.ITEM("u32", profile.blaster_energy),
blaster_count: K.ITEM("u32", profile.blaster_count),
code: K.ITEM("str", IDToCode(profile.code)),
name: K.ITEM("str", profile.name),
creator_id: K.ITEM("u32", profile.creator_id),
skill_level: K.ITEM("s16", profile.skill_level),
skill_name_id: K.ITEM("s16", profile.skill_name_id),
gamecoin_packet: K.ITEM("u32", profile.gamecoin_packet),
gamecoin_block: K.ITEM("u32", profile.gamecoin_block),
item: {
info: item
},
param: {},
result: K.ITEM("u8", 0),
ea_shop: {
packet_booster: K.ITEM("s32", 0),
block_booster: K.ITEM("s32", 0)
}
})
}
export const load_m: EPR = async (info, data, send) => {
const refid = $(data).str('dataid');
if (!refid) return send.deny();
const scores = (await DB.FindOne<Scores>(refid, { collection: 'scores'})).scores
const music: any[] = [];
for (const mid in scores) {
for (const type in scores[mid]) {
let score = scores[mid][type]
music.push({
music_id: K.ITEM("u32", parseInt(mid)),
music_type: K.ITEM("u32", parseInt(type)),
score: K.ITEM("u32", score.score),
cnt: K.ITEM("u32", score.count),
clear_type: K.ITEM("u32", score.clear_type),
score_grade: K.ITEM("u32", score.score_grade),
btn_rate: K.ITEM("u32", score.btn_rate),
long_rate: K.ITEM("u32", score.long_rate),
vol_rate: K.ITEM("u32", score.vol_rate)
})
};
};
send.object({
new: {
music
},
// This field seems used on Museca 1, Ignore this.
old: {}
})
}
export const save: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const dbItem = (await DB.FindOne<Profile>(refid, { collection: "profile" })).item
for(const item of $(data).elements("item.info")) {
const id = item.number("id");
const type = item.number("type")
// Grafica and Mission shares same ID. Why?????
dbItem[type == 16 ? `g${id}` : id] = {
type,
param : item.number("param")
}
}
await DB.Upsert<Profile>(refid, { collection: "profile" }, {
$set: {
last: {
headphone: $(data).number("headphone"),
appeal_id: $(data).number("appeal_id"),
comment_id: $(data).number("comment_id"),
music_id: $(data).number("music_id"),
music_type: $(data).number("music_type"),
sort_type: $(data).number("sort_type"),
narrow_down: $(data).number("narrow_down"),
gauge_option: $(data).number("gauge_option"),
},
hidden_param: $(data).numbers("hidden_param"),
blaster_count: $(data).number("blaster_count"),
item: dbItem,
},
$inc: {
blaster_energy: $(data).number("earned_blaster_energy"),
gamecoin_block: $(data).number("earned_gamecoin_block"),
gamecoin_packet: $(data).number("earned_gamecoin_packet")
}
})
send.success()
}
export const save_m: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const scores = (await DB.FindOne<Scores>(refid, { collection: "scores" })).scores
const mid = $(data).number("music_id")
const type = $(data).number("music_type")
if (!scores[mid]) {
scores[mid] = {};
}
scores[mid][type] = {
score: Math.max(_.get(scores[mid][type], 'score', 0), $(data).number("score")),
clear_type: Math.max(_.get(scores[mid][type], 'clear_type', 0), $(data).number("clear_type")),
score_grade: Math.max(_.get(scores[mid][type], 'score_grade', 0), $(data).number("score_grade")),
count: _.get(scores[mid][type], 'count', 0) + 1,
btn_rate: Math.max(_.get(scores[mid][type], 'btn_rate', 0), $(data).number("btn_rate")),
long_rate: Math.max(_.get(scores[mid][type], 'long_rate', 0), $(data).number("long_rate")),
vol_rate: Math.max(_.get(scores[mid][type], 'vol_rate', 0), $(data).number("vol_rate")),
};
const store: Scores = {
collection: "scores",
scores
}
await DB.Upsert<Scores>(refid, { collection: "scores" }, store)
send.success()
}
export const newProfile: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const name = $(data).str('name', 'NONAME');
let code = _.random(0, 99999999);
while (await DB.FindOne<Profile>(null, { collecttion: 'profile', code })) {
code = _.random(0, 99999999);
}
let defItem = {};
for(let i = 1; i < 801; i++) {
defItem[i] = {
type: 4,
param : 1
}
}
const profile: Profile = {
collection: "profile",
code,
name,
hidden_param: Array(20).fill(0),
play_count: 0,
daily_count: 0,
play_chain: 0,
last: {
headphone: 0,
appeal_id: 0,
comment_id: 0,
music_id: 0,
music_type: 0,
sort_type: 0,
narrow_down: 0,
gauge_option: 0,
},
blaster_energy: 0,
blaster_count: 0,
creator_id: 0,
skill_level: 0,
skill_name_id: 0,
gamecoin_packet: 0,
gamecoin_block: 0,
item: defItem,
packet_booster: 0,
block_booster: 0,
}
await DB.Upsert<Profile>(refid, { collection: "profile"}, profile)
await DB.Upsert<Scores>(refid, { collection: "scores" }, { collection: "scores", scores: {}})
send.success()
}

59
museca@asphyxia/index.ts Normal file
View File

@ -0,0 +1,59 @@
import { common, exception, lounge, shop, hiscore, frozen } from "./handlers/common";
import { load, load_m, newProfile, save, save_m } from "./handlers/player";
import { isRequiredVersion } from "./utils";
export function register() {
if(!isRequiredVersion(1, 19)) {
console.error("You need newer version of Core. v1.19 or newer required.")
}
R.GameCode('PIX');
R.Config("unlock_all_songs", {
name: "Force unlock all songs",
type: "boolean",
default: false
})
R.Config("enable_custom_mdb", {
name: "Enable Custom MDB",
desc: "For who uses own MDB",
type: "boolean",
default: false,
})
R.DataFile("data/custom_mdb.xml", {
accept: ".xml",
name: "Custom MDB",
desc: "You need to enable Custom MDB option first."
})
const Route = (method: string, handler: EPR | boolean) => {
// Helper for register multiple versions.
// Use this when plugin supports first version.
R.Route(`game_3.${method}`, handler);
};
// Common
Route("common", common)
Route("shop", shop)
Route("exception", exception)
Route("hiscore", hiscore),
Route("lounge", lounge),
Route("frozen", frozen)
Route("play_e", true)
// Player
Route("new", newProfile)
Route("save", save)
Route("save_m", save_m)
//Route("save_c", save_c)
Route("load", load)
Route("load_m", load_m)
R.Unhandled(async (info, data, send) => {
if (["eventlog"].includes(info.module)) return;
console.error(`Received Unhandled Response on ${info.method} by ${info.model}/${info.module}`)
console.error(`Received Request: ${JSON.stringify(data, null, 4)}`)
})
}

View File

@ -0,0 +1,38 @@
export interface Profile {
collection: 'profile';
code: number;
name: string;
hidden_param: number[];
play_count: number;
daily_count: number;
play_chain: number;
last: {
headphone: number;
appeal_id: number;
comment_id: number;
music_id: number;
music_type: number;
sort_type: number;
narrow_down: number;
gauge_option: number;
},
blaster_energy: number;
blaster_count: number;
creator_id: number;
skill_level: number;
skill_name_id: number;
gamecoin_packet: number;
gamecoin_block: number;
item: {
[id: number]: {
type: number,
param: number
}
}
packet_booster: number;
block_booster: number;
}

View File

@ -0,0 +1,17 @@
export interface Scores {
collection: 'scores',
scores: {
[mid: string]: {
[type: string]: {
score: number;
count: number;
clear_type: number;
score_grade: number;
btn_rate: number;
long_rate: number;
vol_rate: number;
};
}
};
}

11
museca@asphyxia/utils.ts Normal file
View File

@ -0,0 +1,11 @@
export function IDToCode(id: number) {
const padded = _.padStart(id.toString(), 8);
return `${padded.slice(0, 4)}-${padded.slice(4)}`;
}
export function isRequiredVersion(major: number, minor: number) {
// version value exposed since Core v1.19
const core_major = typeof CORE_VERSION_MAJOR === "number" ? CORE_VERSION_MAJOR : 1
const core_minor = typeof CORE_VERSION_MINOR === "number" ? CORE_VERSION_MINOR : 18
return core_major >= major && core_minor >= minor
}

View File

@ -0,0 +1,33 @@
# Nostalgia
Plugin Version: **v1.2.0**
Supported Versions
-------------------
- ノスタルジア/ First Version (Experiment-Old)
- Forte (Experiment-Old)
- Op.2
About Experiment-Old Support
----------------------------
A version that marked as **Experiment-Old** is _Not_ Primary supported experiment version.
Since This plugin is mainly focused on Op.2, other versions may not work correctly.
If you have a problem that move from old version to new version, There's webui for mitigate the issue.
Changelog
=========
1.2.0 (Current)
---------------
- Nostalgia First version support.
1.1.0
-----
- Fix saving issue with brooch, island, and kentei.
- Moved to Base64 encoded data base.
- Forte support.
1.0.0
-----
Initial Release.

3
nostalgia@asphyxia/data/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.json
*.json
*.xml

View File

@ -0,0 +1,11 @@
import { CommonMusicData, readJSONOrXML } from "./ForteMusic";
import { readB64JSON } from "./helper";
export async function processData(): Promise<CommonMusicData> {
if (IO.Exists("data/first_mdb.json.b64")) {
return await readB64JSON("data/first_mdb.json.b64");
}
const data = await readJSONOrXML("data/first_mdb.json", "data/first_mdb.xml")
// await IO.WriteFile("data/first_mdb.json.b64", Buffer.from(JSON.stringify(data)).toString("base64"))
return data;
}

View File

@ -0,0 +1,70 @@
import { CommonMusicDataField, readB64JSON, readXML } from "./helper";
export async function processData(): Promise<CommonMusicData> {
if (IO.Exists("data/forte_mdb.json.b64")) {
return await readB64JSON("data/forte_mdb.json.b64");
}
const data = await readJSONOrXML("data/forte_mdb.json", "data/forte_mdb.xml")
return data;
}
export async function processMdbData(path: string): Promise<CommonMusicData> {
const data = await readXML(path);
const attr = $(data).attr("music_list");
const mdb = $(data).elements("music_list.music_spec");
const music: CommonMusicDataField[] = [];
for (const m of mdb) {
music.push(K.ATTR({ index: m.attr().index }, {
basename: K.ITEM("str", m.str("basename")),
title: K.ITEM("str", m.str("title", "title")),
title_kana: K.ITEM("str", m.str("title_kana", "title_kana")),
artist: K.ITEM("str", m.str("artist", "artist")),
artist_kana: K.ITEM("str", m.str("artist_kana", "artist_kana")),
priority: K.ITEM("s8", m.number("priority")),
category_flag: K.ARRAY("s32", m.numbers("category_flag")),
primary_category: K.ITEM("s8", m.number("primary_category")),
level_normal: K.ITEM("s8", m.number("level_normal")),
level_hard: K.ITEM("s8", m.number("level_hard")),
level_extreme: K.ITEM("s8", m.number("level_extreme")),
demo_popular: K.ITEM("bool", m.bool("demo_popular")),
demo_bemani: K.ITEM("bool", m.bool("demo_bemani")),
destination_j: K.ITEM("bool", true),
destination_a: K.ITEM("bool", true),
destination_y: K.ITEM("bool", true),
destination_k: K.ITEM("bool", true),
offline: K.ITEM("bool", m.bool("offline")),
unlock_type: K.ITEM("s8", m.number("unlock_type") == 3 ? 1 : m.number("unlock_type")),
volume_bgm: K.ITEM("s8", m.number("volume_bgm")),
volume_key: K.ITEM("s8", m.number("volume_key")),
start_date: K.ITEM("str", m.str("start_date")),
end_date: K.ITEM("str", "9999-12-31 23:59"),
description: K.ITEM("str", m.str("description", "description"))
}));
}
return K.ATTR({
release_code: attr.release_code,
revision: attr.revision,
}, {
music_spec: music,
});
}
export async function readJSONOrXML(jsonPath: string, xmlPath: string): Promise<CommonMusicData> {
if (!IO.Exists(jsonPath)) {
const data = await processMdbData(xmlPath)
await IO.WriteFile(jsonPath, JSON.stringify(data))
await IO.WriteFile(jsonPath.replace(".json", ".json.b64"), Buffer.from((JSON.stringify(data))).toString('base64'));
return data
} else {
const json = JSON.parse(await IO.ReadFile(jsonPath, 'utf-8'))
return json
}
}
export interface CommonMusicData {
"@attr": {
revision: string,
release_code: string
}
music_spec: CommonMusicDataField[]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,43 @@
export interface CommonMusicDataField {
basename: KITEM<"str">;
title: KITEM<"str">;
title_kana: KITEM<"str">;
artist: KITEM<"str">;
artist_kana: KITEM<"str">
priority: KITEM<"s8">;
category_flag: KARRAY<"s32">;
primary_category: KITEM<"s8">;
level_normal: KITEM<"s8">;
level_hard: KITEM<"s8">;
level_extreme: KITEM<"s8">;
demo_popular: KITEM<"bool">;
demo_bemani: KITEM<"bool">
destination_j: KITEM<"bool">;
destination_a: KITEM<"bool">;
destination_y: KITEM<"bool">;
destination_k: KITEM<"bool">;
unlock_type: KITEM<"s8">;
offline: KITEM<"bool">;
volume_bgm: KITEM<"s8">;
volume_key: KITEM<"s8">;
start_date: KITEM<"str">;
end_date: KITEM<"str">;
description: KITEM<"str">;
}
export async function readXML(path: string) {
const xml = await IO.ReadFile(path, 'utf-8');
const json = U.parseXML(xml, false)
return json
}
export async function readJSON(path: string) {
const str = await IO.ReadFile(path, 'utf-8');
const json = JSON.parse(str)
return json
}
export async function readB64JSON(b64path: string) {
const buff = await IO.ReadFile(b64path, 'utf-8');
return JSON.parse(Buffer.from(buff, 'base64').toString('utf-8'));
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,141 @@
import { processData as firstData } from "../data/FirstMusic";
import { processData as forteData } from "../data/ForteMusic";
import { readB64JSON } from "../data/helper";
import { NosVersionHelper } from "../utils";
export const permitted_list = {
flag: [
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '0' }),
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '1' }),
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '2' }),
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '3' }),
],
};
export const forte_permitted_list = {
flag: [
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '0' }),
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '1' }),
K.ARRAY('s32', Array(32).fill(-1), { sheet_type: '2' }),
],
}
async function ReadData(filename: string) {
if (!IO.Exists(`data/${filename}.json.b64`)) {
const xml = await IO.ReadFile(`data/${filename}.xml`, 'utf-8');
const json = U.parseXML(xml, false)
// await IO.WriteFile(`data/${filename}.json.b64`, Buffer.from(JSON.stringify(json)).toString('base64'));
return json
}
return readB64JSON(`data/${filename}.json.b64`)
}
async function processIslandData() {
const islandData = (await ReadData('island')).island_data_list.island_data;
for (const island of islandData) {
island.flag_permitted = K.ITEM('bool', 1);
island.cost['@content'][0] = ~~(island.cost['@content'][0] / 10);
let containers = island.get_music_index_list.container;
if (!_.isArray(containers)) {
containers = [containers];
}
for (const cont of containers) {
cont['@attr'].no = cont['@attr'].container_no;
delete cont['@attr'].container_no;
if (!_.isArray(cont.music)) {
cont.music = [cont.music];
}
for (const m of cont.music) {
m.get_point['@content'][0] = ~~(m.get_point['@content'][0] / 6);
}
}
island.get_music_index_list.container = containers;
};
return { island_data: islandData };
}
async function processCourseData() {
const courseData = (await ReadData('course')).course_data_list.course_data;
for (const course of courseData) {
course.req_nos['@content'][0] = ~~(course.req_nos['@content'][0] / 10);
if (course.seq_list && course.seq_list.tune) {
course.seq_list = course.seq_list.tune;
course.is_open = K.ITEM('bool', 1);
}
}
return { course_data: courseData };
}
export const get_common_info = async (info, data, send) => {
send.object({
permitted_list,
olupdate: {
delete_flag: K.ITEM('bool', 0),
},
});
};
export const get_music_info: EPR = async (info, data, send) => {
const version = new NosVersionHelper(info)
const music_spec: any = [];
for (let i = 1; i < 400; ++i) {
music_spec.push(K.ATTR({ index: `${i}` }, {
jk_jpn: K.ITEM('bool', 1),
jk_asia: K.ITEM('bool', 1),
jk_kor: K.ITEM('bool', 1),
jk_idn: K.ITEM('bool', 1),
start_date: K.ITEM('str', '2017-01-11 10:00'),
end_date: K.ITEM('str', '9999-12-31 23:59'),
expiration_date: K.ITEM('str', '9999-12-31 23:59'),
real_start_date: K.ITEM('str', '2017-01-11 10:00'),
real_end_date: K.ITEM('str', '9999-12-31 23:59'),
real_once_price: K.ITEM('s32', 300),
real_forever_price: K.ITEM('s32', 7500),
}));
}
const music_list = async () => version.version === 'Forte' ? await forteData() : await firstData()
const versionObject = version.isFirstOrForte()
? {
permitted_list: forte_permitted_list,
music_list: await music_list()
}
: {
permitted_list,
island_data_list: await processIslandData(),
course_data_list: await processCourseData(),
overwrite_music_list: K.ATTR({
revision: '16706',
release_code: '2019100200',
}, {
music_spec: music_spec,
}),
};
send.object({
...versionObject,
gamedata_flag_list: {
event: {
index: K.ITEM('s32', 0),
status: K.ITEM('s8', 1),
start_time: K.ITEM('u64', BigInt(0)),
end_time: K.ITEM('u64', BigInt(0)),
param1: K.ITEM('u64', BigInt(0)),
param2: K.ITEM('u64', BigInt(0)),
},
},
olupdate: {
delete_flag: K.ITEM('bool', 0),
},
});
};

View File

@ -0,0 +1,610 @@
import { Profile } from '../models/profile';
import { Scores } from '../models/scores';
import { NosVersionHelper } from '../utils';
import { permitted_list, forte_permitted_list } from './common';
// export const event_list = {
// event: {
// '@attr': {
// index: 1,
// },
// 'status': K.ITEM('s8', 1),
// 'start_time': K.ITEM('u64', BigInt(0)),
// 'end_time': K.ITEM('u64', BigInt(0)),
// },
// };
const getEventInfo = (version: NosVersionHelper) => {
const event: any[] = [];
const event_num = version.getEventMaxIndex()
for (let i = 1; i <= event_num; ++i) {
event.push({
type: K.ITEM('s32', 4),
index: K.ITEM('s32', i),
status: K.ITEM('s8', 1),
start_time: K.ITEM('u64', BigInt(0)),
end_time: K.ITEM('u64', BigInt(0)),
param1: K.ITEM('u64', BigInt(0)),
param2: K.ITEM('u64', BigInt(0)),
});
}
return event;
};
const getPlayerData = async (refid: string, info: EamuseInfo, name?: string) => {
const p = await readProfile(refid);
const version = new NosVersionHelper(info)
if (name && name.length > 0) {
p.name = name;
await writeProfile(refid, p);
}
const param: any[] = [];
for (const t in p.params) {
const para = p.params[t];
param.push(K.ATTR({ type: t }, {
count: K.ITEM('s32', para.length),
params_array: K.ARRAY('s32', para),
}));
}
const brooch: any[] = [];
for (const b in p.brooches) {
if (parseInt(b, 10) > version.getBroochMaxIndex()) continue;
const bData = p.brooches[b];
brooch.push(K.ATTR({ index: b }, {
watch_count: K.ITEM('s32', bData.watch),
level: K.ITEM('s8', bData.level),
invested_steps: K.ITEM('s32', bData.steps),
is_new_brooch: K.ITEM('bool', bData.new),
}));
}
// Unlock brooches
for (let i = 101; i <= 124; ++i) {
if (i > version.getBroochMaxIndex()) continue;
brooch.push(K.ATTR({ index: `${i}` }, {
'watch_count': K.ITEM('s32', 0),
'level': K.ITEM('s8', 1),
'invested_steps': K.ITEM('s32', 0),
'is_new_brooch': K.ITEM('bool', 0),
}));
}
// Forte
const stairs: any[] = [];
for (const s in (p.cat_stairs || defaultProfile.cat_stairs)) {
const stair = (p.cat_stairs || defaultProfile.cat_stairs)[s];
stairs.push(K.ATTR({ index: s }, {
total_steps: K.ITEM("s32", stair.total),
chapter_index: K.ITEM("s32", stair.index),
chapter_steps: K.ITEM("s32", stair.steps),
chapter_goal: K.ITEM("s32", stair.goal)
}));
}
// >= Op2
const kentei_record: any[] = [];
for (const k in p.kentei) {
const kentei = p.kentei[k];
kentei_record.push({
stage_prog: K.ITEM('s8', kentei.stage),
kentei_index: K.ITEM('s32', parseInt(k, 10)),
score: K.ARRAY('s32', kentei.score),
clear_rate: K.ITEM('s32', kentei.rate),
clear_flag: K.ITEM('u32', kentei.flag),
play_count: K.ITEM('s32', kentei.count),
});
}
// >= Op2
const island_progress: any[] = [];
for (const i in p.islands) {
const island = p.islands[i];
const container: any[] = [];
for (const c in island.containers) {
const cont = island.containers[c];
const reward: any[] = [];
for (const r in cont.rewards) {
const rew = cont.rewards[r];
reward.push({
reward_index: K.ITEM('s32', parseInt(r, 10)),
point: K.ITEM('s32', rew),
});
}
container.push({
container_no: K.ITEM('s32', parseInt(c, 10)),
movie_prog: K.ITEM('s8', cont.prog),
reward,
});
}
island_progress.push(K.ATTR({ index: i }, {
lookUnlockWin: K.ITEM('s32', island.look),
select_container: K.ITEM('s32', island.select),
travelledTime: K.ITEM('u32', island.time),
container,
}));
}
const correct_permitted_list = !version.isFirstOrForte() ? permitted_list : forte_permitted_list
const music_list = [
K.ARRAY('s32', p.musicList.type_0, { sheet_type: '0' }),
K.ARRAY('s32', p.musicList.type_1, { sheet_type: '1' }),
K.ARRAY('s32', p.musicList.type_2, { sheet_type: '2' }),
K.ARRAY('s32', p.musicList.type_3, { sheet_type: '3' }),
];
const music_list2 = [
K.ARRAY('s32', p.musicList2.type_0, { sheet_type: '0' }),
K.ARRAY('s32', p.musicList2.type_1, { sheet_type: '1' }),
K.ARRAY('s32', p.musicList2.type_2, { sheet_type: '2' }),
K.ARRAY('s32', p.musicList2.type_3, { sheet_type: '3' }),
];
if(version.isFirstOrForte()) {
music_list.pop();
music_list2.pop();
}
return {
name: K.ITEM('str', p.name),
play_count: K.ITEM('s32', p.playCount),
today_play_count: K.ITEM('s32', p.todayPlayCount),
permitted_list: correct_permitted_list,
event_info_list: { event: getEventInfo(version) }, // Op2
event_control_list: { event: getEventInfo(version) }, // Forte
music_list: {
flag: music_list,
},
free_for_play_music_list: {
flag: music_list2,
},
last: {
music_index: K.ITEM('s32', version.numericHandler('music_index', p.music, 0)),
sheet_type: K.ITEM('s8', version.numericHandler('sheet_type', p.sheet, 0)),
brooch_index: K.ITEM('s32', version.numericHandler('brooch_index', p.brooch, 0)),
hi_speed_level: K.ITEM('s32', p.hispeed),
beat_guide: K.ITEM('s8', p.beatGuide),
headphone_volume: K.ITEM('s8', p.headphone),
judge_bar_pos: K.ITEM('s32', p.judgeBar),
music_group: K.ITEM('s32', p.group),
hands_mode: version.isFirstOrForte() ? K.ITEM('s32', p.mode) : K.ITEM('s8', p.mode),
near_setting: K.ITEM('s8', p.near),
judge_delay_offset: version.isFirstOrForte() ? K.ITEM('s32', p.offset) : K.ITEM('s8', p.offset),
bingo_index: K.ITEM('s32', p.bingo),
total_skill_value: K.ITEM('u64', BigInt(p.skill)),
key_beam_level: K.ITEM('s8', p.keyBeam),
orbit_type: K.ITEM('s8', p.orbit),
note_height: K.ITEM('s8', p.noteHeight),
note_width: K.ITEM('s8', p.noteWidth),
judge_width_type: K.ITEM('s8', p.judgeWidth),
beat_guide_volume: K.ITEM('s8', p.beatVolume),
beat_guide_type: K.ITEM('s8', p.beatType),
key_volume_offset: K.ITEM('s8', p.keyVolume),
bgm_volume_offset: K.ITEM('s8', p.bgmVolume),
note_disp_type: K.ITEM('s8', p.note),
slow_fast: K.ITEM('s8', p.sf),
judge_effect_adjust: K.ITEM('s8', p.judgeFX),
simple_bg: K.ITEM('s8', p.simple),
},
// TODO: Full unlock instead of saving?
cat_progress: {
stair: stairs
},
brooch_list: { brooch },
extra_param: { param },
present_list: {},
various_music_list: {
data: [
K.ATTR({ list_type: '0' }, {
cond_flag: K.ITEM('s32', 0),
flag: K.ITEM('s32', 0, { sheet_type: '0' }),
}),
],
},
island_progress_list: { island_progress },
player_information_list: {},
kentei_record_list: { kentei_record },
linkage_data_list: {},
travel: {
money: K.ITEM('s32', p.money),
fame: K.ITEM('s32', p.fame),
fame_index: K.ITEM('s32', p.fameId),
island_id: K.ITEM('s32', p.island),
},
};
};
export const regist_playdata: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const name = $(data).str('name');
console.debug(`nos op2 regist: ${name}`);
send.object(await getPlayerData(refid, info, name));
};
export const get_playdata: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
send.object(await getPlayerData(refid, info));
};
// export const set_stage_result: EPR = async (info, data, send) => {
// return send.object();
// };
export const set_total_result: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const isForte = new NosVersionHelper(info).isFirstOrForte()
const p = await readProfile(refid);
p.playCount = $(data).number('play_count', p.playCount);
p.todayPlayCount = $(data).number('today_play_count', p.todayPlayCount);
const last = $(data).element('last');
p.music = last.number('music_index', p.music);
p.sheet = last.number('sheet_type', p.sheet);
p.brooch = last.number('brooch_index', p.brooch);
p.hispeed = last.number('hi_speed_level', p.hispeed);
p.beatGuide = last.number('beat_guide', p.beatGuide);
p.headphone = last.number('headphone_volume', p.headphone);
p.judgeBar = last.number('judge_bar_pos', p.judgeBar);
p.group = last.number('music_group', p.group);
p.mode = last.number('hands_mode', p.mode);
p.near = last.number('near_setting', p.near);
p.offset = last.number('judge_delay_offset', p.offset);
p.bingo = last.number('bingo_index', p.bingo);
p.skill = `${last.bigint('total_skill_value') || p.skill}`;
p.keyBeam = last.number('key_beam_level', p.keyBeam);
p.orbit = last.number('orbit_type', p.orbit);
p.noteHeight = last.number('note_height', p.noteHeight);
p.noteWidth = last.number('note_width', p.noteWidth);
p.judgeWidth = last.number('judge_width_type', p.judgeWidth);
p.beatVolume = last.number('beat_guide_volume', p.beatVolume);
p.beatType = last.number('beat_guide_type', p.beatType);
p.keyVolume = last.number('key_volume_offset', p.keyVolume);
p.bgmVolume = last.number('bgm_volume_offset', p.bgmVolume);
p.note = last.number('note_disp_type', p.note);
p.sf = last.number('slow_fast', p.sf);
p.judgeFX = last.number('judge_effect_adjust', p.judgeFX);
p.simple = last.number('simple_bg', p.simple);
p.money = $(data).number('travel.money', p.money);
p.fame = $(data).number('travel.fame', p.fame);
p.fameId = $(data).number('travel.fame_index', p.fameId);
p.island = $(data).number('travel.island_id', p.island);
let flags = _.get($(data).obj, 'music_list.flag', []);
if (!_.isArray(flags)) flags = [flags];
for (const flag of flags) {
const sheet = _.get(flag, '@attr.sheet_type', -1);
if (sheet == '0') {
p.musicList.type_0 = _.get(flag, '@content', p.musicList.type_0);
} else if (sheet == '1') {
p.musicList.type_1 = _.get(flag, '@content', p.musicList.type_1);
} else if (sheet == '2') {
p.musicList.type_2 = _.get(flag, '@content', p.musicList.type_2);
} else if (sheet == '3') {
p.musicList.type_3 = _.get(flag, '@content', p.musicList.type_3);
}
}
let freeFlags = _.get($(data).obj, 'free_for_play_music_list.flag', []);
if (!_.isArray(freeFlags)) freeFlags = [freeFlags];
for (const flag of freeFlags) {
const sheet = _.get(flag, '@attr.sheet_type', -1);
if (sheet == '0') {
p.musicList2.type_0 = _.get(flag, '@content', p.musicList2.type_0);
} else if (sheet == '1') {
p.musicList2.type_1 = _.get(flag, '@content', p.musicList2.type_1);
} else if (sheet == '2') {
p.musicList2.type_2 = _.get(flag, '@content', p.musicList2.type_2);
} else if (sheet == '3') {
p.musicList2.type_3 = _.get(flag, '@content', p.musicList2.type_3);
}
}
// KENTEI
let kenteis = $(data).elements('kentei_result_list.kentei_result');
for (const kentei of kenteis) {
const index = kentei.number('kentei_index', -1);
if (index < 0) continue;
const clearRate = kentei.number('clear_rate', 0);
const oldClearRate = _.get(p, `kentei.${index}.rate`, 0);
const isHigh = clearRate >= oldClearRate;
p.kentei[index] = {
rate: isHigh ? clearRate : oldClearRate,
score: isHigh ? kentei.number('score', 0) : _.get(p, `kentei.${index}.score`, 0),
stage: Math.max(kentei.number('stage_prog', 0), _.get(p, `kentei.${index}.stage`, 0)),
flag: Math.max(kentei.number('clear_flag', 0), _.get(p, `kentei.${index}.flag`, 0)),
count: Math.max(kentei.number('play_count', 0), _.get(p, `kentei.${index}.count`, 0)),
};
}
// PARAMS
let params = $(data).elements('extra_param.param');
for (const param of params) {
const type = param.attr().type;
const parray = param.numbers('params_array');
if (type == null || parray == null) continue;
p.params[type] = parray;
}
// BROOCHES
let broochs = $(data).elements('brooch_list.brooch');
for (const brooch of broochs) {
const index = parseInt(brooch.attr().index || '-1', 10);
if (index < 0) continue;
p.brooches[index] = {
watch: brooch.number('watch_count', 0),
level: brooch.number('level', 1),
steps: brooch.number('invested_steps', 0),
new: brooch.number('is_new_brooch', 0),
};
}
// ISLAND
let islands = $(data).elements('island_progress_list.island_progress');
for (const island of islands) {
const index = parseInt(island.attr().index || '-1', 10);
if (index < 0) continue;
const containers: Profile['islands']['0']['containers'] = {};
let conts = $(data).elements('container');
for (const cont of conts) {
const index = cont.number('container_no', -1);
if (index < 0) continue;
const rewards: { [key: string]: number } = {};
let rews = $(data).elements('reward');
for (const rew of rews) {
const index = rew.number('reward_index', -1);
if (index < 0) continue;
rewards[index] = rew.number('point', 0);
}
containers[index] = {
prog: cont.number('movie_prog', 1),
rewards,
};
}
p.islands[index] = {
look: island.number('lookUnlockWin', 0),
select: island.number('select_container', 0),
time: island.number('travelledTime', 0),
containers,
};
}
// CAT STAIR
let stairs = $(data).elements('cat_progress.stair');
if (!p.cat_stairs) {
p.cat_stairs = defaultProfile.cat_stairs
}
for (const stair of stairs) {
const index = parseInt(stair.attr().index || '-1', 10);
if (index < 0) continue;
p.cat_stairs[index] = {
total: stair.number('total_steps', 0),
index: stair.number('chapter_index', 1),
steps: stair.number('chapter_steps', 0),
goal: stair.number('chapter_goal', 0),
};
}
await writeProfile(refid, p);
const scoreData = await readScores(refid);
// Save Scores
let stages = $(data).elements('stageinfo.stage');
for (const stage of stages) {
const mid = stage.attr().music_index
const type = stage.attr().sheet_type
const key = `${mid}:${type}`;
const c = isForte ? stage : stage.element('common');
const o = _.get(scoreData, `scores.${key}`, {});
const isHigh = c.number('score', 0) >= _.get(o, 'score', 0);
scoreData.scores[key] = {
score: Math.max(c.number('score', 0), _.get(o, 'score', 0)),
grade: Math.max(Math.max(c.number('grade_basic', 0), c.number('evaluation', 0)), _.get(o, 'grade', 0)),
recital: Math.max(c.number('grade_recital', 0), _.get(o, 'recital', 0)),
mode: isHigh ? c.number('hands_mode', 0) : _.get(o, 'mode', 0),
count: Math.max(c.number('play_count', 0), _.get(o, 'count', 1)),
clear: c.number('clear_count', _.get(o, 'clear', 0)),
multi: c.number('multi_count', _.get(o, 'multi', 0)),
flag: Math.max(c.number('clear_flag', 0), _.get(o, 'flag', 0)),
};
}
// Save Recitals
const rInfo = $(data).element('recital_info.recital');
if (rInfo) {
const rIndex = rInfo.number('recital_index', -1);
if (rIndex >= 0) {
const r = rInfo.element('result');
const o = _.get(scoreData, `recitals.${rIndex}`);
const isHigh = r.number('total_score', 0) >= _.get(o, 'score', 0);
scoreData.recitals[rIndex] = {
count: Math.max(rInfo.number('recital_count', 0), _.get(o, 'count', 1)),
hall: isHigh ? rInfo.number('hall_index') : _.get(o, 'hall', 0),
cat: isHigh ? rInfo.numbers('cat_index') : _.get(o, 'cat', [3, 7, 9, 5, 16]),
audience: isHigh ? r.number('audience') : _.get(o, 'audience', 0),
money: isHigh ? r.number('money') : _.get(o, 'money', 0),
fame: isHigh ? r.number('fame') : _.get(o, 'fame', 0),
player: isHigh ? r.number('player_fame') : _.get(o, 'player', 0),
score: isHigh ? r.number('total_score', 0) : _.get(o, 'score', 0),
start: (isHigh ? r.number('recital_start_time', 0) : _.get(o, 'start', 0)).toString(),
end: (isHigh ? r.number('recital_end_time', 0) : _.get(o, 'end', 0)).toString(),
};
}
}
await writeScores(refid, scoreData);
send.success()
};
export const get_musicdata: EPR = async (info, data, send) => {
const refid = $(data).str('refid');
if (!refid) return send.deny();
const version = new NosVersionHelper(info)
const scoreData = await readScores(refid);
const recital_record: any[] = [];
const music: any[] = [];
for (const r in scoreData.recitals) {
const reci = scoreData.recitals[r];
recital_record.push({
recital_index: K.ITEM('s32', parseInt(r, 10)),
recital_count: K.ITEM('s32', reci.count),
hall_index: K.ITEM('s8', reci.hall),
cat_index: K.ARRAY('s16', reci.cat),
audience: K.ITEM('s32', reci.audience),
money: K.ITEM('s32', reci.money),
fame: K.ITEM('s32', reci.fame),
player_fame: K.ITEM('s32', reci.player),
total_score: K.ITEM('s32', reci.score),
total_base_score: K.ITEM('s32', reci.score),
best_start_time: K.ITEM('u64', BigInt(reci.start)),
best_end_time: K.ITEM('u64', BigInt(reci.end)),
});
}
for (const m in scoreData.scores) {
const mdata = m.split(':');
const musi = scoreData.scores[m];
if (parseInt(mdata[0], 10) > version.getMusicMaxIndex()) continue;
music.push(K.ATTR({
music_index: mdata[0],
sheet_type: mdata[1],
}, {
score: K.ITEM('s32', musi.score),
evaluation: K.ITEM('u32', musi.grade), // Forte
grade_basic: K.ITEM('u32', musi.grade),
grade_recital: K.ITEM('u32', musi.recital),
play_count: K.ITEM('s32', musi.count),
clear_count: K.ITEM('s32', musi.clear),
multi_count: K.ITEM('s32', musi.multi),
hands_mode: K.ITEM('s8', musi.mode),
clear_flag: K.ITEM('s32', musi.flag),
}));
}
send.object({
recital_record,
music,
});
};
async function readProfile(refid: string): Promise<Profile> {
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile' })
return profile || defaultProfile
}
async function writeProfile(refid: string, profile: Profile) {
await DB.Upsert<Profile>(refid, { collection: 'profile' }, profile)
}
async function readScores(refid: string): Promise<Scores> {
const score = await DB.FindOne<Scores>(refid, { collection: 'scores' })
return score || { collection: 'scores', recitals: {}, scores: {} }
}
async function writeScores(refid: string, scores: Scores) {
await DB.Upsert<Scores>(refid, { collection: 'scores' }, scores)
}
const defaultProfile: Profile = {
collection: 'profile',
name: 'GUEST',
music: 0,
sheet: 0,
brooch: 0,
hispeed: 0,
beatGuide: 1,
headphone: 0,
judgeBar: 250,
group: 0,
mode: 0,
near: 0,
offset: 0,
bingo: 0,
skill: '0',
playCount: 0,
todayPlayCount: 0,
keyBeam: 0,
orbit: 0,
noteHeight: 10,
noteWidth: 0,
judgeWidth: 0,
beatVolume: 0,
beatType: 0,
keyVolume: 0,
bgmVolume: 0,
note: 0,
sf: 0,
judgeFX: 0,
simple: 0,
money: 0,
fame: 0,
fameId: 0,
island: 0,
brooches: {
'1': {
level: 1,
watch: 0,
steps: 0,
new: 0,
},
},
islands: {},
kentei: {},
cat_stairs: {
'0': {
total: 0,
index: 0,
steps: 0,
goal: 0
}
},
params: {
'1': [0],
},
musicList: {
type_0: Array(32).fill(-1),
type_1: Array(32).fill(-1),
type_2: Array(32).fill(-1),
type_3: Array(32).fill(-1),
},
musicList2: {
type_0: Array(32).fill(-1),
type_1: Array(32).fill(-1),
type_2: Array(32).fill(-1),
type_3: Array(32).fill(-1),
},
}

View File

@ -0,0 +1,20 @@
import { Profile } from "../models/profile";
export const fixIndexBug = async (data: {
refid: string;
confirm: string;
}) => {
if (data.confirm == "on") {
console.warn(`refid "${data.refid}" performs index reset!`)
await DB.Update<Profile>(
data.refid,
{ collection: 'profile' },
{ $set: {
music: 0,
sheet: 0,
brooch: 0
}
}
);
}
};

View File

@ -0,0 +1,40 @@
import { get_common_info, get_music_info } from "./handler/common";
import { get_musicdata, get_playdata, regist_playdata, set_total_result } from "./handler/player"
import { fixIndexBug } from "./handler/webui";
export function register() {
R.GameCode('PAN');
R.WebUIEvent("nosFixIndexBug", fixIndexBug)
const MultiRoute = (method: string, handler: EPR | boolean) => {
// Helper for register multiple versions.
R.Route(method, handler); // First version and Forte.
R.Route(`op2_${method}`, handler);
};
const CommonRoute = (method: string, handler: EPR | boolean) =>
MultiRoute(`common.${method}`, handler)
const PlayerRoute = (method: string, handler: EPR | boolean) =>
MultiRoute(`player.${method}`, handler)
// Common
CommonRoute('get_common_info', get_common_info);
CommonRoute('get_music_info', get_music_info);
// Player
PlayerRoute('get_musicdata', get_musicdata)
PlayerRoute('get_playdata', get_playdata)
PlayerRoute('regist_playdata', regist_playdata)
PlayerRoute('set_total_result', set_total_result)
//TODO: Fix this things with actual working handler.
PlayerRoute('set_stage_result', true)
R.Unhandled(async (info, data, send) => {
if (["eventlog"].includes(info.module)) return;
console.error(`Received Unhandled Response on ${info.method} by ${info.model}/${info.module}`)
console.error(`Received Request: ${JSON.stringify(data, null, 4)}`)
})
}

View File

@ -0,0 +1,90 @@
export interface Profile {
collection: 'profile',
name: string;
playCount: number;
todayPlayCount: number;
music: number;
sheet: number;
brooch: number;
hispeed: number;
beatGuide: number;
headphone: number;
judgeBar: number;
group: number;
mode: number;
near: number;
offset: number;
bingo: number;
skill: string;
keyBeam: number;
orbit: number;
noteHeight: number;
noteWidth: number;
judgeWidth: number;
beatVolume: number;
beatType: number;
keyVolume: number;
bgmVolume: number;
note: number;
sf: number;
judgeFX: number;
simple: number;
money: number;
fame: number;
fameId: number;
island: number;
params: {
[key: string]: number[];
};
brooches: {
[key: string]: {
watch: number;
level: number;
steps: number;
new: number;
};
};
islands: {
[key: string]: {
look: number;
select: number;
time: number;
containers: {
[key: string]: {
prog: number;
rewards: { [key: string]: number };
};
};
};
};
kentei: {
[key: string]: {
stage: number;
score: number[];
rate: number;
flag: number;
count: number;
};
};
cat_stairs: {
[key: string]: {
total: number,
index: number,
steps: number,
goal: number
}
};
musicList: {
type_0: number[];
type_1: number[];
type_2: number[];
type_3: number[];
};
musicList2: {
type_0: number[];
type_1: number[];
type_2: number[];
type_3: number[];
};
}

View File

@ -0,0 +1,30 @@
export interface Scores {
collection: 'scores',
recitals: {
[key: string]: {
count: number;
hall: number;
cat: number[];
audience: number;
money: number;
fame: number;
player: number;
score: number;
start: string;
end: string;
};
};
scores: {
[key: string]: {
score: number;
grade: number;
recital: number;
count: number;
clear: number;
multi: number;
mode: number;
flag: number;
};
};
}

View File

@ -0,0 +1,47 @@
type NostalgiaVersions = 'First' | 'Forte' | 'Op2' | 'Op3'
type NostalgiaNumericTypes = 'music_index' | 'sheet_type' | 'brooch_index' | 'event_index'
export class NosVersionHelper {
public version: NostalgiaVersions
private table = { // FIXME: All of Op3 values are placeholder
music_index: { First: 87, Forte: 195, Op2: 315, Op3: 500 },
brooch_index: { First: 120, Forte: 147, Op2: 148, Op3: 200 },
sheet_type: { First: 2, Forte: 2, Op2: 3, Op3: 3 },
event_index: { First: 10, Forte: 10, Op2: 17, Op3: 20 }
}
constructor (info: EamuseInfo) {
const version = parseInt(info.model.trim().substr(10), 10)
if (version >= 2020000000) {
this.version = 'Op3'
} else if (version >= 2019000000) {
this.version = 'Op2'
} else if (version >= 2018000000) {
this.version = 'Forte'
} else {
this.version = 'First'
}
}
getMusicMaxIndex() {
return this.table['music_index'][this.version]
}
getBroochMaxIndex() {
return this.table['brooch_index'][this.version]
}
getEventMaxIndex() {
return this.table['event_index'][this.version]
}
numericHandler(type: NostalgiaNumericTypes, input: number, def: number = 0) {
return input > this.table[type][this.version] ? def : input;
}
isFirstOrForte() {
return this.version === 'First' || this.version === 'Forte'
}
}

View File

@ -0,0 +1,26 @@
div
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| Fix Error with Login
.card-content
p If you unable to login after travels of between versions, This may helpful.
p Normally, login issue caused you played deleted song last time in previous version that not deleted time.
p This page is about reset of last indexes that causing login problem.
form(method="post" action="/emit/nosFixIndexBug")
.field
label.label ID
.control
input.input(type="text" name="refid", value=refid readonly)
.field
label.label Are you sure to reset indexes?
.control
input(type="checkbox" name="confirm")
| This is not recoverable. Do this if you have a problem.
.field
button.button.is-primary(type="submit")
span.icon
i.mdi.mdi-check
span Submit

View File

@ -1,9 +0,0 @@
{
"name": "asphyxia-plugins",
"version": "1.0.0",
"description": "Community Plugins for Asphyxia CORE",
"dependencies": {
"@types/lodash": "^4.14.153",
"@types/node": "^14.0.5"
}
}

View File

@ -0,0 +1,7 @@
Hello! Pop'n Music
==================
This plugin supports _Hello! Pop'n Music_.
# ! IMPORTANT !
You may need proper data to connect a network. Otherwise, it won't work!

View File

@ -0,0 +1,161 @@
import { Profile } from '../models/profile';
import { Scores } from '../models/scores';
export const newProfile: EPR = async (req, data, send) => {
const refid = $(data).attr().refid;
if (!refid) return send.deny();
await writeProfile(refid, await readProfile(refid));
send.success();
};
export const load: EPR = async (req, data, send) => {
try{
const refid = $(data).attr().refid;
if (!refid) return send.deny();
const profile = await readProfile(refid);
const chara: any[] = [];
if (profile.unlockAll) {
for (let i = 0; i < 11; ++i) {
chara.push(K.ATTR({ id: `${i}`, love: '5' }));
}
} else {
for (const i in profile.charas) {
chara.push(K.ATTR({ id: `${i}`, love: `${profile.charas[i]}` }));
}
}
send.object({
last: K.ATTR({
chara: `${profile.history.chara}`,
level: `${profile.history.level}`,
music_id: `${profile.history.mid}`,
style: `${profile.history.style}`,
}),
chara,
// threshold: {
// '@attr': { id: 0, point: 100 },
// },
})
} catch (e) { console.log(e.stack || e.stacktrace )}
};
export const load_m: EPR = async (req, data, send) => {
const refid = $(data).attr().refid;
if (!refid) return send.deny();
const scores = (await readScores(refid)).scores;
const music: any[] = [];
for (const mid in scores) {
const style: any[] = [];
for (const sid in scores[mid]) {
const level: any[] = [];
for (const lid in scores[mid][sid]) {
level.push(K.ATTR({
id: lid,
score: `${scores[mid][sid][lid].score}`,
clear_type: `${scores[mid][sid][lid].clear}`,
}),
);
}
style.push(K.ATTR({ id: sid }, { level }));
}
music.push(K.ATTR({ music_id: mid }, { style }));
}
send.object({
music,
});
};
export const save_m: EPR = async (req, data, send) => {
const refid = $(data).attr().refid;
if (!refid) return send.deny();
const scores = (await readScores(refid)).scores;
const clear = parseInt($(data).attr().clear_type || '-1');
const level = parseInt($(data).attr().level || '-1');
const mid = parseInt($(data).attr().music_id || '-1');
const score = parseInt($(data).attr().score || '-1');
const style = parseInt($(data).attr().style || '-1');
if (clear < 0 || level < 0 || mid < 0 || score < 0 || style < 0) {
return send.success();
}
if (!scores[mid]) {
scores[mid] = {};
}
if (!scores[mid][style]) {
scores[mid][style] = {};
}
scores[mid][style][level] = {
score: Math.max(_.get(scores[mid][style][level], 'score', 0), score),
clear: Math.max(_.get(scores[mid][style][level], 'clear', 0), clear),
};
writeScores(refid, { collection: 'scores', scores });
send.success();
};
export const save: EPR = async (req, data, send) => {
const refid = $(data).attr().refid;
if (!refid) return send.deny();
const profile = await readProfile(refid);
const chara = parseInt($(data).attr('last').chara || '-1');
const level = parseInt($(data).attr('last').level || '-1');
const love = parseInt($(data).attr('last').love || '-1');
const mid = parseInt($(data).attr('last').music_id || '-1');
const style = parseInt($(data).attr('last').style || '-1');
if (chara < 0 || level < 0 || mid < 0 || love < 0 || style < 0) {
return send.success();
}
if (!profile.unlockAll) {
profile.charas[chara] = _.get(profile.charas, chara, 0) + love;
}
profile.history = {
chara,
mid,
level,
style,
};
await writeProfile(refid, profile);
send.success();
};
async function readProfile(refid: string): Promise<Profile> {
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile'} )
return profile || {
collection: 'profile',
unlockAll: false,
history: { chara: 0, level: 0, mid: 0, style: 0 },
charas: {},
}
}
async function writeProfile(refid: string, profile: Profile) {
await DB.Upsert<Profile>(refid, { collection: 'profile'}, profile)
}
async function readScores(refid: string): Promise<Scores> {
const score = await DB.FindOne<Scores>(refid, { collection: 'scores'} )
return score || { collection: 'scores', scores: {}}
}
async function writeScores(refid: string, scores: Scores) {
await DB.Upsert<Scores>(refid, { collection: 'scores'}, scores)
}

View File

@ -0,0 +1,15 @@
import { Profile } from "../models/profile";
export const setUnlockState = async (data: {
refid: string;
unlock_all: string;
}) => {
await DB.Update<Profile>(
data.refid,
{ collection: 'profile' },
{ $set: {
unlockAll: data.unlock_all == "on"
}
}
);
};

View File

@ -0,0 +1,39 @@
import { load, load_m, newProfile, save, save_m } from "./handler/player";
import { setUnlockState } from "./handler/webui";
const common: EPR = async (req, data, send) => {
send.object(
{
flag: K.ATTR({ id: '1', s1: '1', s2: '1', t: '1' }),
cnt_music: K.ITEM('u32', 36),
},
{ encoding: 'euc-jp' }
);
};
const shop: EPR = async (req, data, send) => {
send.success()
}
export function register() {
R.GameCode("JMP")
R.WebUIEvent("setUnlockState", setUnlockState)
// const Route = (method: string, handler: EPR) =>
// R.Route(`game.${method}`, handler)
// Common
R.Route("game.common", common)
R.Route("game.shop", shop)
// Player
R.Route("game.new", newProfile)
R.Route("game.load", load)
R.Route("game.load_m", load_m)
R.Route("game.save", save)
R.Route("game.save_m", save_m)
R.Unhandled()
}

View File

@ -0,0 +1,12 @@
export interface Profile {
collection: 'profile',
unlockAll: boolean;
history: {
chara: number;
level: number;
mid: number;
style: number;
};
charas: { [id: string]: number };
}

View File

@ -0,0 +1,14 @@
export interface Scores {
collection: 'scores',
scores: {
[mid: string]: {
[style: string]: {
[level: string]: {
score: number;
clear: number;
};
};
}
};
}

View File

@ -0,0 +1,26 @@
//DATA//
profile: DB.FindOne(refid, { collection: 'profile' })
div
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| Detail Change
.card-content
form(method="post" action="/emit/setUnlockState")
.field
label.label ID
.control
input.input(type="text" name="refid", value=refid readonly)
.field
label.label Unlock All
.control
input(type="checkbox" name="unlock_all", checked=profile.unlockAll)
| Turn this on to max out all characters and unlock 11 hidden songs.
.field
button.button.is-primary(type="submit")
span.icon
i.mdi.mdi-check
span Submit

23
popn@asphyxia/README.md Normal file
View File

@ -0,0 +1,23 @@
# Pop'n Music
Plugin Version: **v1.1.0**
## Supported Versions
- pop'n music 23 Eclale
- pop'n music 24 Usagi to Neko to Shōnen no Yume
- pop'n music 25 peace
## Changelog
#### 1.1.0
Update phase data : All versions are on latest phase.
#### 1.0.0
Initial Release.
## How to import data from non-core Asphyxia
To import data, you have to :
* Create your popn profile in Asphyxia-core. You just have to insert your card in the game and follow the process until coming to the select mode select screen. Once here, quit the game.
* Create a backup of your savedata.db file (in case something goes wrong).
* In the web UI of Asphyxia, go to POPN -> Profile and click detail on your profile
* Put the content of your non-core asphyxia popn music files in the text fields (pop.json and popn_scores.json) and click Import.
* Data is imported. Run the game, insert your card and your scores are available.

View File

@ -0,0 +1,322 @@
import { getVersion } from './utils';
interface Phase {
id: number;
p: number;
}
const PHASE23 : Phase[] = [
{ id: 0, p: 16 },
{ id: 1, p: 3 },
{ id: 2, p: 1 },
{ id: 3, p: 2 },
{ id: 4, p: 1 },
{ id: 5, p: 2 },
{ id: 6, p: 1 },
{ id: 7, p: 4 },
{ id: 8, p: 3 },
{ id: 9, p: 4 },
{ id: 10, p: 4 },
{ id: 11, p: 1 },
{ id: 12, p: 1 },
{ id: 13, p: 4 },
];
const PHASE24 : Phase[] = [
{ id: 0, p: 11 },
{ id: 1, p: 2 },
{ id: 2, p: 2 },
{ id: 3, p: 4 },
{ id: 4, p: 1 },
{ id: 5, p: 1 },
{ id: 6, p: 1 },
{ id: 7, p: 1 },
{ id: 8, p: 2 },
{ id: 9, p: 2 },
{ id: 10, p: 15 },
{ id: 11, p: 1 },
{ id: 12, p: 2 },
{ id: 13, p: 1 },
];
const PHASE25 : Phase[] = [
{ id: 0, p: 23 },
{ id: 1, p: 4 },
{ id: 2, p: 2 },
{ id: 3, p: 4 },
{ id: 4, p: 1 },
{ id: 5, p: 1 },
{ id: 6, p: 1 },
{ id: 7, p: 1 },
{ id: 8, p: 2 },
{ id: 9, p: 2 },
{ id: 10, p: 30 },
{ id: 11, p: 1 },
{ id: 12, p: 2 },
{ id: 13, p: 1 },
{ id: 14, p: 39 },
{ id: 15, p: 2 },
{ id: 16, p: 3 },
{ id: 17, p: 8 },
{ id: 18, p: 1 },
{ id: 19, p: 1 },
{ id: 20, p: 13 },
{ id: 21, p: 20 },
{ id: 22, p: 2 },
{ id: 23, p: 1 },
{ id: 24, p: 1 },
];
export const getInfo = (req: EamuseInfo) => {
const version = getVersion(req);
if (version == 'v23') {
return getInfo23();
} else if (version == 'v24') {
if(req.model == 'M39:J:A:A:2020092800') {
return getInfo24(PHASE25);
} else {
return getInfo24(PHASE24);
}
}
}
const getInfo23 = () => {
const result: any = {
phase: [],
area: [],
goods: [],
};
for (const phase of PHASE23) {
result.phase.push({
event_id: K.ITEM('s16', phase.id),
phase: K.ITEM('s16', phase.p),
});
}
for (let i = 1; i <= 100; ++i) {
result.area.push({
area_id: K.ITEM('s16', i),
end_date: K.ITEM('u64', BigInt(0)),
medal_id: K.ITEM('s16', i),
is_limit: K.ITEM('bool', 0),
});
}
for (let i = 1; i <= 420; ++i) {
result.goods.push({
goods_id: K.ITEM('s16', i),
price: K.ITEM('s32', i <= 80 ? 60 : 100),
goods_type: K.ITEM('s16', 0),
});
}
return result;
};
const getInfo24 = (phaseData : Phase[]) => {
const result: any = {
phase: [],
goods: [],
};
for (const phase of phaseData) {
result.phase.push({
event_id: K.ITEM('s16', phase.id),
phase: K.ITEM('s16', phase.p),
});
}
for (let i = 3; i <= 96; ++i) {
result.goods.push({
item_id: K.ITEM('s32', i),
item_type: K.ITEM('s16', 3),
price: K.ITEM('s32', 60),
goods_type: K.ITEM('s16', 0),
});
}
return result;
};
export const M39_EXTRA_DATA: {
[ver: string]: {
[field: string]: {
path: string;
type: string;
default: any;
isArray?: true;
};
};
} = {
v23: {
tutorial: { type: 's8', path: 'account', default: 0 },
area_id: { type: 's16', path: 'account', default: 0 },
lumina: { type: 's16', path: 'account', default: 300 },
medal_set: { type: 's16', path: 'account', default: [0, 0, 0, 0], isArray: true },
read_news: { type: 's16', path: 'account', default: 0 },
staff: { type: 's8', path: 'account', default: 0 },
item_type: { type: 's16', path: 'account', default: 0 },
item_id: { type: 's16', path: 'account', default: 0 },
is_conv: { type: 's8', path: 'account', default: 0 },
active_fr_num: { type: 'u8', path: 'account', default: 0 },
nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true },
favorite_chara: { type: 's16', path: 'account', default: Array(20).fill(-1), isArray: true },
special_area: { type: 's16', path: 'account', default: Array(8).fill(0), isArray: true },
chocolate_charalist: {
type: 's16',
path: 'account',
default: Array(5).fill(-1),
isArray: true,
},
teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(0), isArray: true },
license_data: { type: 's16', path: 'account', default: [-1, -1], isArray: true },
welcom_pack: { type: 'bool', path: 'account', default: 1 },
meteor_flg: { type: 'bool', path: 'account', default: 1 },
hispeed: { type: 's16', path: 'option', default: 0 },
popkun: { type: 'u8', path: 'option', default: 0 },
hidden: { type: 'bool', path: 'option', default: 0 },
hidden_rate: { type: 's16', path: 'option', default: 0 },
sudden: { type: 'bool', path: 'option', default: 0 },
sudden_rate: { type: 's16', path: 'option', default: 0 },
randmir: { type: 's8', path: 'option', default: 0 },
gauge_type: { type: 's8', path: 'option', default: 0 },
ojama_0: { type: 'u8', path: 'option', default: 0 },
ojama_1: { type: 'u8', path: 'option', default: 0 },
forever_0: { type: 'bool', path: 'option', default: 0 },
forever_1: { type: 'bool', path: 'option', default: 0 },
full_setting: { type: 'bool', path: 'option', default: 0 },
judge: { type: 'u8', path: 'option', default: 0 },
ep: { type: 'u16', path: 'info', default: 0 },
effect_left: { type: 'u16', path: 'customize', default: 0 },
effect_center: { type: 'u16', path: 'customize', default: 0 },
effect_right: { type: 'u16', path: 'customize', default: 0 },
hukidashi: { type: 'u16', path: 'customize', default: 0 },
comment_1: { type: 'u16', path: 'customize', default: 0 },
comment_2: { type: 'u16', path: 'customize', default: 0 },
mode: { type: 'u8', path: 'config', default: 0 },
chara: { type: 's16', path: 'config', default: -1 },
music: { type: 's16', path: 'config', default: -1 },
sheet: { type: 'u8', path: 'config', default: 0 },
category: { type: 's8', path: 'config', default: -1 },
sub_category: { type: 's8', path: 'config', default: -1 },
chara_category: { type: 's8', path: 'config', default: -1 },
course_id: { type: 's16', path: 'config', default: 0 },
course_folder: { type: 's8', path: 'config', default: 0 },
ms_banner_disp: { type: 's8', path: 'config', default: 0 },
ms_down_info: { type: 's8', path: 'config', default: 0 },
ms_side_info: { type: 's8', path: 'config', default: 0 },
ms_raise_type: { type: 's8', path: 'config', default: 0 },
ms_rnd_type: { type: 's8', path: 'config', default: 0 },
enemy_medal: { type: 's16', path: 'event', default: 0 },
hp: { type: 's16', path: 'event', default: 0 },
valid: { type: 's8', path: 'custom_cate', default: 0 },
lv_min: { type: 's8', path: 'custom_cate', default: -1 },
lv_max: { type: 's8', path: 'custom_cate', default: -1 },
medal_min: { type: 's8', path: 'custom_cate', default: -1 },
medal_max: { type: 's8', path: 'custom_cate', default: -1 },
friend_no: { type: 's8', path: 'custom_cate', default: -1 },
score_flg: { type: 's8', path: 'custom_cate', default: -1 },
},v24: {
enemy_medal: { type: 's16', path: 'event', default: 0 },
hp: { type: 's16', path: 'event', default: 0 },
tutorial: { type: 's16', path: 'account', default: -1 },
area_id: { type: 's16', path: 'account', default: 51 },
lumina: { type: 's16', path: 'account', default: 0 },
medal_set: { type: 's16', path: 'account', default: [0, 0], isArray: true },
read_news: { type: 's16', path: 'account', default: 0 },
staff: { type: 's8', path: 'account', default: 0 },
is_conv: { type: 's8', path: 'account', default: 0 },
item_type: { type: 's16', path: 'account', default: 0 },
item_id: { type: 's16', path: 'account', default: 0 },
license_data: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true },
active_fr_num: { type: 'u8', path: 'account', default: 0 },
nice: { type: 's16', path: 'account', default: Array(30).fill(-1), isArray: true },
favorite_chara: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true },
special_area: { type: 's16', path: 'account', default: Array(8).fill(-1), isArray: true },
chocolate_charalist: {
type: 's16',
path: 'account',
default: Array(5).fill(-1),
isArray: true,
},
chocolate_sp_chara: { type: 's32', path: 'account', default: 0 },
chocolate_pass_cnt: { type: 's32', path: 'account', default: 0 },
chocolate_hon_cnt: { type: 's32', path: 'account', default: 0 },
chocolate_giri_cnt: { type: 's32', path: 'account', default: 0 },
chocolate_kokyu_cnt: { type: 's32', path: 'account', default: 0 },
teacher_setting: { type: 's16', path: 'account', default: Array(10).fill(-1), isArray: true },
welcom_pack: { type: 'bool', path: 'account', default: 0 },
meteor_flg: { type: 'bool', path: 'account', default: 0 },
use_navi: { type: 's16', path: 'account', default: 0 },
ranking_node: { type: 's32', path: 'account', default: 0 },
chara_ranking_kind_id: { type: 's32', path: 'account', default: 0 },
navi_evolution_flg: { type: 's8', path: 'account', default: 0 },
ranking_news_last_no: { type: 's32', path: 'account', default: 0 },
power_point: { type: 's32', path: 'account', default: 0 },
player_point: { type: 's32', path: 'account', default: 0 },
power_point_list: { type: 's32', path: 'account', default: [0], isArray: true },
mode: { type: 'u8', path: 'config', default: 0 },
chara: { type: 's16', path: 'config', default: 0 },
music: { type: 's16', path: 'config', default: 0 },
sheet: { type: 'u8', path: 'config', default: 0 },
category: { type: 's8', path: 'config', default: 0 },
sub_category: { type: 's8', path: 'config', default: 0 },
chara_category: { type: 's8', path: 'config', default: 0 }, // check
story_id: { type: 's16', path: 'config', default: 0 },
ms_banner_disp: { type: 's8', path: 'config', default: 0 },
ms_down_info: { type: 's8', path: 'config', default: 0 },
ms_side_info: { type: 's8', path: 'config', default: 0 },
ms_raise_type: { type: 's8', path: 'config', default: 0 },
ms_rnd_type: { type: 's8', path: 'config', default: 0 },
banner_sort: { type: 's8', path: 'config', default: 0 },
course_id: { type: 's16', path: 'config', default: 0 },
course_folder: { type: 's8', path: 'config', default: 0 },
story_folder: { type: 's8', path: 'config', default: 0 },
hispeed: { type: 's16', path: 'option', default: 10 },
popkun: { type: 'u8', path: 'option', default: 0 },
hidden: { type: 'bool', path: 'option', default: 0 },
hidden_rate: { type: 's16', path: 'option', default: -1 },
sudden: { type: 'bool', path: 'option', default: 0 },
sudden_rate: { type: 's16', path: 'option', default: -1 },
randmir: { type: 's8', path: 'option', default: 0 },
gauge_type: { type: 's8', path: 'option', default: 0 },
ojama_0: { type: 'u8', path: 'option', default: 0 },
ojama_1: { type: 'u8', path: 'option', default: 0 },
forever_0: { type: 'bool', path: 'option', default: 0 },
forever_1: { type: 'bool', path: 'option', default: 0 },
full_setting: { type: 'bool', path: 'option', default: 0 },
guide_se: { type: 's8', path: 'option', default: 0 },
judge: { type: 'u8', path: 'option', default: 0 },
slow: { type: 's16', path: 'option', default: 0 },
fast: { type: 's16', path: 'option', default: 0 },
valid: { type: 's8', path: 'custom_cate', default: 0 },
lv_min: { type: 's8', path: 'custom_cate', default: 0 },
lv_max: { type: 's8', path: 'custom_cate', default: 0 },
medal_min: { type: 's8', path: 'custom_cate', default: 0 },
medal_max: { type: 's8', path: 'custom_cate', default: 0 },
friend_no: { type: 's8', path: 'custom_cate', default: 0 },
score_flg: { type: 's8', path: 'custom_cate', default: 0 },
ep: { type: 'u16', path: 'info', default: 0 },
ap: { type: 'u16', path: 'info', default: 0 },
effect_left: { type: 'u16', path: 'customize', default: 0 },
effect_center: { type: 'u16', path: 'customize', default: 0 },
effect_right: { type: 'u16', path: 'customize', default: 0 },
hukidashi: { type: 'u16', path: 'customize', default: 0 },
comment_1: { type: 'u16', path: 'customize', default: 0 },
comment_2: { type: 'u16', path: 'customize', default: 0 },
},
};

View File

@ -0,0 +1,519 @@
import { Profile } from '../models/profile';
import { Scores } from '../models/scores';
import { getInfo, M39_EXTRA_DATA } from './common';
import { getVersion } from './utils';
const getPlayer = async (refid: string, version: string, name?: string) => {
const profile = await readProfile(refid);
if (name && name.length > 0) {
profile.name = name;
await writeProfile(refid, profile);
}
let player: any = {
result: K.ITEM('s8', 0),
account: {
name: K.ITEM('str', profile.name),
g_pm_id: K.ITEM('str', 'ASPHYXIAPLAY'),
// Fixed stats
total_play_cnt: K.ITEM('s16', 100),
today_play_cnt: K.ITEM('s16', 50),
consecutive_days: K.ITEM('s16', 365),
total_days: K.ITEM('s16', 366),
interval_day: K.ITEM('s16', 1),
// TODO: do these
my_best: K.ARRAY('s16', Array(10).fill(-1)),
latest_music: K.ARRAY('s16', [-1, -1, -1, -1, -1]),
},
netvs: {
record: K.ARRAY('s16', [0, 0, 0, 0, 0, 0]),
dialog: [
K.ITEM('str', 'dialog#0'),
K.ITEM('str', 'dialog#1'),
K.ITEM('str', 'dialog#2'),
K.ITEM('str', 'dialog#3'),
K.ITEM('str', 'dialog#4'),
K.ITEM('str', 'dialog#5'),
],
ojama_condition: K.ARRAY('s8', Array(74).fill(0)),
set_ojama: K.ARRAY('s8', [0, 0, 0]),
set_recommend: K.ARRAY('s8', [0, 0, 0]),
netvs_play_cnt: K.ITEM('u32', 0),
},
eaappli: {
relation: K.ITEM('s8', 0),
},
stamp: [],
item: [],
chara_param: [],
medal: [],
};
const profileStamps = profile.stamps[version] || { '0': 0 };
for (const stamp in profileStamps) {
player.stamp.push({
stamp_id: K.ITEM('s16', parseInt(stamp, 10)),
cnt: K.ITEM('s16', profileStamps[stamp]),
});
}
const profileCharas = profile.charas[version] || {};
for (const chara_id in profileCharas) {
player.chara_param.push({
chara_id: K.ITEM('u16', parseInt(chara_id, 10)),
friendship: K.ITEM('u16', profileCharas[chara_id]),
});
}
const profileMedals = profile.medals[version] || {};
for (const medal_id in profileMedals) {
const medal = profileMedals[medal_id];
player.medal.push({
medal_id: K.ITEM('s16', parseInt(medal_id, 10)),
level: K.ITEM('s16', medal.level),
exp: K.ITEM('s32', medal.exp),
set_count: K.ITEM('s32', medal.set_count),
get_count: K.ITEM('s32', medal.get_count),
});
}
const profileItems = profile.items[version] || {};
for (const key in profileItems) {
const keyData = key.split(':');
const type = parseInt(keyData[0], 10);
const id = parseInt(keyData[1], 10);
const item: any = {
type: K.ITEM('u8', type),
id: K.ITEM('u16', id),
param: K.ITEM('u16', profileItems[key]),
is_new: K.ITEM('bool', 0),
};
if (version != 'v23') {
item.get_time = K.ITEM('u64', BigInt(0));
}
player.item.push(item);
}
// EXTRA DATA
if (M39_EXTRA_DATA[version]) {
for (const field in M39_EXTRA_DATA[version]) {
const fieldMetaData = M39_EXTRA_DATA[version][field];
if (fieldMetaData.isArray) {
_.set(
player,
`${fieldMetaData.path}.${field}`,
K.ARRAY(
fieldMetaData.type as any,
_.get(profile, `extras.${version}.${field}`, fieldMetaData.default)
)
);
} else {
_.set(
player,
`${fieldMetaData.path}.${field}`,
K.ITEM(
fieldMetaData.type as any,
_.get(profile, `extras.${version}.${field}`, fieldMetaData.default)
)
);
}
}
}
// Extra Fixed Data
if (version == 'v24') {
player = {
...player,
navi_data: {
raisePoint: K.ARRAY('s32', [-1, -1, -1, -1, -1]),
navi_param: {
navi_id: K.ITEM('u16', 0),
friendship: K.ITEM('s32', 0),
},
},
area: {
area_id: K.ITEM('u32', 0),
chapter_index: K.ITEM('u8', 0),
gauge_point: K.ITEM('u16', 0),
is_cleared: K.ITEM('bool', 0),
diary: K.ITEM('u32', 0),
},
mission: [
{
mission_id: K.ITEM('u32', 170),
gauge_point: K.ITEM('u32', 0),
mission_comp: K.ITEM('u32', 0),
},
{
mission_id: K.ITEM('u32', 157),
gauge_point: K.ITEM('u32', 0),
mission_comp: K.ITEM('u32', 0),
},
{
mission_id: K.ITEM('u32', 47),
gauge_point: K.ITEM('u32', 0),
mission_comp: K.ITEM('u32', 0),
},
],
};
}
return player;
};
export const newPlayer: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const version = getVersion(req);
const name = $(data).str('name');
send.object(await getPlayer(refid, version, name));
};
export const read: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const version = getVersion(req);
send.object(await getPlayer(refid, version));
};
export const readScore: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const scoresData = await readScores(refid);
const version = getVersion(req);
const result: any = {
music: [],
};
if(version == 'v24') {
for (const key in scoresData.scores) {
const keyData = key.split(':');
const score = scoresData.scores[key];
const music = parseInt(keyData[0], 10);
const sheet = parseInt(keyData[1], 10);
result.music.push({
music_num: K.ITEM('s16', music),
sheet_num: K.ITEM('u8', sheet),
score: K.ITEM('s32', score.score),
clear_type: K.ITEM('u8', score.clear_type || 0),
clear_rank: K.ITEM('u8', score.clear_rank || 0),
cnt: K.ITEM('s16', score.cnt),
});
}
} else if(version == 'v23') {
for (const key in scoresData.scores) {
const keyData = key.split(':');
const score = scoresData.scores[key];
const music = parseInt(keyData[0], 10);
const sheet = parseInt(keyData[1], 10);
result.music.push({
music_num: K.ITEM('s16', music),
sheet_num: K.ITEM('u8', sheet),
score: K.ITEM('s32', score.score),
clear_type: K.ITEM('u8', score.clearmedal || 0),
cnt: K.ITEM('s16', score.cnt),
old_score: K.ITEM('s32', 0),
old_clear_type: K.ITEM('u8', 0),
});
}
}
send.object(result);
};
export const writeMusic: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const music = $(data).number('music_num', -1);
const sheet = $(data).number('sheet_num', -1);
const clear_type = $(data).number('clear_type');
const clear_rank = $(data).number('clear_rank');
const clearmedal = $(data).number('clearmedal');
const score = $(data).number('score', 0);
if (music < 0 || sheet < 0) {
return send.deny();
}
const key = `${music}:${sheet}`;
const scoresData = await readScores(refid);
if (!scoresData.scores[key]) {
scoresData.scores[key] = {
score,
cnt: 1,
};
} else {
scoresData.scores[key] = {
score: Math.max(score, scoresData.scores[key].score),
cnt: scoresData.scores[key].cnt + 1,
};
}
if (clear_type) {
scoresData.scores[key].clear_type = Math.max(clear_type, scoresData.scores[key].clear_type || 0);
}
if (clear_rank) {
scoresData.scores[key].clear_rank = Math.max(clear_rank, scoresData.scores[key].clear_rank || 0);
}
if (clearmedal) {
scoresData.scores[key].clearmedal = Math.max(clearmedal, scoresData.scores[key].clearmedal || 0);
}
writeScores(refid, scoresData);
const version = getVersion(req);
if (version == 'v24') {
const p = await readProfile(refid);
const settings = [
'hispeed',
'popkun',
'hidden',
'hidden_rate',
'sudden',
'sudden_rate',
'randmir',
'ojama_0',
'ojama_1',
'forever_0',
'forever_1',
'full_setting',
'guide_se',
'judge',
'slow',
'fast',
'mode',
];
for (const setting of settings) {
_.set(
p,
`extras.v24.${setting}`,
_.get(data, `${setting}.@content.0`, _.get(p, `extras.v24.${setting}`, 0))
);
}
_.set(p, `extras.v24.tutorial`, 32767);
const chara = $(data).number('chara_num');
if (chara) {
_.set(p, 'extras.v24.chara', chara);
}
const music = $(data).number('music_num');
if (music) {
_.set(p, 'extras.v24.music', music);
}
const sheet = $(data).number('sheet_num');
if (sheet) {
_.set(p, 'extras.v24.sheet', sheet);
}
writeProfile(refid, p);
}
send.success();
};
export const write: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const version = getVersion(req);
const profile = await readProfile(refid);
const writeData: Partial<Profile> = {};
if (M39_EXTRA_DATA[version]) {
const extraFields = M39_EXTRA_DATA[version];
for (const field in extraFields) {
const fieldMetaData = extraFields[field];
let value = _.get(data, `${fieldMetaData.path}.${field}.@content`);
if ( value == 'undefined' && value == null ) {
continue;
}
if (_.isArray(value) && value.length == 1) {
value = value[0];
}
_.set(writeData, `extras.${version}.${field}`, value);
}
}
const newProfile:Profile = {
...profile,
...writeData,
};
// stamps
let stamps = _.get(data, 'stamp', []);
if (!newProfile.stamps[version]) {
newProfile.stamps[version] = { '0': 0 };
}
if (!_.isArray(stamps)) {
stamps = [stamps];
}
for (const stamp of stamps) {
const id = $(stamp).number('stamp_id');
const cnt = $(stamp).number('cnt');
newProfile.stamps[version][id] = cnt;
}
// medals
let medals = _.get(data, 'medal', []);
if (!newProfile.medals[version]) {
newProfile.medals[version] = {};
}
if (!_.isArray(medals)) {
medals = [medals];
}
for (const medal of medals) {
const id = $(medal).number('medal_id');
const level = $(medal).number('level');
const exp = $(medal).number('exp');
const set_count = $(medal).number('set_count');
const get_count = $(medal).number('get_count');
newProfile.medals[version][id] = {
level,
exp,
set_count,
get_count,
};
}
// items
let items = _.get(data, 'item', []);
if (!newProfile.items[version]) {
newProfile.items[version] = {};
}
if (!_.isArray(items)) {
items = [items];
}
for (const item of items) {
const type = $(item).number('type');
const id = $(item).number('id');
const param = $(item).number('param');
const key = `${type}:${id}`;
newProfile.items[version][key] = param;
}
// charas
let charas = _.get(data, 'chara_param', []);
if (!newProfile.charas[version]) {
newProfile.charas[version] = {};
}
if (!_.isArray(charas)) {
charas = [charas];
}
for (const chara of charas) {
const id = $(chara).number('chara_id');
const param = $(chara).number('friendship');
newProfile.charas[version][id] = param;
}
await writeProfile(refid, newProfile);
send.success();
};
export const start: EPR = async (req, data, send) => {
const result = {
play_id: K.ITEM('s32', 1),
...getInfo(req),
};
await send.object(result);
};
export const buy: EPR = async (req, data, send) => {
const refid = $(data).str('ref_id');
if (!refid) return send.deny();
const type = $(data).number('type', -1);
const id = $(data).number('id', -1);
const param = $(data).number('param', 0);
const version = getVersion(req);
if (type < 0 || id < 0) {
return send.deny();
}
const key = `${type}:${id}`;
const profile = await readProfile(refid);
if (!profile.items[version]) {
profile.items[version] = {};
}
profile.items[version][key] = param;
await writeProfile(refid, profile);
send.success();
};
const defaultProfile:Profile = {
collection: 'profile',
name: 'ゲスト',
stamps: {},
medals: {},
items: {},
charas: {},
extras: {},
};
async function readProfile(refid: string): Promise<Profile> {
const profile = await DB.FindOne<Profile>(refid, { collection: 'profile'} )
return profile || defaultProfile
}
async function writeProfile(refid: string, profile: Profile) {
await DB.Upsert<Profile>(refid, { collection: 'profile'}, profile)
}
async function readScores(refid: string): Promise<Scores> {
const score = await DB.FindOne<Scores>(refid, { collection: 'scores'} )
return score || { collection: 'scores', scores: {}}
}
async function writeScores(refid: string, scores: Scores) {
await DB.Upsert<Scores>(refid, { collection: 'scores'}, scores)
}

View File

@ -0,0 +1,4 @@
export const getVersion = (info: EamuseInfo) => {
const moduleName: string = info.module;
return `v${moduleName.match(/[0-9]+/)[0]}`;
};

View File

@ -0,0 +1,33 @@
import { Profile } from "../models/profile";
import { Scores } from "../models/scores";
export const importPnmData = async (data: {
refid: string;
profile: string;
scores: string;
}) => {
const profile = JSON.parse(data.profile);
const scores = JSON.parse(data.scores);
await DB.Update<Profile>(
data.refid,
{ collection: 'profile' },
{
$set: {
...profile
}
}
);
await DB.Upsert<Scores>(
data.refid,
{ collection: 'scores' },
{
$set: {
scores: {
...scores
}
}
}
);
};

33
popn@asphyxia/index.ts Normal file
View File

@ -0,0 +1,33 @@
import { getInfo } from "./handler/common";
import { newPlayer, read, readScore, start, writeMusic, write, buy } from "./handler/player";
import { importPnmData } from "./handler/webui";
export function register() {
R.GameCode('M39');
R.WebUIEvent('importPnmData', importPnmData);
const PlayerRoute = (method: string, handler: EPR | boolean) => {
R.Route(`player24.${method}`, handler);
R.Route(`player23.${method}`, handler);
};
const CommonRoute = (method: string, handler: EPR | boolean) => {
R.Route(`info24.${method}`, handler);
R.Route(`info23.${method}`, handler);
};
// Common
CommonRoute('common', (req, data, send) => {
return send.object(getInfo(req));
});
// Player
PlayerRoute('new', newPlayer);
PlayerRoute('read', read);
PlayerRoute('read_score', readScore);
PlayerRoute('write_music', writeMusic);
PlayerRoute('write', write);
PlayerRoute('start', start);
PlayerRoute('buy', buy);
}

View File

@ -0,0 +1,40 @@
export interface Profile {
collection: 'profile',
name: string;
stamps: {
[ver: string]: {
[stamp_id: string]: number;
};
};
medals: {
[ver: string]: {
[id: string]: {
level: number;
exp: number;
set_count: number;
get_count: number;
};
};
};
items: {
[ver: string]: {
[key: string]: number;
};
};
charas: {
[ver: string]: {
[chara_id: string]: number;
};
};
extras: {
[ver: string]: {
[key: string]: any;
};
};
}

View File

@ -0,0 +1,13 @@
export interface Scores {
collection: 'scores',
scores: {
[key: string]: {
clearmedal?: number;
clear_type?: number;
clear_rank?: number;
score: number;
cnt: number;
};
};
}

View File

@ -0,0 +1,11 @@
$('#import-click').on('click', () => {
data = {
refid: $('#refid').val(),
profile: $('#profile').val(),
scores: $('#scores').val()
}
emit('importPnmData', data).then(() => {
$('#import-success').removeClass("is-hidden");
});
});

View File

@ -0,0 +1,24 @@
div
div.notification.is-success.is-hidden#import-success
.field
label.label.is-small Data imported
.card
.card-header
p.card-header-title
span.icon
i.mdi.mdi-account-edit
| Import data
.card-content
input(type="hidden" id="refid" name="refid" value=refid)
.field
label.label Import data from previous Asphyxia (non-core)
.field
input.input(type="text" id="profile" name="profile" placeholder="Put content of popn.json")
.field
input.input(type="text" id="scores" name="scores" placeholder="Put content of popn_scores.json")
.field
label.help.is-danger /!\ Please backup your savedata.db before importing data /!\
.field
button.button.is-primary#import-click Import
script(src="static/js/profile_page.js")

View File

@ -1,6 +1,6 @@
# SOUND VOLTEX
Plugin Version: **v1.0**
Plugin Version: **v1.1**
Supported Versions:

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"noImplicitAny": false,
"moduleResolution": "node",
"types": ["./asphyxia-core"],
"sourceMap": false
}
}

View File

@ -1,9 +0,0 @@
{
"includeDeclarations": true,
"excludeExternals": true,
"externalPattern": ["!asphyxia-core.d.ts"],
"mode": "file",
"readme": "../docs/_typedoc.md",
"name": "Asphyxia CORE Module API",
"out": "../docs/typedoc"
}