Merge pull request #21 from DitFranXX/gitadora-nextage

Gitadora NEX+AGE support
This commit is contained in:
Freddie Wang 2021-05-25 20:23:17 +08:00 committed by GitHub
commit a8759b3a4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 334 additions and 234 deletions

View File

@ -1,15 +1,39 @@
GITADORA Plugin for Asphyxia-Core
=================================
This plugin is converted from public-exported Asphyxia's Routes.
![Version: v1.1.0](https://img.shields.io/badge/version-v1.1.0-blue)
This plugin is based on converted from public-exported Asphyxia's Routes.
Supported Versions
==================
- Matixx
- Exchain
- NEX+AGE
When Plugin Doesn't work correctly / Startup Error on Plugin
------------------------------------------------------------
The folder structure between v1.0 and v1.1 is quite different. Do not overwrite plugin folder.
<br>If encounter error, Please try these step:
1. Remove `gitadora@asphyxia` folder.
2. C-C and C-V the newest version of `gitadora@asphyxia`
3. (Custom MDB Users) Reupload MDB or move `data/custom_mdb.xml` to `data/mdb/custom.xml`
Known Issues
============
* Information dialog keep showing as plugin doesn't store item data currently.
* Special Premium Encore on Nextage
- Bandage solution is implemented. Try it.
Release Notes
=============
v1.0.0 (Current)
v1.1.0 (Current)
----------------
* NEX+AGE Support (Not full support.)
* Restructure bit for maintaining.
v1.0.0
------
* Initial release for public

View File

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

View File

@ -1,58 +0,0 @@
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

@ -1,58 +0,0 @@
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,72 @@
import { getVersion } from "../utils";
interface EncoreStageData {
level: number
musics: number[]
unlock_challenge?: number[]
}
export function getEncoreStageData(info: EamuseInfo): EncoreStageData {
const fallback = { level: 10, musics: [0] }
const level: number = U.GetConfig("encore_version")
const ntDummyEncore = U.GetConfig("nextage_dummy_encore")
switch (getVersion(info)) {
case 'nextage':
return {
level,
musics: !ntDummyEncore ? [
2587, // 悪魔のハニープリン
2531, // The ULTIMATES -reminiscence-
2612, // ECLIPSE 2
2622, // Slip Into My Royal Blood
2686, // CYCLONICxSTORM
// FIXME: Fix special encore.
305, 602, 703, 802, 902, 1003, 1201, 1400, 1712, 1916, 2289, 2631, // DD13 and encores.
1704, 1811, 2121, 2201, 2624, // Soranaki and encores.
1907, 2020, 2282, 2341, 2666 // Stargazer and encores.
] : [
2622, 305, 1704, 1907, 2686 // Dummy.
]
}
case 'exchain':
return {
level,
musics: [
2246, // 箱庭の世界
2498, // Cinnamon
2500, // キヤロラ衛星の軌跡
2529, // グリーンリーフ症候群
2548, // Let's Dance
2587, // 悪魔のハニープリン
5020, // Timepiece phase II (CLASSIC)
5033, // MODEL FT2 Miracle Version (CLASSIC)
2586, // 美麗的夏日風
5060, // EXCELSIOR DIVE (CLASSIC)
2530, // The ULTIMATES -CHRONICLE-
2581, // 幸せの代償
5046 // Rock to Infinity (CLASSIC)
]
}
case 'matixx':
return {
level,
musics: [
2432, // Durian
2445, // ヤオヨロズランズ
2456, // Fate of the Furious
2441, // PIRATES BANQUET
2444, // Aion
2381, // Duella Lyrica
2471, // triangulum
2476, // MODEL FT4
2486, // 煉獄事変
2496, // CAPTURING XANADU
2497, // Physical Decay
2499, // Cinnamon
2498 // けもののおうじゃ★めうめう
]
}
default:
return fallback
}
}

View File

@ -1,36 +0,0 @@
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
}
}

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

@ -0,0 +1,9 @@
ex.xml
mt.xml
nt.xml
hv.xml
ex.json
mt.json
nt.json
hv.json
custom.xml

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,126 @@
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 enum DATAVersion {
HIGHVOLTAGE = "hv",
NEXTAGE = "nt",
EXCHAIN = "ex",
MATTIX = "mt"
}
type processRawDataHandler = (path: string) => Promise<CommonMusicData>
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: processRawDataHandler): 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
}
}
export async function readB64JSON(b64path: string) {
const buff = await IO.ReadFile(b64path, 'utf-8');
return JSON.parse(Buffer.from(buff, 'base64').toString('utf-8'));
}
export function gameVerToDataVer(ver: string): DATAVersion {
switch(ver) {
case 'highvoltage':
return DATAVersion.HIGHVOLTAGE
case 'nextage':
return DATAVersion.NEXTAGE
case 'exchain':
return DATAVersion.EXCHAIN
case 'matixx':
default:
return DATAVersion.MATTIX
}
}
export async function processDataBuilder(gameVer: string, processHandler?: processRawDataHandler) {
const ver = gameVerToDataVer(gameVer)
const base = `data/mdb/${ver}`
if (IO.Exists(`${base}.b64`)) {
return await readB64JSON(`${base}.b64`);
}
const { music } = await readJSONOrXML(`${base}.json`, `${base}.xml`, processHandler ?? defaultProcessRawData)
// await IO.WriteFile(`${base}.b64`, Buffer.from(JSON.stringify({music})).toString("base64"))
return { music };
}
export async function defaultProcessRawData(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", 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],
]),
});
}
return {
music,
};
}

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

File diff suppressed because one or more lines are too long

View File

@ -1,57 +1,13 @@
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";
import { defaultProcessRawData, processDataBuilder } from "../data/mdb"
import { CommonMusicDataField, readJSONOrXML, readXML } from "../data/mdb";
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],
]),
});
}
music = (await defaultProcessRawData('data/mdb/custom.xml')).music
}
} catch (e) {
console.error(e.stack);
@ -60,11 +16,7 @@ export const playableMusic: EPR = async (info, data, send) => {
}
if (music.length == 0) {
if (version == 'exchain') {
music = _.get(await ExchainMusic(), 'music', []);
} else {
music = _.get(await MatixxMusic(), 'music', []);
}
music = _.get(await processDataBuilder(version), 'music', []);
}

View File

@ -1,3 +1,5 @@
import { getEncoreStageData } from "../data/extrastage";
export const shopInfoRegist: EPR = async (info, data, send) => {
send.object({
data: {
@ -19,6 +21,7 @@ export const gameInfoGet: EPR = async (info, data, send) => {
bonus_musicid: K.ITEM('s32', 0),
},
bear_fes: {},
nextadium: {},
};
const time = BigInt(31536000);
for (let i = 1; i <= 20; ++i) {
@ -42,6 +45,18 @@ export const gameInfoGet: EPR = async (info, data, send) => {
term: K.ITEM('u8', 0),
sticker_list: {},
};
addition['thanksgiving'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
addition['lotterybox'] = {
...obj,
box_term: {
state: K.ITEM('u8', 0)
}
};
} else {
addition[`phrase_combo_challenge_${i}`] = obj;
}
@ -59,16 +74,20 @@ export const gameInfoGet: EPR = async (info, data, send) => {
}
}
const extraData = getEncoreStageData(info)
await send.object({
now_date: K.ITEM('u64', time),
now_date: K.ITEM('u64', BigInt(Date.now())),
extra: {
extra_lv: K.ITEM('u8', 10),
extra_lv: K.ITEM('u8', extraData.level),
extramusic: {
music: {
musicid: K.ITEM('s32', 0),
get_border: K.ITEM('u8', 0),
},
},
music: extraData.musics.map(mid => {
return {
musicid: K.ITEM('s32', mid),
get_border: K.ITEM('u8', 0),
}
})
}
},
infect_music: { term: K.ITEM('u8', 0) },
unlock_challenge: { term: K.ITEM('u8', 0) },

View File

@ -45,6 +45,7 @@ export const check: EPR = async (info, data, send) => {
const playerInfo = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
if (playerInfo) {
@ -90,6 +91,7 @@ export const getPlayer: EPR = async (info, data, send) => {
const name = await DB.FindOne<PlayerInfo>(refid, {
collection: 'playerinfo',
version
})
const dmProfile = await getProfile(refid, version, 'dm')
const gfProfile = await getProfile(refid, version, 'gf')
@ -388,12 +390,21 @@ export const getPlayer: EPR = async (info, data, send) => {
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),
},
secretmusic: { // TODO: FIX THIS
music: _.merge(_.range(0,2800), _.range(5000, 5100)).map(mid => {
return {
musicid: K.ITEM('s32', mid),
seq: K.ITEM('u16', 255),
kind: K.ITEM('s32', 40),
}
}),
},
trbitem: { // TODO: FIX THIS
item: _.range(0,750).map(id => {
return {
itemid: K.ITEM('s32', id),
}
}),
},
chara_list: {},
title_parts: {},
@ -512,6 +523,20 @@ export const getPlayer: EPR = async (info, data, send) => {
unlock_status_6: K.ITEM('s32', 0),
unlock_status_7: K.ITEM('s32', 0),
},
thanksgiving: {
term: K.ITEM("u8", 0),
score: {
one_day_play_cnt: K.ITEM("s32", 0),
one_day_lottery_cnt: K.ITEM("s32", 0),
lucky_star: K.ITEM("s32", 0),
bear_mark: K.ITEM("s32", 0),
play_date_ms: K.ITEM("u64", BigInt(0))
},
lottery_result: {
unlock_bit: K.ITEM("u64", BigInt(0))
}
},
lotterybox: {},
...addition,
...playerData,
finish: K.ITEM('bool', 1),
@ -910,7 +935,7 @@ export const savePlayer: EPR = async (info, data, send) => {
scores[mid].update[1] = newSkill;
}
scores[mid].diffs[seq] = {
scores[mid].diffs[seq] = { //FIXME: Real server is bit complicated. this one is too buggy.
perc: Math.max(_.get(scores[mid].diffs[seq], 'perc', 0), perc),
rank: Math.max(_.get(scores[mid].diffs[seq], 'rank', 0), rank),
meter: meter.toString(),

View File

@ -2,23 +2,39 @@ 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";
import { isAsphyxiaDebugMode, isRequiredCoreVersion } from "./utils";
export function register() {
if(!isRequiredVersion(1, 20)) {
if(!isRequiredCoreVersion(1, 20)) {
console.error("You need newer version of Core. v1.20 or newer required.")
}
R.GameCode('M32');
R.Config("encore_version", {
name: "Encore Version",
desc: "Set encore version",
type: "integer",
default: 13,
})
R.Config("nextage_dummy_encore", {
name: "Dummy Encore for SPE (Nextage Only)",
desc: "Since Nextage's Special Premium Encore system is bit complicated, \n"
+ "SPE System isn't fully implemented. \n"
+ "This thing is bandage of these problem as limiting some Encores for SPE.",
type: "boolean",
default: false
})
R.Config("enable_custom_mdb", {
name: "Enable Custom MDB",
desc: "For who uses own MDB",
desc: "For who uses own MDB. eg) Omnimix.",
type: "boolean",
default: false,
})
R.DataFile("data/custom_mdb.xml", {
R.DataFile("data/mdb/custom.xml", {
accept: ".xml",
name: "Custom MDB",
desc: "You need to enable Custom MDB option first."
@ -30,7 +46,7 @@ export function register() {
// Helper for register multiple versions.
R.Route(`exchain_${method}`, handler);
R.Route(`matixx_${method}`, handler);
// TODO: NEXTAGE
R.Route(`nextage_${method}`, handler)
// TODO: TB, TBRE and more older version?
};
@ -46,4 +62,12 @@ export function register() {
MultiRoute('cardutil.check', check);
MultiRoute('gametop.get', getPlayer);
MultiRoute('gameend.regist', savePlayer);
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}`)
if (isAsphyxiaDebugMode()){
console.error(`Received Request: ${JSON.stringify(data, null, 4)}`)
}
})
}

View File

@ -2,7 +2,7 @@ export interface Scores {
collection: 'scores';
game: 'gf' | 'dm';
version: string;
version?: string;
pluginVer: number
scores: {

View File

@ -11,9 +11,14 @@ export const getVersion = (info: EamuseInfo) => {
return moduleName.match(/([^_]*)_(.*)/)[1];
};
export function isRequiredVersion(major: number, minor: number) {
export function isRequiredCoreVersion(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
};
export function isAsphyxiaDebugMode() {
const argv = process.argv
return argv.includes("--dev") || argv.includes("--console")
}